20220131のJavaScriptに関する記事は16件です。

最速の開発

素早く開発するコツ 無料で利用でき、特に小規模な開発やマークダウンを書く際はWeb版VsCodeが役に立ちます。Chromebookと一緒に使うと相性がよく数秒から数分で目的を達成できることは非常に喜ばしいです。また、ネットが繋がらない環境でも使える点も非常に素晴らしいです。 メリット 速く使える Google AppsやNotionなどのWebアプリと相性がいい 無料 デメリット 多くの言語はデバックできない 日本語UIの非対応(実はGoogle翻訳が起動する場合日本語化します) 中規模開発や大規模開発が苦手(中規模開発以上の場合インストール版やIDEを検討してみよう) シェルが使えない 開発オススメの言語 以上を踏まえて以下の言語に特にオススメです。 javascript html css markdown json google apps script(コードの検討) 使った感想 気がついたときに早く使えるので速度に関するイライラはなくなるが、機能制限や中規模から大規模開発の苦手さやできないこともあるのでその時に応じた使い分けすると非常に良い。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

N予備プログラミング 〜冬コンテスト作品制作過程を晒してみる⑥〜

みなさんこんばんわ。 一月後半から、冬コンテスト作品作って今年こそ出品したい!と思って頑張ってきましたが、 1月31日21時現在、思い描いてたソフトになってません。。 とても悔しくて、全く諦めがついてませんが、記事を読んで参考にして頂いた方々に現時点で区切りのご報告です。 トップページ。夏作品VRCalc(covid19ワクチン検討リスクチェッカー)では、毎回各項目を入力する仕様だった。ユーザーを覚えるようにできた点では満足。 おなまえ(モジュール名dataset)展開画面。 しかしっ!!肝心の、最新のリスク状況を”計算する”ボタンを押しても!!うんともすんとも。。うごかにゃい。ぐすん(泣) ワクチン摂取状況の部分は、だいたいねらった形にできたんですけどね。 F12ボタンを押してコンソールをチェックしてみたところ。「HTMLからのvalueがnullですよ」とおこられてる。。 サーバーログにeRNumber.csvと見えるから、夏作品のvRCalc.jsからなんらかのデータは渡ってるっぽい。 けど、uuid型入力構文が不正です。とか、PromiseRejectionとか色々おこられてる。。 『予定調整くん』を改造して、npx webpackで前作vRCalc.jsをぶち込んだらうまくいくんじゃないか?と目論んでましたが。。そんなに甘くはなかったみたい。ウワァァァン!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js(React)をざっくり始める

こんにちは!いまだにreturnの綴りが怪しいフロントエンドエンジニアです。 今回は自分の備忘録も兼ねて、Next.jsの概念についてまとめたいと思います。(Reactと重複する内容有/今回は概念だけですので、詳しい書き方については書いていません。) その前にまず私の状況 今までVue.jsしかやったことがなく、React系を始めて1か月経ったところです。 そのためかなり知識が未熟なところがあります。他のブログ/本/動画などを参考にし、補助的にこちらを見て頂ければと思います。 そのような状況で今回記事を書いた理由は、  ・「とりあえず書き始めたい!」という人向けの記事が少ないように感じたため  ・TypeScriptのNext.jsの資料が少ない というところです。 サイトパフォーマンス等一旦気にせず、ざっくり始めたい人向けになるべく分かりやすく説明したいと思います。 目次 1.基本的な概念 2.ページについて 3.拡張子について 4.use/memoで囲む 5.型について 6.APIについて 7.まとめ 8.おまけ 1. 基本的な概念 ■ファイルの中身 大体1ファイルの中身はこのような構成です。拡張子については後ほど説明します。 ■コンポーネントについて Nextは「とにかく部品を作って、それをページに埋めていく!」という考えのもと作っていきます。(ここで言う「ページ」は実際にサイトとして見せる用のファイルの事) 例えば、ボタンを3つ使いたいページを作る際に。。。 そのため、部品(コンポーネントファイル等)を1つ作成して、それを使いたい度に呼び出してページに埋めていきます。 ■コンポーネント以外の部品達 「部品」はコンポーネントの他にも色々あります。  ・コンポーネント:HTMLにて埋め込む部品(ボタン/フォーム/ヘッダー等々…)  ・カスタムhook:色々なところで使いたいメソッド  ・プロバイダー:色々なところで使いたい値  ・タイプ:型をまとめておく  等々… それぞれのファイルの置き場所はこのような感じです。 hooksフォルダを作成して、その中にカスタムhookファイルを作成するイメージ ※ちなみにカスタムhookのカスタムは、Nextに元々用意されているhookがあるので、自分で作成したhookの事をカスタムhookと呼んでいます。 ■コンポーネントの作り方 細かいところは後ほど説明していきます。一旦コンポーネントの書き方のみ紹介です。 コンポーネント //Buttonというコンポーネント //export const コンポーネント名:型(()=>{ページの内容}) export const Button: FC = memo(() => { //これを埋め込んだ際に返すHTML(このHTMLが表示される) return ( <><button>ボタン</button></> ); }); ページ <!--埋め込みたいところにこれを書けば埋め込める--> <Button/> という事でこのように色々な部品を埋め込んで1ページを作成していくイメージになります。 ■コンポーネントに値を渡す:Props/Children しかしここでこのような事に気が付きませんでしょうか。。 このような問題を解決するために、Propsという概念が存在します。 Propsは一旦コンポーネント内の値を「仮」にしておいて、ページで使う際に入れる値を決定出来るシステムです。 コンポーネント //最初に受け取るPropsの型を宣言 export type Props = { label: string; }; //propsを使用する際には()の中にpropsを入れる export const Button: FC<Props> = memo((props) => { //Propsで受け取る値を宣言 const { label } = props; //これを埋め込んだ際に返すHTML(このHTMLが表示される) //ここのlabel部分に渡された値が入る return ( <><button>{label}</button></> ); }); ページ <!--ボタンコンポーネントのラベルに「検索」を渡す--> <Button label={検索} /> 同じようにChildrenという概念も存在します。 これは挟んで値を渡してあげるやり方です。渡す値が大きい範囲の際はこちら利用すると良いかも。 コンポーネント //ここの書き方は固定 type Props = { children: ReactNode; }; //propsを使用する際には()の中にpropsを入れる export const Button: FC<Props> = memo((props) => { //Propsで受け取る値を宣言 const { children } = props; return ( <><button>{children}</button></> ); }); ページ <!--ボタンコンポーネントのchildrenに「検索」を渡す--> <!--挟んだ値が渡される--> <Button>検索</Button> 1のまとめ ・Nextは部品を作成してページに埋めていく ・コンポーネント:HTML/hooks:メソッド/providers:値/types:型 ・コンポーネントはProps/Childrenで値を渡せる 2. ページについて 先程「ページはサイト表示用ファイル」と述べましたが、超厳密にいうと表示しているのは「ページを埋めたレイアウトコンポーネントを埋めた_app.jsx」です。ここを理解するのもなかなか難しい。。 それぞれの書き方も見ていきたいと思います。 ■ページファイル コンポーネントは上でexportしてたけど、ページは下でexportする。 //const ページ名 :型 const Page: NextPage = () => { return ( <>ページの内容</> ); }; export default Page; ■レイアウトコンポーネント Childrenのところにページファイルが入る export const Layout: FC<Props> = memo((props) => { const { children } = props; return ( <> <Header/> <!--ヘッダーコンポーネント--> <main>{children}</main> <Footer /> <!--フッターコンポーネント--> </> ); }); ■_app.tsx Headに書く事なければ初期のままでOK function MyApp({ Component, pageProps }: AppProps) { return ( <> <Head> <title>サイトのタイトル</title> <meta name="description" content="Generated by create next app" /> <link rel="icon" href="/image/favicon.ico" /> </Head> <Component {...pageProps} /> </> ); } export default MyApp; 2のまとめ ・全体に表示させたい内容はレイアウトコンポーネントにて ・全体の設定(Headタグ等)は_app.tsxにて ・コンポーネントは上でexport/ページは下でexport 3. 拡張子について ファイルの拡張子なのですが、.ts(.js)と.tsx(.jsx)が存在します。 この違いは「そのファイルは、最後何でreturnしているか」です。 ・値/メソッド等、TypeScript形式でreturn→.ts(カスタムhook等) return num1; ・HTMLの形でreturn→.tsx(ページ/コンポーネント等) return (<button>{label}</button>); 3のまとめ ・「そのファイルは、最後何でreturnしているか」でファイルの拡張子を決める ・HTML → .tsx ・値/メソッド → .ts 4. use/memoで囲む Nextでは以下のuse/memoを使って、変数等を囲む必要があります。 それぞれの理由を説明すると長くなるのでここでは置いておきます。 囲む対象 使うもの メソッド外で宣言している変数 useState 関数 useCallback コンポーネント memo (※使いたいときのみ)関数:値の変更→発動        useEffect 囲む/囲まないを場合によって判断する必要がある様ですが、その判断基準が人によって割とまちまちなため、一旦は全てにつけて問題ないかと思います。とにかく、コンポーネントはmemo/変数はuseState/関数はuseCallbackで囲む!です。(useEffectは使いたいときのみ…) ■変数:useState //この先aaaの中身を初期値から書き換えない時 const [aaa] = useState("初期値") //この先aaaの中身を書き替える予定の時 //後ろの値はset変数名の形にする //letでなく、constでOK const [aaa,setAaa] = useState("初期値"); //aaaの中身を書きかえる時 setAaa("書き換えたい内容") ///NG! aaa = "書き換えたい内容" メソッド外で宣言している変数にはuseState必要だけど、 とあるメソッド内でしか使っていない場合は不要 //num1はuseState必要 const [num1,setNum1] = useState(0) const aaa = useCallback(()=>{ //num2はこのメソッド内でしか使っていないのでuseStateいらない const num2 = 1; setNum1(num2); },[num1,setNum1]) ■関数:useCallback 後ろの[]には、処理に使っている値が入ります。 VScodeのESLintを使っていれば恐らく下線が引かれるので、クイックフィックスで直せます。。 //囲まない場合 console.log("関数"); const aaa =()=>{console.log("関数")} //囲む場合 useCallback()=>{console.log("関数");},[]); const aaa = useCallback(()=>{console.log("関数");},[]); ■コンポーネント:memo これを export const Button: FC = () => { return ( <><button>ボタン</button></> ); }; memo()で囲んでこうする コンポーネント export const Button: FC = memo(() => { return ( <><button>ボタン</button></> ); }); ■useEffect []の中の値が変更された際に発動する。 []を空にするとmount時(このページが作られる際)に自動発動 useEffect()=>{console.log("関数");},[num1]); const aaa = useEffect(()=>{console.log("関数");},[num1]); 4のまとめ ・コンポーネントはmemo/変数はuseState/関数はuseCallbackで囲む! ・useEffectは値の中身が変更されたタイミングで発動/始めに自動発動させることが出来る 5. 型について ここがNextの資料はTypeScriptで書かれているものが少なく、苦労した点です。。 ざっくり、覚えておくと良さそうな型を紹介します。 ■ページファイル NextPageをつける const AaaPage: NextPage = () => {} ■コンポーネントファイル FCとVFCがあるようですが、私はFCを使用しています。 //propsなし export const Button: FC = memo(() => {}) //propsあり export const Button: FC<Props> = memo((props) => {}) ■useStateで型を設定する時 const [aaa]=useState<string>(""); ■propsで定義する際に役に立ちそうな型 export type Props = { title: string; //ここに入れる型 }; //メソッド method: () => void; //文字 mozi: string;  //数 id: number; //true・false isOpen: boolean; //2種類のどちらかしか入らない時  color: "赤" | "青";  //onChange(ユーザが値を入力して、値の中身が変わったら発動する) onChange: ChangeEventHandler<HTMLInputElement>; ■APIでとってきた値に型を設定する type.ts //types>type.tsで型の設定をしておく export type Mailtype = { email: string; }; ページ //APIで取得したデータ:dataの中の「メール」に型をつける const data: Mailtype = data.userMail; 6. APIについて 皆さんおなじみ(?)axiosやfetchで呼べるのですが、こちらも「APIを呼ぶタイミング」によって囲まなくてはいけません。ここについてはまだ私も勉強不足なので異なる点があればご教示ください。 とにかく  ・SSG→静的なページの初期表示用(早い)  ・SSR→動的なページ初期表示用(遅い)  ・useSWR→動的なページ リアルタイム更新用(遅い) という認識です。APIの種類によってこれらを使い分ける事がミソです。 7. まとめ 1. Nextは部品を作成してページに埋めていく ・コンポーネント:HTML ・hooks:メソッド ・providers:値 ・types:型 コンポーネントはProps/Childrenで値を渡せる 2. ・全体に表示させたい内容はレイアウトコンポーネント ・全体の設定(Headタグ等)は_app.tsx ・コンポーネントは上でexport ・ページは下でexport 3. 「そのファイルは、最後何でreturnしているか」でファイルの拡張子を決める ・値、メソッド → ts ・HTML → tsx 4. ・コンポーネントはmemo ・変数はuseState ・関数はuseCallbackで囲む! 6. APIの呼び方はタイミングによって書き方が異なる。 ・SSG→静的なページの初期表示用(早い) ・SSR→動的なページ初期表示用(遅い) ・useSWR→動的なページ リアルタイム更新用(遅い) 今後追加したい項目: ・onChange/onClick等イベントハンドラーについて ・mapで回す ここまで記事をお読みいただきありがとうございました! 8. おまけ Next.js/Reactを勉強するにあたり読んだ資料達 ■本 ■Next.js公式チュートリアル ■YouTube (さいごに: 色々教えてくれたお二人ありがとうございました!@suzu1997 @hiroki-yama-1118)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

divの4辺の内、どの辺からマウスが出たかを判定する

StackOverflowで発見し、いつか使いそうと思ったので備忘録。 https://stackoverflow.com/questions/44878011/detect-from-which-side-the-mouse-left-a-div See the Pen divのどの辺からマウスが出たかを調べる by Yuki (@yuki-yoshimura) on CodePen.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactの再レンダリングの仕組み

現在Reactを学んでいるWeb系への転職を目指している者です。 ポートフォリオをSPA化するために学んでいます。 自分用のメモのために残します。 再レンダリングされるときの条件 1.stateが更新されたコンポーネントは再レンダリングされる。 2.propsが変更されたコンポーネントは再レンダリングされる。 3.再レンダリングされたコンポーネント配下の子要素はすべて再レンダリングされる。 大きくこの3つの条件でレンダリングされます。 検証のためにはcodesandboxを使用します。 1.stateが更新されたコンポーネントは再レンダリングされる。 再レンダリングされるとconsole.logでレンダリングされましたというのが表示されるようなコードを作成しました。 App.jsx import "./styles.css"; import React, { useState } from "react"; const style = { margin: "20px", padding: "20px", backgroundColor: "pink" }; const textBox = { width: "100%" }; export default function App() { const [text, setText] = useState(""); const onChangeText = (event) => setText(event.target.value); console.log("レンダリング"); return ( <> <div style={style}> <input style={textBox} value={text} onChange={onChangeText} placeholder="文字を入力してください" /> </div> </> ); } このコードではテキストボックスに文字入力されるたびにstateが更新されます。 画面を表示した状態ではレンダリングは1回されているので、コンソールにはレンダリングの文字が1回表示されます。 今回は文字を5回入力したので、stateが5回更新されます。 表示したときのものと合わせて計6回コンソールにレンダリングの文字が表示されます。 2.propsが更新されたコンポーネントは再レンダリングされる。 先ほどのテキストボックスをコンポーネント化しました。 改修したコードはこちらになります。 TextBox.jsx const style = { margin: "20px", padding: "20px", backgroundColor: "pink" }; const textBox = { width: "100%" }; export const TextBox = (props) => { const { text, onChangeText } = props; console.log("TextBoxコンポーネントが更新された"); return ( <div style={style}> <input style={textBox} value={text} onChange={onChangeText} placeholder="文字を入力してください" /> </div> ); }; App.jsx import "./styles.css"; import { TextBox } from "./components/TextBox"; import React, { useState } from "react"; const style = { margin: "20px", padding: "20px", backgroundColor: "pink" }; export default function App() { const [text, setText] = useState(""); const onChangeText = (event) => setText(event.target.value); console.log("レンダリング"); return ( <> <h1 style={style}>レンダリングの条件を理解する</h1> <TextBox text={text} onChangeText={onChangeText} /> </> ); } この状態で文字を入力すると子コンポーネントであるTextBox.jsxに記述した「TextBoxコンポーネントがレンダリングされた」というコンソールも表示されていることがわかります。 3.再レンダリングされたコンポーネント配下の子要素はすべて再レンダリングされる。 ここからが注意しなければならない部分になります。 まずは新たなコンポーネントを作成します。 ChildArea.jsx const style = { margin: "20px", padding: "20px", backgroundColor: "pink" }; const textBox = { width: "100%" }; export const TextBox = (props) => { const { text, onChangeText } = props; console.log("TextBoxコンポーネントがレンダリングされました"); return ( <div style={style}> <input style={textBox} value={text} onChange={onChangeText} placeholder="文字を入力してください" /> </div> ); }; 新たなコンポーネント作成に伴い既存のコードも改修したので、載せます。 TextBox.jsx const style = { margin: "20px", padding: "20px", backgroundColor: "pink" }; const textBox = { width: "100%" }; export const TextBox = (props) => { const { text, onChangeText } = props; console.log("TextBoxコンポーネントがレンダリングされました"); return ( <div style={style}> <input style={textBox} value={text} onChange={onChangeText} placeholder="文字を入力してください" /> </div> ); }; App.js import "./styles.css"; import { TextBox } from "./components/TextBox"; import { ChildArea } from "./components/ChildArea"; import React, { useState } from "react"; const style = { margin: "20px", padding: "20px", backgroundColor: "pink" }; const buttonStyle = { margin: "20px", padding: "20px" }; export default function App() { const [text, setText] = useState(""); const [display, setDisplay] = useState(false); const onChangeText = (event) => setText(event.target.value); const onClickChanged = () => setDisplay(!display); console.log("レンダリング"); return ( <> <h1 style={style}>レンダリングの条件を理解する</h1> <TextBox text={text} onChangeText={onChangeText} /> <button style={buttonStyle} onClick={onClickChanged}> 表示 </button> <br /> <ChildArea display={display} /> </> ); } これにより表示ボタンを押すと画像が表示される機能が追加されました。 画像を表示させる。 画像を非表示させる。 ここでの問題というのは再レンダリングされたコンポーネントの子コンポーネントはすべて再レンダリングされてしまいます。 コンソールを確認してみると。 ・文字をテキストボックスに入力した場合 ・表示ボタンをクリックした場合 どちらも関係ないコンポーネントまで再レンダリングされてしまいます。 要するにすべてのコンポーネントが呼び出されています。 子要素がすべて再レンダリングされる場合の解決方法。 コンポーネントをmemoで囲む。 memoで囲まれた関数はpropsが渡された時のみ、再レンダリングされるという指定がされます。 memo化の方法 ChildArea.jsx // memo化に必要 import { memo } from "react"; const imageStyle = { height: "100px", width: "100px" }; // 関数をmemo()で囲んであげればmemo化が完成。 export const ChildArea = memo((props) => { const { display } = props; console.log("ChildAreaがレンダリングされました"); return ( <> {display ? ( <img style={imageStyle} src="images/account.jpeg" alt="Logo" /> ) : null} </> ); }); 今回はchildAreaコンポーネントをmemo化しました。 これで文字を入力した場合にChildAreaコンポーネントが再レンダリングされることはないはずです。 確認してみると。 試しの文字を二回入力しました。 ページが開かれた際に呼び出されたのみで、再レンダリングはされていないことがわかります。 これでpropsが渡されたときのに再レンダリングされる機能の実装ができました。 memo化の注意点 memo化したコンポーネントに関数を渡す場合は、渡す関数は再レンダリングの際に再生成されるpropsと判断されるためmemo化がうまく機能しなくなります。 検証するために表示ボタンもChildAreaコンポーネントに組み込みます。 ChildArea.jsx import { memo } from "react"; const imageStyle = { height: "100px", width: "100px" }; const buttonStyle = { margin: "20px", padding: "20px" }; export const ChildArea = memo((props) => { const { display, onClickChanged } = props; console.log("ChildAreaがレンダリングされました"); return ( <> <button style={buttonStyle} onClick={onClickChanged}> 表示 </button> <br /> {display ? ( <img style={imageStyle} src="images/account.jpeg" alt="Logo" /> ) : null} </> ); }); App.js import "./styles.css"; import { TextBox } from "./components/TextBox"; import { ChildArea } from "./components/ChildArea"; import React, { useState } from "react"; const style = { margin: "20px", padding: "20px", backgroundColor: "pink" }; export default function App() { const [text, setText] = useState(""); const [display, setDisplay] = useState(false); const onChangeText = (event) => setText(event.target.value); const onClickChanged = () => setDisplay(!display); console.log("レンダリング"); return ( <> <h1 style={style}>レンダリングの条件を理解する</h1> <TextBox text={text} onChangeText={onChangeText} /> {/* 表示ボタンをChildAreaコンポーネントに追加したため関数をpropsとして渡しています。 */} <ChildArea display={display} onClickChanged={onClickChanged} /> </> ); } このコードで注目して欲しいのは、ChildAreaコンポーネントに関数をpropsとして渡しているということです。 関数をpropsでコンポーネントに渡すとmemo化がうまく働かなくなります。 試しに文字を入力した際のコンソールを確認してみます。 memo化したはずなのに再レンダリングが起きてしまっています。 関数をpropsで渡した際にmemo化がうまく動いていない原因 原因は新たなpropsが渡されていると判断されてしまっているからです。 なぜなら、関数はレンダリングされるたびに再生成されていると判断されているため、同じ名前の関数でもレンダリングされる前とは別の関数として認識されてしまうからです。 そのため、memoがうまく機能しません。 改善方法 useCallBackを使用する。 memo化したコンポーネントに渡す関数を、useCallBackで囲むことで、第二引数に指定した配列の値が変化する時のみ再生成されると設定できる。 配列が空の場合は最初のレンダリング時から再生成されなくなります。 それでは、useCallbackで関数を囲みます。 App.js const onClickChanged = useCallback(() => setDisplay(!display), [display]); これにより変数であるdisplayが変化する時のみonClickChangedが再生成されるように設定できました。 それではまた、試しに文字を入力した際のコンソールを確認してみます。 今回はしっかりとmemo化が行われていることがわかります。 まとめ Reactを扱う際に基本的な部分だったと思いますが、基本が大切だと思うのでメモを残しました。 この記事が私と同じくReactを勉強し始めた人の助けになったらうれしいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

国当てルーレットクイズをGitHubからwebサーバーを使わずに公開する。

昨年の夏ぐらいにJavaScriptとLeafletの練習用に作っていた国名当てのクイズアプリのソースコードをGitHubで公開してみました。 ソースコードも画像ファイルなんかも全部公開しなくてはいけなくなるので公開OKのフリーの素材を使って作っています。 フリーのデータは(特に海外の世界地図データ)は国境や海岸線等もGISでいろいろいじらなくてはいけなかったのでそのまま使えるってわけじゃないですね。 音はMacのGarageBandで作ったので一応okのはずです。 音を使うとずっとダウンロードしちゃうかもしれませんのでwifi必須でお願いします。。。。 GItHubで公開するのはすごい簡単で - GitHubのリポジトリの「setting」→「Options」→「Danger Zone」の「Change repository visibility」をpublicに変更するだけでした。 - あとはリポジトリの直下にあるindex.htmlを読みにいけばよいです。 - https://(アカウント名).github.io/(リポジトリ名)/index.html - 私のアプリはhttps://0712kazu.github.io/roulette_World/index.htmlとなります。 - アプリ 非常に簡単でびっくりしました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】数値のカウントアップ・ダウン

採用サイトでよく見かけるようになった、スクロールで数値がカウントアップ表示されるアレ。 プラグインを利用したくない HTML のdata属性でオプションを設定したい というわけで、自作してみました! 仕様 全般 文書構造を考慮し、HTMLにはカウント後の数値を設定する カウントしたい要素にCSSで opacity: 0; を設定する JavaScript でカウント後からカウント開始数へと変更しているため読み込み時のチラツキ対策 カウントしたい要素にclass(js-countNumber)を付与する 画面と数値が交差した時に特定のclass(is-inView)が付与されたらカウント開始 オプション カウントのオプションはHTMLのdata属性に設定します。 項目 デフォルト data属性 カウント開始数 0 data-count-start アニメーション秒数 2000 data-count-duration イージング なし data-count-easing イージングについては止まる時に適用したいので、以下の4つが設定可能です。 easeOutQuad easeOutCubic easeOutQuart easeOutQuint JavaScript Counter JS class Counter { constructor() { this.countNums = document.querySelectorAll('.js-countNumber') this.easings = { easeOutQuad: (t) => t * (2 - t), easeOutCubic: (t) => --t * t * t + 1, easeOutQuart: (t) => 1 - --t * t * t * t, easeOutQuint: (t) => 1 + --t * t * t * t * t } this.inViewClass = 'is-inView' } init() { for (const el of this.countNums) { this.setStartNumber(el) this.observeNumber(el) } } // カウント開始数を設定 setStartNumber(el) { const start = Number(el.dataset.countStart) || 0 el.dataset.countEnd = el.textContent el.textContent = start el.style.opacity = 1 } // 数値のclass属性を監視 observeNumber(el) { // オブザーバーの作成 const observer = new MutationObserver(() => { // 画面と数値が交差した時のclassが付与されたら if (el.classList.contains(this.inViewClass)) { this.countNumber(el) } }) // 監視の開始 observer.observe(el, { attributes: true, attributeFilter: ['class'] }) } // 数値のカウント countNumber(el) { const start = Number(el.dataset.countStart) || 0 const endStr = el.dataset.countEnd const end = parseFloat(endStr) const duration = Number(el.dataset.countDuration) || 2000 const decimal = endStr.split('.')[1] const digits = typeof decimal === 'undefined' ? 0 : decimal.length const easing = el.dataset.countEasing ? this.easings[el.dataset.countEasing] : null let startTime = null // ブラウザのフレームを更新するタイミング(16ms)毎に実行する関数 const countAnimation = (currentTime) => { if (!startTime) startTime = currentTime let progress = Math.min((currentTime - startTime) / duration, 1) // イージングの設定 if (easing) { progress = easing(progress) } // htmlに数値を設定 el.textContent = digits ? (progress * (end - start) + start).toFixed(digits) : Math.floor(progress * (end - start) + start) // カウントアニメーションを終了 if (progress < 1) { requestAnimationFrame(countAnimation) } } requestAnimationFrame(countAnimation) } } InView JS class InView { constructor() { this.inViews = document.querySelectorAll('.js-inView') this.inViewClass = 'is-inView' } init() { for (const el of this.inViews) { const options = { rootMargin: el.dataset.inviewRootMargin || '0px' } const callback = (entries) => { for (const entry of entries) { const isInViewToggle = el.dataset.inviewToggle === '' // 画面と要素が交差 if (entry.isIntersecting) { el.classList.add(this.inViewClass) // target.dataset.inviewToggle属性がない場合 // 表示後は監視を解除 if (!isInViewToggle) { observer.unobserve(el) } } // data-inview-toggle属性がある場合 // 画面内から消えたら要素のclassを削除 if (!entry.isIntersecting && isInViewToggle) { el.classList.remove(this.inViewClass) } } } const observer = new IntersectionObserver(callback, options) observer.observe(el) } } } DEMO See the Pen Number Count Up Down by Takuya Mori (@taqumo) on CodePen.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript で数値のカウントアップ・ダウン

採用サイトでよく見かけるようになった、スクロールで数値がカウントアップ表示されるアレ。 プラグインを利用したくない HTML のdata属性でオプションを設定したい というわけで、自作してみました! 仕様 全般 文書構造を考慮し、HTMLにはカウント後の数値を設定する カウントしたい要素にCSSで opacity: 0; を設定する JavaScript でカウント後からカウント開始数へと変更しているため読み込み時のチラツキ対策 カウントしたい要素にclass(js-countNumber)を付与する 画面と数値が交差した時に特定のclass(is-inView)が付与されたらカウント開始 オプション カウントのオプションはHTMLのdata属性に設定します。 項目 デフォルト data属性 カウント開始数 0 data-count-start アニメーション秒数 2000 data-count-duration イージング なし data-count-easing イージングについては止まる時に適用したいので、以下の4つが設定可能です。 easeOutQuad easeOutCubic easeOutQuart easeOutQuint JavaScript Counter JS class Counter { constructor() { this.countNums = document.querySelectorAll('.js-countNumber') this.easings = { easeOutQuad: (t) => t * (2 - t), easeOutCubic: (t) => --t * t * t + 1, easeOutQuart: (t) => 1 - --t * t * t * t, easeOutQuint: (t) => 1 + --t * t * t * t * t } this.inViewClass = 'is-inView' } init() { for (const el of this.countNums) { this.setStartNumber(el) this.observeNumber(el) } } // カウント開始数を設定 setStartNumber(el) { const start = Number(el.dataset.countStart) || 0 el.dataset.countEnd = el.textContent el.textContent = start el.style.opacity = 1 } // 数値のclass属性を監視 observeNumber(el) { // オブザーバーの作成 const observer = new MutationObserver(() => { // 画面と数値が交差した時のclassが付与されたら if (el.classList.contains(this.inViewClass)) { this.countNumber(el) } }) // 監視の開始 observer.observe(el, { attributes: true, attributeFilter: ['class'] }) } // 数値のカウント countNumber(el) { const start = Number(el.dataset.countStart) || 0 const endStr = el.dataset.countEnd const end = parseFloat(endStr) const duration = Number(el.dataset.countDuration) || 2000 const decimal = endStr.split('.')[1] const digits = typeof decimal === 'undefined' ? 0 : decimal.length const easing = el.dataset.countEasing ? this.easings[el.dataset.countEasing] : null let startTime = null // ブラウザのフレームを更新するタイミング(16ms)毎に実行する関数 const countAnimation = (currentTime) => { if (!startTime) startTime = currentTime let progress = Math.min((currentTime - startTime) / duration, 1) // イージングの設定 if (easing) { progress = easing(progress) } // htmlに数値を設定 el.textContent = digits ? (progress * (end - start) + start).toFixed(digits) : Math.floor(progress * (end - start) + start) // カウントアニメーションを終了 if (progress < 1) { requestAnimationFrame(countAnimation) } } requestAnimationFrame(countAnimation) } } InView JS class InView { constructor() { this.inViews = document.querySelectorAll('.js-inView') this.inViewClass = 'is-inView' } init() { for (const el of this.inViews) { const options = { rootMargin: el.dataset.inviewRootMargin || '0px' } const callback = (entries) => { for (const entry of entries) { const isInViewToggle = el.dataset.inviewToggle === '' // 画面と要素が交差 if (entry.isIntersecting) { el.classList.add(this.inViewClass) // target.dataset.inviewToggle属性がない場合 // 表示後は監視を解除 if (!isInViewToggle) { observer.unobserve(el) } } // data-inview-toggle属性がある場合 // 画面内から消えたら要素のclassを削除 if (!entry.isIntersecting && isInViewToggle) { el.classList.remove(this.inViewClass) } } } const observer = new IntersectionObserver(callback, options) observer.observe(el) } } } DEMO See the Pen Number Count Up Down by Takuya Mori (@taqumo) on CodePen.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

firestoreのコレクション内データをtimestampでソートする

具体例 消費期限前の果物をbeforeTodayFruitsList 消費期限後の果物をafterTodayFruitsList にふり分ける(そんな運用はしないだろうけど…)。 方法1:firestoreの複合インデックスを使用する firestoreのインデックスタブ、複合インデックスを作成し、item_idとstarted_idをAscendingで登録したのち、以下記述する。 https://firebase.google.com/docs/firestore/query-data/index-overview?hl=ja#single-field-indexes firebase .firestore() .collection('fruits') .where('item_id', '==', registor.docs[0].get('item_id').trim()) //日付が若い順にソート .orderBy('expiration_date', 'asc') .onSnapshot(fruitsList => { // 本日と比較して未来と過去に分け、昇順で表示させる this.beforeTodayFruitsList = [] this.afterTodayFruitsList = [] fruitsList.forEach(value => { if ( this.isBefore(value.get('expiration_date').toDate()) ) { this.beforeTodayFruitsList.push(value) } else if ( !(this.isBefore(value.get('expiration_date').toDate())) ) { this.afterTodayFruitsList.push(value) } }) 方法2:sort()を使用する this.beforeTodayFruitsListは昇順 this.afterTodayFruitsListは降順 にそれぞれソートしたい場合は複合インデックスで事足りないので以下。 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort https://stackoverflow.com/questions/7555025/fastest-way-to-sort-an-array-by-timestamp firebase .firestore() .collection('fruits') .where('item_id', '==', registor.docs[0].get('item_id').trim()) .onSnapshot(fruitsList => { // 本日と比較して未来と過去に分け、未来は昇順、過去は降順に表示させる this.beforeTodayFruitsList = [] this.afterTodayFruitsList = [] fruitsList.forEach(value => { if ( this.isBefore(value.get('expiration_date').toDate()) ) { this.beforeTodayFruitsList.push(value) } else if ( !(this.isBefore(value.get('expiration_date').toDate())) ) { this.afterTodayFruitsList.push(value) } }) this.beforeTodayFruitsList.sort(function (a, b) { return a.get('expiration_date') - b.get('expiration_date') }) this.afterTodayFruitsList.sort(function (a, b) { return b.get('expiration_date') - a.get('expiration_date') })
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テレワークでサボってない?そんな上司の不安に応えたい

皆さんテレワークされてますか? 私の働いている会社ではコロナでの出勤人数制限が進み、テレワークをしている人が多くいます。 そんな最中雑談半分で本当にテレワーク中まともに働いてるの?と言われたので少なくともPCの前にはちゃんと座っていることで上司を安心させてあげようと思いました笑 作ったもの カメラの前から一定時間いなくなるとLINEへメッセージが飛ぶ監視装置 こんなふうに動きます(動画) 使ったもの Teachable Machine codepen LINEmessagingAPI Node-red レシピ 1. Teachable Machine 画像モデルを使用し、機械学習させる。 通常業務中=カメラの前で普通に座っている 離席中=画面にいない 2. codepen ボタンを押すとカメラが起動、カメラから読みとった画像をTeachable Machineで作成した機械学習モデルに送り推論を返してもらう。結果離席中が継続するごとに変数でカウンタを積み増し、閾値を超えたらNode-redへデータを送信する。 See the Pen カメラを止めるな! by T.N (@kkaammaaddoo) on CodePen. //離席回数カウンタ let counter = 0; //画像の結果が離席の時変数counterでカウントアップ if (results[0].label == '離席'){ counter = counter + 1; console.log(counter);} else{ counter = 0 console.log(counter);} //離席カウンターが特定の値を超えたらnord-redにデータ送信 if (counter == 5) { axios.post('https://nodered-tn046.herokuapp.com/receiver',);} 3. Nord-red Nord-redでは下記のフローとした。 codepen側からデータ送信、Nord-redでデータを受信すると、 msg.payloadへメッセージを文字列で代入し、メッセージをLINEへ送信する。 まとめ チャレンジ内容としてはボチボチ動いたな~と言うのが実感です。 Teachable Machineに学習させる内容を赤外線カメラでも変えられたら、どこでも動作できそうですね。 こんなものが実装されない日を祈ってます笑
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テレワークでサボってない?そんな上司の不安に応えたいLINE Bot

皆さんテレワークされてますか? 私の働いている会社ではコロナでの出勤人数制限が進み、テレワークをしている人が多くいます。 そんな最中雑談半分で本当にテレワーク中まともに働いてるの?と言われたので少なくともPCの前にはちゃんと座っていることで上司を安心させてあげようと思いました笑 作ったもの カメラの前から一定時間いなくなるとLINEへメッセージが飛ぶ監視装置 こんなふうに動きます(動画) 使ったもの Teachable Machine codepen LINEmessagingAPI Node-red レシピ 1. Teachable Machine 画像モデルを使用し、機械学習させる。 通常業務中=カメラの前で普通に座っている 離席中=画面にいない 2. codepen ボタンを押すとカメラが起動、カメラから読みとった画像をTeachable Machineで作成した機械学習モデルに送り推論を返してもらう。結果離席中が継続するごとに変数でカウンタを積み増し、閾値を超えたらNode-redへデータを送信する。 See the Pen カメラを止めるな! by T.N (@kkaammaaddoo) on CodePen. //離席回数カウンタ let counter = 0; //画像の結果が離席の時変数counterでカウントアップ if (results[0].label == '離席'){ counter = counter + 1; console.log(counter);} else{ counter = 0 console.log(counter);} //離席カウンターが特定の値を超えたらnord-redにデータ送信 if (counter == 5) { axios.post('https://nodered-tn046.herokuapp.com/receiver',);} 3. Nord-red Nord-redでは下記のフローとした。 codepen側からデータ送信、Nord-redでデータを受信すると、 msg.payloadへメッセージを文字列で代入し、メッセージをLINEへ送信する。 まとめ チャレンジ内容としてはボチボチ動いたな~と言うのが実感です。 Teachable Machineに学習させる内容を赤外線カメラでも変えられたら、どこでも動作できそうですね。 こんなものが実装されない日を祈ってます笑
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでJavaScriptファイルからのパスの通し方

実現したいこと RailsのJavaScriptファイル内に記載した、画像・音楽ファイルへのパスを正常に読み込ませたい✅ 開発環境 Ruby 3.0.2 Rails 6.1.4.4 結論 パスの指定を以下のように、asset_pathを使用する。 ※ JavaScriptの記載は、viewファイルのscriptタグ内に記載するか、hghg.js.erbのように、Rubyの埋め込みが可能な状態にする必要があります⚠️ 任意のファイル var ASSETS = { sound: { music: "<%= asset_path("energy.mp3") %>", ring: "<%= asset_path("tamborine.mp3") %>", }, json: { beatmap: "<%= asset_path("notes.json") %>" }, image: { 'bg': "<%= asset_path("club.jpg") %>" } }; manifest.jsにassets内の読み込むフォルダを指定する。 app/assets/config/manifest.js //= link_tree ../images //= link_tree ../music //= link_tree ../json これでOK!! エラー表示 間違ったパスの指定をしていた際のエラーも掲載しておきます? 下記コードのように、assets内の任意のファイルを相対パスで指定してもRouting Error(No route matches [GET] ... /assets...)になります。 任意のファイル var ASSETS = { sound: { music: "../../assets/music/energy.mp3", ring: "../../assets/music/tamborine.mp3", }, json: { beatmap: "../../assets/json/notes.json" }, image: { 'bg': "../../assets/images/club.jpg" } }; 最後に このエラーで数日詰まり、解決記事も他になかったので本記事を執筆しました✏️ 本記事が多くの悩める方に届けば幸いです!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

和訳: TypeScript 設計目標 (TypeScript Design Goals)

GitHub上のmicrosoft/TypeScript Wiki にある"TypeScript Design Goals"の和訳です。TypeScript という言語が目指しているもの(Goals)と目指していないもの(Non-goals)がまとめられています。TypeScriptで書いている理由を見失った場合に役に立つかも知れないような気がしたので、ここだけ翻訳してみました。 (原文) TypeScript Design Goals · microsoft/TypeScript Wiki https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals TypeScript 設計目標 (2020/2/27) 前書き この文書は、私達が TypeScript 言語の基礎にした基本的な設計原理の概要を説明するものです。完全ではありませんが、言語を形作ってきた多くの決定の根拠となる規則の要約をねらいとしています。これら規則のいくらかは主観的で、また時には互いに矛盾しています ― 適切なバランスを保ち適切な例外を作る事は、プログラミング言語がいかに成功するかどうかの本質的要素なのです。 目標(Goals) エラーになる可能性のある構成要素の静的な識別。 より巨大なコードへの構造化機構の提供。 生成されたプログラムに実行時のオーバーヘッドを課さない事。 簡潔で、慣用的かつ、分かりやすい JavaScript コードの生成。 組み合わせ可能で推論が容易な言語の作成。 現在および将来の ECMAScript の提案(proposals)に準拠している事。 全ての JavaScript コードの実行時の振る舞いを棄損しない事。 式レベルでの文法の追加を避ける事。 矛盾の無い、完全に消去可能な、構造化された型システムの使用。 クロスプラットフォームな開発ツールである事。 TypeScript 1.0 からの重大な破壊的変更を引き起こさない事。 目標でないもの(Non-goals) 既存言語の設計の完全なる模倣。代わりに、JavaScript の振る舞いとプログラム製作者の意図をこの言語の最も意味を成す指標として考える。 プログラム実行時のパフォーマンスの積極的な最適化。代わりに、実行環境の性能特性と上手く調和する、慣用的な JavaScript コードを生成する。 健全、もしくは「証明可能なほどに正しい」型システムの適用。代わりに、正確さと生産性のつり合いを取る。 エンドツーエンドのビルドパイプラインの提供。代わりに、より複雑なビルド・ワークフローのために外部ツールがコンパイラを使用できるよう、システムを拡張性のあるものにする。 プログラム実行時に型情報を追加したり、それに依存する事。または型システムの結果によって異なるコードを生成する事。代わりに、実行時のメタデータを必要としないプログラミングパターンを奨励する。 実行時の追加的機能やライブラリの提供。代わりに、既存ライブラリの記述に TypeScript を使用する。 ユーザーを驚かせるような振る舞いの導入。代わりに、他の一般的な言語で採用されているパターンに対して十分な考慮をする。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

個人開発2年続けて、やっぱりヒカキンはすごいってこと

こんにちは。ぬこすけです。 1年ほど前に「開設後3週間で収益10万円を得た個人開発サイトに立ち向かった話の全部を公開する」を記事を公開し、Qiitaのトレンドの載ったりTwitterでもシェアされるなど、多くの方に読まれてうれしい限りです。 さて、もうすぐ個人開発が2年目になるのですが、個人開発を続けていたからこそ思うことがいくつか思うところが出てきました。 これから個人開発をしようとしている方の参考になったり、あるいは今も個人開発を続けていて「あーこれわかる!」みたいな共感が得られたらうれしいです。 どんなサイト作ったの? 知らない方が多数だと思うので簡単に個人開発しているサイトを紹介します。 「ぬこぷろ」という技術書ランキングサイトを開発しています。 QiitaやTwitter、Youtubeなどから本の情報を取得して技術書を点数化するサイトです。 技術的には、 Next.js や Django , GCP などを使って開発されています。 サイトとしては月2〜3万PV稼いでおり、「Python 参考書」や「Java 参考書」、「GCP 参考書」などでググると1ページ目には表示されます(2022/1現在。環境によって変わります)。 サイトを企画した背景などは「開設後3週間で収益10万円を得た個人開発サイトに立ち向かった話の全部を公開する」を見ていただけるとわかりやすいと思います。 サイトの紹介はこんなところで、個人開発を2年続けて思ったことをポエムります。 やっぱりヒカキンはすごいってこと 「Youtuber」といえばヒカキンさん。 Qiitaでも「 HIKAKIN 」というタグが存在するほど、ヒカキンさんは有名でしょう。 そんなヒカキンさんですが、彼のすごいところって一体何でしょうか? もちろん、Youtubeの再生回数やビートボックスの上手さはありますが、 個人的には「ゼロからYoutubeを初めて、今もなお継続している」ことだと思います。 ヒカキンさんはスーパーの店員をしながらYoutubeを始めました。 継続的に動画を投稿し、2年間ほど経って「Super Mario Beatbox」という動画がバズったことをきっかけに有名になったそうです(Wikipediaより)。 ビートボックスの才能もありますが、ゼロからYoutbeを初め、2年間挫けず投稿し、そして今もなお投稿し続けていることがやっぱりすごいところだと思います。 個人開発も同じことが言えます。 ゼロからモノを作り、継続して開発し続ける。言うのは簡単なのですが、やるのは難しい。 私も昔 Ruby On Rails でURLからtitleタグやmetaタグなど抽出するWebアプリを作りましたが、リリースして満足してしまいました。結局、サイトはクローズしています(笑)。 個人開発を続けていると、Youtuberであれインスタグラマーであれ自然と「クリエイターってみんなすごいんやな」という視点が生まれます。 普段何も考えずに見ているYoutube、水着の美人なお姉さんを眺めるインスタグラムでも、継続的にコンテンツを作っているクリエイターは努力家です。 孤独。 自分で作ったものはやっぱり誰かに見せびらかしたいもの。 ワイ「こんなサイト作ったんや!」 友達「へぇ〜。どんくらい儲かるの?」 ワイ「月1万円くらいかなー」 友達「ふ〜ん」 このくらいの会話で終わります(笑)。 本人からしたら血のにじむような努力で作ったとしても、周りからしたらその努力は知りえません。 フロントとバックエンドのアプリケーションのコード書いて、データベース用意して、サーバー立てて、マーケティングや企画も考えて...全て一人でやるのは大変です。 私の場合は当時使ったことのないPythonやらReactやらで色々と苦労した覚えがあります。 収益に関しても、本人からしたら「自分の力だけで稼いだ!」と思っても、額が額だと周りからは「こんなもん?」しか思われません。 やってみるとわかるのですが、会社の力を借りず自分の力だけで稼ぐのは結構難しかったです。 ぬこぷろはリリースして数ヶ月はほぼ収益なし、1年後にようやく飲み代くらい稼げるようになりました。初めて収入が入った時はすごい嬉しかった記憶があります。 起業しかりYoutuberデビューしかり、身近に独力で何か始めた人がいたらかまってあげてください。 たぶん彼らは喜びます(笑)。 終わりなき道 さきほどヒカキンさんの継続力はすごいというお話をした手前、矛盾してしまうかもしれませんが、いつまで自分が作ったものに対して継続的に取り組むかという悩みもあります。 誰かに売却したり、あきらめたりしない限り、個人開発に終わりはありません。 今後開発を続けてちゃんと成果は出るのか、他に時間を費やしたいことはないのか、色々考えて継続すべきかを判断する必要があります。 もちろん、飽きたから次の開発へ、というのもアリだと思います。 私の場合、ぬこぷろの他にも新しいサイトの開発を進めています。 もちろん身体は一つしかないので、限りある開発時間をどちらかに割り振る必要があります。 開発を進めていくと、無限に課題が山積みになっていきます。 新しいサイトの開発をそっちのけで、アレもコレもと課題を消化していくうちに気づいたら2年弱が経っていました(笑) いつまで既存のアプリ開発を進めるか、新しいアプリの開発の方に専念するのか、悩ましいところです。 好きなことしかやらない問題 個人開発は自由なので、うまく自分の手綱を握らないと好きなことばかりしてしまいます。 私の場合はフロントエンドの開発をしがちでした。 特にパフォーマンスチューニングはハマってしまい、気づいたらLightHouseで98点になっていました(笑)。 ※ブラウザのシークレットモードで、拡張機能オフとキャッシュ削除した上でモバイルを計測 あまりにもこだわり過ぎて、ライブラリから自前コードでリプレイスもしていました。 例えば、、、 react-window 仮想無限スクロールを実現するためのライブラリ UX的に問題があったので自前作成 react-lazyload コンポーネントを遅延レンダリングするためのライブラリ 私の環境だとなぜか正常に動作しないので自前作成 react-autosuggest 検索ボックスのライブラリ componentWillReceivePropsというReactの非推奨のAPIを使っている、バグがissueに起票されていてもメンテされない、検索はWebWorkerに使いたい、などの理由で自前作成 アプリで最も重要なのはコンテンツです。 頭でわかっているのですがどうしても好きな方へ開発が向いてしまいます。 個人開発なので自由に開発できるのも強みですが、自分自身の手綱を握らないと永遠に自分の好きな開発ばかりしてしまうのも悩みです。 まとめ やっぱりヒカキンはすごいってこと やっぱりヒカキンはすごいってこと 孤独。 あなたが作ったものは誰も気に留めません。 終わりなき道 いつまで開発続けようか。 好きなことしかやらない問題 パフォーマンスチューニングしてればいいじゃない!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GraphQLとコードジェレータでスキーマファーストな開発

フロントエンドとバックエンドを結合テスト ...動かない みたいな手戻りは最小限に抑え、効率的な開発を行っていきましょう。 この記事では、GraphQLのスキーマ定義からコード生成してフロントエンドとバックエンドを実装するスキーマファーストな開発について紹介しようと思います。 また、この記事で紹介するコードは以下のリポジトリに置いてます。 技術スタック TypeScript バックエンド、フロントエンド共にTypeScriptで実装しています。 GraphQL.js GraphQLのJavaScriptライブラリです。 React フロントエンドはcreate-react-appで作りました。 Apollo GraphQL Client フロントエンドのGraphQLクライアントとしてApolloを使います。 以下で紹介するGraphQL Code GeneratorでスキーマからReact hooksをコード生成して利用します。 Fastify サーバーは、Fastifyを採用しました。また、FastifyでGraphQLサーバーを作るのにmercuriusというAdapterを使います。 GraphQL Code Generator スキーマ定義からフロントエンド、バックエンドのコードを自動生成するツールです。 Prisma DBクライアントです。prismaはスキーマ定義をもとにClientを生成したり、マイグレーションを実行してテーブルを作ったり、CLIコマンドが充実しています。 GraphQLのスキーマ定義 サンプルのリポジトリでは、backend/ディレクトリ下にあります。 schema.graphql scalar DateTime type Post { id: Int! title: String! content: String published: Boolean! author: User viewCount: Int! createdAt: DateTime! } type User { id: Int! email: String! name: String } type Query { allUsers: [User!]! draftsByUser(input: UserUniqueInput): [Post!] feed(q: String, sort: FeedSort, skip: Int, take: Int): [Post!]! } type Mutation { createDraft(authorEmail: String!, data: PostCreateInput!): Post deletePost(id: Int!): Post incrementPostViewCount(id: Int!): Post signup(data: UserCreateInput!): User togglePublishPost(id: Int!): Post } enum SortOrder { asc desc } input UserUniqueInput { email: String id: Int } input FeedSort { updatedAt: SortOrder! } input PostCreateInput { title: String! content: String } input UserCreateInput { email: String! name: String } このスキーマ定義をもとにクライアントサイドとサーバーサイドをそれぞれ実装していきます。 バックエンドの実装 graphql-codegenでResolverの型を生成 以下の公式ドキュメントを参考にGraphQL Code Generatorを使ってみます。 GraphQL Code Generatorで自動生成するのにプラグインを指定します。今回使ったプラグインは、 @graphql-codegen/typescript @graphql-codegen/typescript-resolvers です。 設定ファイル codegen.yml overwrite: true schema: "./schema.graphql" generates: src/lib/generated/graphql.ts: plugins: - typescript - "typescript-resolvers" 以下のコマンドを実行してResolverの型を生成します  yarn graphql-codegen --config codegen.yml Resolverを実装 Resolverの型が生成されたら、それをもとに実装していきます。 resolvers.ts export const resolvers: Resolvers<Context> = { Query: { allUsers: (_parent, _args, context, _info) => { return context.prisma.user.findMany() }, ... }, Mutation: { ... } } PrismaClientをContextを通してResolverで受け取ります。 Contextの実装は以下。 context.ts const prisma = new PrismaClient({ log: ['query', 'info', 'warn', 'error'], }) export interface Context { prisma: PrismaClient } export const context: Context = { prisma } export const buildContext = async ( _req: FastifyRequest, _reply: FastifyReply ): Promise<Context> => { return { prisma } } サーバーはFastifyを使いました。FastifyでGraphQLサーバーを実装するのにmercuriusというAdapterを使います。 上で実装したcontextとresolversも一緒に渡します。 server.ts import { resolvers } from './resolvers' import { buildContext } from './context' // Load schema from the file const schema = loadSchemaSync(join(__dirname, '../schema.graphql'), { loaders: [new GraphQLFileLoader()] }) // Add resolvers to the schema const schemaWithResolvers = addResolversToSchema({ schema, resolvers }) const fastify = Fastify() fastify.register(mercurius, { schema: schemaWithResolvers, context: buildContext, graphiql: true }) fastify.listen(4000, '0.0.0.0', (err, addr) => { if (err) { console.error(err) process.exit(1) } console.info(`Server listening on ${addr}`) }) あっという間にGraphQLサーバーが実装出来ちゃいましたね server.tsを実行すると、GraphQLサーバーが立ち上がります。 GraphiQLも立ち上がる設定にしてあるので、ブラウザで以下のURLで開きます http://localhost:4000/graphiql フロントエンドの実装 フロントエンドの実装を始める前に、モックサーバーを立ち上げてみます。 ApolloServerを使うことでGraphQLのスキーマからお手軽にモックサーバーを立ち上げられます。 スキーマ定義からモックサーバーを立ち上げ サンプルのリポジトリではfrontend/mock/ディレクトリ下にあります。 server.ts // Load schema from the file const schema = loadSchemaSync(join(__dirname, '../../server/schema.graphql'), { loaders: [new GraphQLFileLoader()] }) const server = new ApolloServer({ schema,     mocks: true }) server.listen().then(({ url }) => console.log(`? Server ready at ${url}`) ) server.tsを実行したら、ブラウザで以下のURLからApolloStudioを開けます。 http://localhost:4000 graphql-codegenでApolloクライアント生成 バックエンドと同じようにGraphQL Code Generatorでコード生成します。 以下の公式ドキュメントに従いながら進めました。 使ったプラグインは @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo です。 @graphql-codegen/typescript-react-apollo はApolloClientのReact hooksを生成するプラグインです。 今回は使用しませんでしたが、hooks以外にもComponentやHOCなども生成出来たり、その他の設定も豊富に用意されてあり、便利そうです。詳しくは公式のドキュメントを参照。 設定ファイル codegen.yml overwrite: true schema: "../backend/schema.graphql" documents: "graphql/**/*.graphql" generates: src/graphql/types.ts: plugins: - "typescript" - "typescript-operations" - "typescript-react-apollo" documents documentsは、リクエストするときに発行するクエリを定義します。 サンプルのリポジトリでは、frontend/graphql/下にあります。 post.graphql query feed($q: String!, $take: Int, $skip: Int, $sort: SortOrder!) { feed(q: $q, sort: { updatedAt: $sort }, take: $take, skip: $skip) { id title content viewCount createdAt author { id name } } } コード生成すると、ApolloClientでリクエストするフックが生成されます yarn graphql-codegen --config codegen.yml GraphQLサーバーへのリクエストを実装 上のコマンドでuseFeedQueryが生成されました。 これを使って、データを取得・表示するコンポーネントを作ります。 Feed.tsx export const Feed: React.FC<FeedProps> = ({ q, take, skip, sort }) => { const { loading, error, data } = useFeedQuery({ variables: { q, take, skip, sort } }) if (loading) { return (<div>loading...</div>) } if (error) { return (<div>{error.message}</div>) } const posts = data?.feed ?? [] return ( <div className='Feed'> {posts.map((post) => ( <div className='Feed-Row'> <a className='Feed-Row-Title' href='/'>{post.title}</a> {' '} <span className='Post-Views'>{post.viewCount}views</span> <div> <span className='Post-Author'>author: {post.author?.name}</span> {' '} <DateTime className='Post-DateTime' datetime={post.createdAt} /> </div> </div> ))} </div> ) } 動作チェック こちらのサンプルでは、docker-composeで立ち上がるようになっています。 docker-compose up ブラウザでhttp://localhost:3000を開いてみます。 サーバーからデータを取得して表示出来ていますね! いかがだったでしょうコードジェネレータを活用することで型安全に開発が進められ、尚且つ大幅に実装量を減らすことが出来ましたね 課題 以上で概ね型安全に実装出来たのですが、残念ながら完全には型安全になっていません。。。 問題は、schema.graphqlで定義したcustom scalarのDateTimeです。 バックエンドでは graphql-scalars というライブラリを使ってresolversでDateTimeResolverを渡してやることで、serializeしてレスポンスを返してくれています。 resolvers.ts export const resolvers: Resolvers<Context> = { ..., DateTime: DateTimeResolver, } 一方、クライアントサイドでGraphQL Code Generatorで生成された型を見てみると、DateTimeがany型になっていました。 types.ts export type Scalars = { ID: string; String: string; Boolean: boolean; Int: number; Float: number; DateTime: any; //←これ }; custom scalarのDateTimeをdeserializeしてDate型に変換する処理もコード生成して欲しいのですが、どうすればいいのか分からず。。。 現状方法ないんでしょうかね、、、
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JS]インラインスタイルを操作できるようになろう

アウトプットとして インラインスタイルとは HTML要素の開始タグの中に直接CSSのソースコードを記述するプロパティの指定方法。 JavaScriptでは,メソッドを使って指定した要素上に新しい属性を追加、または既存の属性の値を変更したり、属性を取得することができます。 setAttributeメソッド 指定した要素上に新しい属性を追加、または既存の属性の値を変更できます。 要素.setAttribute(name, value) 要素の取得の仕方はこちら HTMLの要素を取得する方法 nameは属性の名前を文字列で指定します。 valueは属性に設定したい値を指定します。 例 HTML <div id="test">テスト</div> JavaScript const sample = document.getElementById("test") sample.setAttribute("style", "color: red;") // <div id="test" style="color: red;">テスト</div> が取得できる removeAttributeメソッド 指定した要素から、特定の属性を削除できます。書き方はsetAttributeと同じです。 要素.removeAttribute(name, value) 例 HTML <div class="contents" id="apple">りんご</div> JavaScript const apple = document.getElementById("apple") apple.removeAttribute("class", "contents") // <div id="apple">りんご</div> が取得できる(class="contents"が取り除かれている) getAttributeメソッド 要素上の指定した属性の値を戻り値として返します。 要素.getAttribute('属性名') 例 HTML <div class="contents" id="apple">りんご</div> JavaScript const apple = document.getElementById("apple") apple.getAttribute("class") // => contents というclass名が取得できる まとめ 以上のメソッドを活用することで ・カーソルを持っていくと文字の色が変わる ・クリックするとプルダウンメニューが現れる  etc... などの動きをブラウザで操作できるようになる(関数やif文など、色々なものと組み合わせる必要はあるけど) 追記(20220201) インラインスタイルを操作するという目的の場合、setAttributeよりstyleプロパティを使用したほうが簡単に書けることをコメントでいただきました!ありがとうございます! //setAttribute sample.setAttribute("style", "color: red;") //styleプロパティ sample.style.color = 'red'; //処理は両者とも同じ   ※補足等ありましたらコメント頂けると幸いです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む