20211124のReactに関する記事は7件です。

React 18に備えるにはどうすればいいの? 5分で理解する

React 18はReactの次期メジャーバージョンで、2021年の6月にalpha版が、11月にbeta版が出ました。また、Next.js 12でもReact 18のサポートが実験的機能として追加されました。React 18の足音がだんだんと我々に近づき、アーリーアダプターではない皆さんの視界にもいよいよReact 18が入ってきたところです。 特に、React 18ではServer-Side Rendering (SSR) のストリーミングサポートが追加されます。現在ReactでSSRを行いたい人の強い味方としてNext.jsが存在しているわけですが、Next.js 12でもReact 18を通してストリーミングの恩恵を受けることができます(Next.jsではSSR Streamingと呼んでいるようです)。また、厳密にはReact 18とは別ですが、React Server ComponentsについてもNext.js 12により実験的なサポートが提供されています。 SSRがストリーミングをサポートするということにより、従来のSSRの良い点(とくに1RTTでFirst Contentful Paintに到達できる点)を維持しつつ、スケルトン表示などのこれまで専らクライアントサイドで用いられていたパフォーマンス最適化技術(特にLargest Contentful Paintの改善のための技術)も取り入れることができます。この辺りの話はより速い WEB を目指す Next.jsがとても詳しいのでぜひご覧ください。 この記事では、いよいよ無視できなくなってきたReact 18に備えるにはどうすればいいのかについて端的にまとめます。 一言で言えば、答えはとにかくSuspenseを理解しろです。Suspenseのコンセプトさえを理解すれば、ストリーミングSSRやReact Server Componentsはその応用として理解することができ、これらの機能を使いこなせる状態に大きく近づきます。 Suspenseを理解する 以下では、5分でSuspenseを理解します。ただし、5分しかないので具体的なAPIを一つ一つ解説することはせず、コンセプトだけ解説します。具体的な使い方を知りたい方は公式ドキュメントや他の記事をご覧ください。 とはいえ、理解すべきことはたった一つです。Suspenseでは(より正確に言えばReact 18のConcurrent Renderingという機能では)、コンポーネントそのものが「ローディング中なのでまだレンダリングできない」という状態になることがあります。 最も典型的なローディングの例として、APIからデータを取ってくるコンポーネントを想定してみましょう。従来の典型的な方法では、次のようなコンポーネントを書くことになります(React Queryを使用する例)。 従来の方法 const TodoList: React.VFC = () => { const { isLoading, error, data } = useQuery('todoData', loadTodoData); if (isLoading) { // ローディング中なのでスケルトンを表示 return <TodoListSkeleton />; } if (error) { // エラー処理 (TODO) return <p>gyaaaa</p>; } return <TodoListContents data={data} />; } このように、従来のやり方ではコンポーネント(TodoList)がデータの読み込みを担当し、ローディングかどうかという情報はそのコンポーネントが持つ状態でした。これにより、たとえローディング中であっても「TodoListをレンダリングできない」という事態は発生せず、ローディング中のUIを描画することはTodoListの責務でした。 それに対して、Suspenseを使う場合はTodoListの責務が簡略化されます。具体的には、ローディング中の処理(ついでにエラーの場合の処理)はTodoListの責務ではなくなります。 Suspenseを使う方法 const TodoList: React.VFC = () => { const { data } = useQuery('todoData', loadTodoData); return <TodoListContents data={data} />; }; では、この場合ローディング中はどうなるのでしょうか。答えは、TodoListがレンダリングできないと訴えてレンダリングを放棄します。 TodoListを使う側 const App: React.VFC = () => { return (<PageLayout> <TodoList /> {/* TodoListさん「レンダリング無理!!!やめる!!!!!」 Appさん「!?」 */} </PageLayout>); }; より具体的には、ReactランタイムはTodoListをレンダリングしようとしますが、TodoListはデータがまだローディング中だからレンダリングできないと言ってレンダリングを中断します(これをコンポーネントがサスペンドすると呼びます)。具体的な機序としては、useQueryが内部でPromiseをthrowすることで行います。 これにより、TodoListのレンダリングが無事に成功するのはすでに読み込みが完了した場合のみとなります。これがTodoListの責務を削減する秘訣です。 ローディングをハンドリングするSuspenseコンポーネント このように、「コンポーネントがレンダリングを投げ出す」というのは新しい(Suspenseより前には無かった)概念です。上の例では、TodoListがレンダリングを投げ出してしまったらそれを使う側のコンポーネント(App)もレンダリングができないことになってしまいます。何せレンダリング結果が無いのですから。 「レンダリングを投げ出す」と表現しましたが、実際にはこのTodoListは裏で非同期通信を準備し、それが終わってローディング状態でなくなったら自分でリトライをかけてくれる真面目なコンポーネントです。一般に、サスペンドするコンポーネントはリトライがセットになっています。今回は5分で理解できる内容にするためにリトライ周りは省いています。 そのため、「内部のコンポーネントがレンダリングを投げ出して(サスペンドして)しまった場合に対処する」という役目のコンポーネントがReactから提供されます。それこそがSuspenseです。これはfallback propを指定することで、内部がサスペンドした場合の代替表示を指定できます。 const App: React.VFC = () => { return (<PageLayout> <Suspense fallback={<TodoListSkeleton />}> <TodoList /> {/* TodoListさん「レンダリング無理!!!やめる!!!!!」 Suspenseさん「ええで」 */} </Suspense> </PageLayout>); }; これにより、TodoListがサスペンドしてしまった場合でもSuspenseがそれに対処してくれるため、Appはレンダリングに成功します。内部が失敗しても外側への影響を抑えるというのは、try-catch文に近いものがありますね。 エラー処理については別途Error Boundaryを用意することで対処します。 このように、非同期ローディングを行うコンポーネントにおいて従来「ローディング中の処理」と「ローディング完了時の処理」はまとまっていましたが、Suspenseの機構を使うとこれを分離できます。従来はif (isLoading)のような手続き的なプログラムだったところがSuspenseコンポーネントという宣言的な方法になったところも注目に値します。 Suspenseの面白いところは、複数コンポーネントをまとめて面倒見ることができるということです。次のようにすれば、ページの中の何かひとつでもサスペンドすればページ全体がスケルトンになります。ここでは3つのコンテンツがAppの中にありますが、それをひとつのSuspenseで囲んでいます。 const App: React.VFC = () => { return (<PageLayout> <Suspense fallback={<PageSkeleton />}> <MyProfile /> <TodoList /> <Comments /> </Suspense> </PageLayout>); }; 一方で、それぞれを別々のSuspenseで囲めば、それぞれが独立してスケルトン表示を持ち、ローディング完了したところから表示されるようになります。 const App: React.VFC = () => { return (<PageLayout> <Suspense fallback={<MyProfileSkeleton />}> <MyProfile /> </Suspense> <Suspense fallback={<TodoSkeleton />}> <TodoList /> </Suspense> <Suspense fallback={<CommentsSkeleton />}> <Comments /> </Suspense> </PageLayout>); }; このように、Suspenseの配置の仕方を変えることで、サスペンドの制御を細かに行うことができます。 SuspenseとReact 18の関係 残り時間が少ないのでReact 18の話に戻ります。 React 18のSSRストリーミングはSuspenseを前提にしています。SSRのストリーミングは「初期状態(ローディング中でスケルトンとかが表示されている状態)を表すHTMLを最速で送り、その後データが揃ったらスケルトンを置き換えるHTML断片を追加で送る」という方式です。 この処理単位はSuspense単位です。つまり、初期状態というのはSuspenseのfallbackが表示されている状態であり、その部分がローディング完了した場合はSuspenseの中身丸ごとを置き換えるHTML断片が送られてきます。 このように、Suspenseは「非同期的なレンダリングが行われるひとまとまりの領域」を定義するという意味があります(実際にはSuspenseをネストさせることもできるのでもう少し複雑ですが)。 要するに、ストリーミングSSRを活用するためには非同期処理をSuspenseを用いて書く必要がある上に、どのようにSSRのストリーミングが進むかを制御するにはSuspenseを適切な位置に置く必要があるということです。細かくSuspenseを置いて回れば、それだけSSRのストリーミングも細かく進むことになります。 また、React Server Componentsもその「レンダリングがサーバー側で(非同期に)行われる」という特徴から、(クライアント側から見ると)必然的にサスペンドの可能性があります。つまり、Server ComponentもSuspenseで囲む必要があるということです。 まとめ この記事ではReactのSuspenseの概要を説明し、React 18およびReact Server Componentsの機能を使いこなすためにはSuspenseの理解が必要であることを説明しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactのことをざっくりメモ

きっかけ Reactを書いていて、〇〇とは何か。 など根本的なことを理解していないのではと不安になったのでメモ程度に書いてみました。 ここ違います!などあればコメントお願い致します。 Reactの特徴 まず、javascriptのライブラリである。 仮想DOMを利用していること 仮想DOMとは、情報が書き変わってもすぐにブラウザ憑依するのでなく、一旦仮想のDOMにその変化を加えて、差分があった部分のみ変更するこの仕組みのことです。また、仮想DOMから実際のDOMに反映することで処理スピードが速く、比較的高速に切り替えが可能となっています。(ここではあくまでReactにおける仮想DOMについてです。) JSXを使用できる JSXとは、javascript XML。スクリプトとDOMを一緒に記述できるもの。(Vueでも使われています。)   コンポーネントベースのUI 他のライブラリでも採用されているものではあります。 コンポーネントはザックリといえば、構成要素のことで、UIを部品に見立てその部品ごとに作成することで管理と再利用性を高めることができます。大規模なシステムであれば恩恵が大きいと感じるかと思います。  Class component と Functional component この2つの使い分けですが、基本何か理由がなければFunctional componentを利用することをお勧めします。前まではstateを使用するにはclassのみしか選択肢がなかったですが今はどちらでも使えるので問題ありません。理由はいくつかあります。 まずReactのお偉いさん方がこちらを使用するように推奨しています。ですが、Class componentがなくなることはないと言っていますので、無理に変更する必要はありません。  Functionalの方が記述量が減りやすい classではconstructorやrender、ライフサイクルメソッド(componentDidMount)を書く必要がなくなります。3つ目のことが大きくこれは可読性にも関わってくると思ってます。classではライフサイクルメソッドがそれぞれあるので、中身の処理が同一でもまとめて書くことができません。ですが、FunctionalではuseEffectの中でまとめることができる優位性があるかと思います。 // class componentDidMount() { // マウント時処理 } componentDidUpdated() { // 更新時処理 } componentWillUnmount() { // アンマウント時処理 } // functional useEffect(() => { // マウント時処理 // 更新時処理 return () => { // アンマウント処理 }; }) カスタムフックを利用してstateとロジックを一緒に切り出すことができる 再利用も可能となり、管理が容易になり便利なツールだと思っています。ここではザックリ概要なので、詳しくは別途お調べください。 Functionalの方が動作が早いと言われている。 useCallback useMemo React.memo こちらの3つの機能を利用するとレンダリングの最適化が容易にできるかと思います。 useCallbackとuseMemoは似ていて、useCallbackは関数に対して利用できるものでuseMemoはそれ以外です。この2つを利用することで無用な関数の実行や変数の計算をスキップできます。これは処理が重い関数や変数に有効です。結果が同じであれば以前の値を使用してくれるので、動作の改善がみられるとはお思います。大規模なものでないと時間が湧かないかもしれませんが。また、React.memoと一緒に利用することで無用再レンダリングの抑止もできます。 処理の重くないものに関しては無理にuseCallbackuseMemoを利用することはしないで良いかと思います。 それぞれ単独で使いことはなく、組み合わせて使うことが多いかと思います。 最後に 閲覧いただきありがとうございました。 これからはもっと細かい部分や、なぜそれを使うのかなど疑問を潰していてければなと思いました。 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LintとTestをフル活用したNext.jsのテンプレ作成ガイド

1...2の...ポカン! 私は個人開発で小さなアプリを何度もこさえることがありますが、 その度に必要なパッケージをインストールしたり、設定ファイルを書くのは面倒です。 しかも、私の脳は1つ新しいことを学ぶと1つ前のものは忘れるという仕様なので、 その都度調べるということが起きてしまいます。 私の脳みそを改造できたらいいのですが、 そんなことできるはショッカーかダイジョーブ博士ぐらいです。 なので代替案としてテンプレを作っておいて、いつでもそれを使えるようにしておこう! という目論見で開かれたのがこの会です。 参加者は私一人です。 それではいきましょう。 Nextにパッケージ何入れようかサミット 厳重な審査の結果、今回の雛形に選ばれたのは以下のパッケージです。 後の章でこれらの説明や、導入方法について記述します。 また、これら以外にもいくつかの細かいパッケージも必要になってきますが、ここでの記載は省きます。 パッケージ名 説明 備考 Typescript みんな大好きTypescript ESLint js/tsのファイルの書き方を監督してくれる人 リント用 Prettier コードを綺麗にしてくれる人 整形用 StyleLint スタイルの書き方を監督してくれる人 リント用 CommitLint commit messageの書き方に口出ししてくる人 リント用 Sass cssをsass形式で描けるようにしてくれる人 MaterialUI コンポーネントの詰め合わせパックの人 DependBot パッケージのバージョンを新しくしてくれる人 Jest js/tsのテストをできるようにしてくれる人 テスト用 StoryBook 見た目のテストとかカタログ(jsDoc見た目版みたいな)を作る人 テスト用 惜しくも今回含まれなかった方々 ReduxやRecoil、ApploClientとかの「データをメモリ保存する勢」や firebaseとかaxiosとかの「非同期通信を簡単に実装します勢」も含めませんでした。 自分でサーバーを実装するか? GraphQLを使うのか? RESTだけでいくのか?などの要因によって何をいれたいかは変わってくるので、ここら辺は雛形にいれなくてもいいかなと思ったからです。 また、tailwindcssやstyled componentも含めませんでした。 個人的にCSS moduleが好きなので、そちらを採用したいという結論がサミットでは出たためです。 ディレクトリ構成について 今回はAtomicデザインを採用します。 この構成について賛否両論ありますが、詳しい議論について私はあまり知らないので脳死でAtomicにしました。 ディレクトリはこんな感じにしています。 src ├── pages │ ├── _app.tsx │ ├── api │ └── index.tsx ├── components │ └── atoms │ └── molecules │ └── organisms │ └── templates └── styles ├── Home.module.css └── globals.css 因みに、Nextはデフォルトでsrcディレクトリがないので、それを追加する必要があります。 (必要はない。私が追加したいだけ) この先読むのが面倒くさい方へ ここにコードがあるから、これをcloneして貰えばこのパッケージで開発を進めることができます。 StoryBookの導入だけ私が試すとvulnerabilitiesが出てしまって直せていないので、 StoryBook込みのものはwidth_storybookというブランチに取り分けてあります。 プロジェクトの作成 では実際にこれらを追加して行きましょう。 以下のコマンドでTypeScriptをデフォルトとするNext.jsのプロジェクトを作成します。 「すでに作成済みのプロジェクトにパッケージを追加したい」という人は飛ばして下さい。 npx create-next-app --typescript 実行したらプロジェクトの名前はどうするよ?って聞かれると思うので、 いい感じの名前をつけておきましょう。 srcディレクトリの作成 プロジェクトが作成できたら、次にディレクトリ構造を整理しておきましょう。 前述の通り、Next.jsはデフォルトでsrcディレクトリが存在しないので作ってあげましょう。 と言ってもsrcという名前のディレクトリを作って、その中にstylesとかpagesとか入れるだけですね。componentsというディレクトリも作っておくといいでしょう。 ディレクトリ構成についてはこれでOKです。 次にESLintの設定をして行きましょう。 ESLintの設定 Next.jsではver.11からデフォルトでESLintが備えられるようになった ので特にインストールする必要はありません。 なので、ESLintの導入の手順は以下のようになります。 ESLintのパッケージのインストール(インストール済だから不要) srcディレクトリ配下もチェックするように設定 行末にセミコロン;がないとエラーになるように設定 import文を絶対パスで記述するルールの追加 import文の記述順をアルファベット順にする ESLintの整形コマンドを登録しておく (おまけ)VScodeのESLint拡張機能を追加する srcディレクトリ配下もチェックするように設定 lintのコマンド実行時にデフォルトではsrcファイルディレクトリ配下は無視されます。 このままではsrcが可哀想なので、仲間に入れてあげましょう。 package.jsonのscriptsにある"lint"の部分を以下のようにするだけです。 参考:Basic Features: ESLint | Next.js package.json "scripts": { ・・・ // 「--dir src」を追加 "lint": "next lint --dir src" } 行末にセミコロン;がないとエラーになるように設定 行末にセミコロンがなくても動きますが、「必ず全部つける」で統一しておいた方が 綺麗なのでセミコロンが無ければ、ESLintに怒ってもらうように設定します。 [ESLintのルール](https://eslint.org/docs/rules/](https://eslint.org/docs/rules/) eslintrc.json { ・・・ // 追記 "rules": { // セミコロンない場合、エラー出力 "semi": ["error", "always"] } } import文を絶対パスで記述するルールの追加 import文を記述する時に相対パスじゃなくて絶対パスで書きたいですよね。 ESLintで絶対パスのimportに制限するルールを追加するには .eslintrc.jsonに記述するだけでです。 eslintrc.json "rules": { "no-restricted-imports": [ "error", { "patterns": ["./", "../"] } ] } ついでに、以下のように絶対パスを@/...と書けるように設定しておきましょう。 import styles from '../styles/Home.module.css'; // こうじゃなくて import styles from '@/styles/Home.module.css'; // こう tsconfig.jsonに以下のように記述してあげれば @/...をsrc/...と解釈してくれるようになります。 tsconfig.json "compilerOptions": { ・・・ "baseUrl": ".", "paths": { "@/*": ["./src/*"] } } importの順番をアルファベット順にする 些細なことですが、importの順番もアルファベット順だったら 気持ちいい気がするので、ESLintにお願いしておきましょう。 .eslintrc.jsonを以下のようにするだけです。 eslintrc.json { "extends": [ ・・・ // 追記 "plugin:import/recommended", "plugin:import/warnings" ], "rules": { ・・・ // import の順番をルール化 "import/order": [ "error", { "alphabetize": { "order": "asc" } } ] } } ESLintの整形コマンドを登録しておく ESLintに従ってコードを整形してくれるコマンドをpackage.jsonに追加しておきましょう。 commit時に自動でチェック・修正してくれる設定は後述。 その際にここで定義したコマンドを呼ぶようにするので、 ここで登録しておくのがいいかも。 package.json "scripts": { ・・・ // 以下を追加 "lint:fix": "eslint src --ext .js,jsx,.ts,.tsx --fix" } これでnpm run lint:fixを実行すれば、綺麗に整形されるようになっているはずです。 (おまけ)VScodeのESLint拡張機能を追加する VSCodeの場合は以下の拡張機能を入れるとリアルタイムでLintが適用され、 書き方が規則に則っていない場合には警告やエラーが表示されるようになります。 Prettierの設定 Prettierはコードを綺麗に整形してくれる人です。 同じくESLintも綺麗にする能力に長けていますが、 Prettier先生にしかできない整形があったりするらしいので、この先生も入れます。 Prettier導入の手順は以下の通り。 Prettierに必要なパッケージをインストール PrettierとESLintが共存できるような設定の追加 (おまけ) VScodeのPrettierの拡張機能を追加 (おまけ)Prettier拡張機能用の設定を記述 Prettierに必要なパッケージをインストール さて、Prettierをインストールして行きます。 因みにESLintとPrettierは基本仲良しですが、お互いのこだわりが異なる部分では 喧嘩するらしいので、喧嘩しないように気を遣ってくれるeslint-config-prettierも入れておきます。 npm install -D prettier eslint-config-prettier PrettierとESLintが共存できるような設定の追加 PerttierとESLintが喧嘩しないように、設定を追加して行きます。 .eslintrc.jsonのextendsの部分にprettierを加えてあげましょう。 eslintrc.json "extends": ["next/core-web-vitals", "prettier"] これだけです。 (おまけ) VScodeのPrettierの拡張機能を追加 VScodeを使っている人は、Prettierの拡張機能を導入しておくことで、 保存時に自動的に整形してくれるようになります。 (おまけ)Prettier拡張機能用の設定を記述 VScodeのPrettier拡張機能を入れたら、package.jsonにその設定を書きます。 Prettierにおける設定はVSCode上でも設定できます。 ですが、チーム開発する場合を考慮して、コードに残しておくのがいいと思います。 package.jsonが汚くなるのがいやという人は .prettierrcという設定ファイルを別途書いてもいいです。 package.json ・・・ "prettier": { "trailingComma": "all", "tabWidth": 2, "semi": true, "singleQuote": true, "jsxSingleQuote": true, "printWidth": 100 } 上記コードの各プロパティの説明 プロパティ名 説明 trailingComma 末尾のカンマあり tabWidth tabの長さを定義 semi セミコロンの有無 singleQuote シングルクォーテーションに統一するか jsxSingleQuote jsx もシングルクォーテーションに統一するか printWidth 1行の最大文字数 Prettierの設定はここまでです! お疲れ様です! Stylelintのインストール Stylelintとは、スタイル(css, scss, sass)の書き方をチェックし、整形してくれる人です。 Stylelintを導入するステップは以下の通りです。 Stylelintの必要パッケージをインストール Stylelintの設定ファイルを記述する Stylelintのチェックコマンド・修正コマンドを追加する (おまけ)VScodeの拡張機能を追加する Stylelintのインストール 以下の3つをインストールします。 パッケージ名 説明 Stylelint スタイルファイルの構文解析エンジン stylelint-config-standard Google's CSS Style Guide や Airbnb's Styleguide などを含むスタイルガイドラインのルール stylelint-order CSS プロパティをアルファベット順に並べるためのルール コマンドは以下の通り。 npm install -D stylelint stylelint-config-standard stylelint-order Stylelintの設定ファイルを記述する 次に.stylelintrc.jsonという名前でファイルを作成しましょう。 このファイルに以下の設定を記述します。 stylelintrc.json { "plugins": ["stylelint-order"], "extends": ["stylelint-config-standard"], "rules": { "at-rule-no-unknown": [ true, { "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"] } ], "string-quotes": "single", "order/properties-alphabetical-order": true }, "ignoreFiles": ["**/node_modules/**"] } plugin: 使用したいstylelint-orderのような外部パッケージを記述する場所 extends: 使用したいルールのパッケージを記述する場所 rules: lintのルールを記述する場所 at-rule-no-unknown: 使用を許可する「標準的ではない@-規則」を記述する場所 string-quotes: 統一したいクォーテーションの種類を記述する場所 order/properties-alphabetical-order: CSSプロパティをアルファベット順に並べる ignoreFiles: lintチェックの対象外とするファイルを記述する場所 Stylelintのチェックコマンド・修正コマンドを追加する StyleLintはESLinnt同様、コマンドを打たないとチェックや修正はしてくれないので、 あらかじめ、package.jsonにそのコマンドを登録しておきましょう。 commit時に自動でチェック・修正してくれる設定は後述。 その際にここで定義したコマンドを呼ぶようにするので、 ここで登録しておくのがいいかも。 以下のコマンドを追加しておきましょう。 これでnpm run lint:styleでチェックし、 npm run lint:style:fixで整形することができるようになります。 pakage.json "scripts": { ..., "lint:style": "stylelint '**/*.{css,scss,sass}'", "lint:style:fix": "stylelint --fix '**/*.{css,scss,sass}'" } (おまけ)VScodeの拡張機能を追加する Stylelintという拡張機能を追加します。 これでリアルタイムにStyelintに違反している箇所を炙り出すことができます。 これでStylelintの導入は完了です! お疲れ様です! git commit時に自動でESLintとStyleLintをチェックする これまで、ESLintやStyleLintを導入してコマンドを実行すれば、 チェックや整形ができるようにしてきました。 ここでは、git commitした時に自動でLintの整形・チェックを行なってくれるようにします。 その手順は以下の通りです。 lint-stageとhuskyをインストールする。 git commit時に実行したいコマンドを登録する。 huskyに2で登録したコマンドを呼び出すように設定 lint-stageとhuskyをインストールする ここでは、git commitされた時を検知し、指定したコマンドを実行してくれる 便利なパッケージ達であるhuskyとlint-stageをインストールしておきます。 パッケージ名 説明 lint-stage stageされたファイルだけをチェックする husky commitを検知して特定のコマンドを実行させる インストールコマンドは以下の通りです。 npm install -D husky lint-stage git commit時に実行したいコマンドを登録する git commitしたら 1. ESLint/StyleLintでコードを整形する 2. 整形した上で、各Lintに引っかかる構文がないか?をチェック の2つが実行されるようにして行きたいと思います。 そのために、package.jsonに以下のコマンドを追加しましょう。 package.json "scripts": { ..., "lint": "eslint src --ext .js,jsx,.ts,.tsx", "lint:fix": "eslint src --ext .js,jsx,.ts,.tsx --fix", "lint:style": "stylelint '**/*.{css,scss,sass}'", "lint:style:fix": "stylelint --fix '**/*.{css,scss,sass}'", // 以下を追加 "lint-staged": "lint-staged", } // 以下を追加 "lint-staged": { "*.{js,ts,jsx,tsx}": [ //(js,ts系のファイルに対して実行するコマンド) "npm run lint:fix", "npm run lint" ], "*.{css,scss}": [ //(スタイル系のファイルに対して実行するコマンド) "npm run lint:style:fix", "npm run lint:style" ] }, huskyに上の節で登録したコマンドを呼び出すように設定 続いて、git commitがコールされた時に実行して欲しいコマンドを登録しておきましょう。 まずは以下のコマンドを実行して下さい。 npx husky-init && npm install すると.husky/pre-commitというファイルができているはずです。 このファイルに先ほど作ったlint-stageというコマンドを実行するように記述しましょう。 husky/pre-commit. #!/bin/sh . "$(dirname "$0")/_/husky.sh" npm run lint-staged // ここを追記 これで、git commitを入力後すぐにリントの修正・チェックが走るようになりました。 お疲れ様です! commitlintを導入する commitlintはコミットメッセージをチェックしてくれる方です。 自分の過去のコミットをみると、コメントが適当すぎでコードを見ないと 何しているか不明なことが多々ある私には必要なパッケージです。 もう少しcommitlintについて知りたい人へ commitlintについてもう少し詳しく commitlintが許可するメッセージ例 commitlintを導入すると、git commit -m ...の度に、 「コミットメッセージ」がちゃんとテンプレに沿って書かれているかな?? と見てくれます。 commitlintはデフォルトで以下のようなコミットメッセージだけを通します。 title(scope): subject // 空行 body // 空行(省略可) footer(省略可) title, scope, subject, body, footer とは何か? param 説明 備考 title 新機能か?バグ修正か?という情報 決められた値しか受け付けない(後述) scope 影響範囲 影響を受けたファイルやディレクトリなど subject 何をしたのか?を一行でまとめた情報。要約的な。 body なぜ、どのように、という詳しい情報を記述する。 数行で書いてOK footer チケット番号とか。備考を書くみたい。 なくてもOK とりわけtitleは以下の値しか受け付けないので、注意しましょう。 param 説明 build ビルド関係 ci CI関係 chore 雑事、タイポ修正とか軽微なものなど。 docs ドキュメント更新/追加/修正 feat 新機能の追加 fix バグフィックス perf パフォーマンス refactor リファクタリング revert コミット取り消し(git revert) style コードスタイル test テスト その他のルール その他の詳細なルールなどはREADMEを見てもらうのが良さそうです。 自分でルールをカスタムできるみたいなので、お好きにどうぞ。 https://github.com/conventional-changelog/commitlint/#what-is-commitlint commitlintに関する雑な感想 結構几帳面に見てくれます。一人で開発していると、ついつい「えいや!」で 適当なメッセージを残してしまうことがあるのでその抑止力になっていい気がします。 また、デフォルトのルールはAnglarチームの規約に則っているようなので、 特にこだわりがなければデフォルトのまま使っちゃっていいのかなと思います。 https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits commitlint導入のステップは以下の通りです。 commitlintに必要なパッケージをインストール commitlintの基本設定を記述 commitlintを実行するコマンドを登録する commitlintをcommitメッセージ入力後に実行させるように設定 commitlintに必要なパッケージをインストール 以下のコマンドでcommitlintをインストールします。 npm install -D @commitlint/{cli,config-conventional} 「git commit時に自動でESLintとStyleLintをチェックする」を読み飛ばした方へ commitlint単体では、「commitされた時に発動する!」という能力が使えないので 二人の助っ人も導入しましょう。lint-stageさんとhuskyさんです。 パッケージ名 説明 lint-stage stageされたファイルだけをチェックする husky commitを検知して特定のコマンドを実行させる 以下のコマンドでインストールできます npm install -D husky lint-stage commitlintの基本設定を記述 commitlintの設定をpackage.jsonに記述します。 package.json { ..., "commitlint": { "extends": [ "@commitlint/config-conventional" ] } } なお、package.jsonに書きたくない!という人は .commitlintrc.jsonというファイルを別途作成し、そこに記述してもOKです。 commitlintを実行するコマンドを登録する commitlintを実行するコマンドをpackage.jsonに定義しておきましょう。 package.json "scripts": { ..., // 追記 "commitmsg": "commitlint -e $GIT_PARAMS" }, commitlintをcommitメッセージ入力後に実行させるように設定 ここまでcommitlintのインストールと設定を行なってきました。 しかし、現状git commitを実行してもcommitlintは発動しません。 git commitでメッセージを入力し終えた時に、 commitlintが実行されるように設定して行きましょう。 「git commit時に自動でESLintとStyleLintをチェックする」を読み飛ばした方へ 「git commit時に自動でESLintとStyleLintをチェックする」の章で 記載したコマンドを実行していないとこの先のコマンドを実行しても 上手く動作しないと思うので以下のコマンドを実行してから先に進んでください。 npx husky-init && npm install そのためには先ほどインストールしたhuskyを使います。 まずは以下のコマンドを実行してみましょう。 npx husky add .husky/commit-msg .husky/commit-msgというファイルができているはずです。 このファイルは「commitメッセージを記入後に実行されるコマンドを書くファイル」です。 このファイルにpackage.jsonに登録したコマンドを`記載するだけで commitメッセージ入力後にcommitlintが実行されるようになります。 /husky/commit-msg. #!/bin/sh . "$(dirname "$0")/_/husky.sh" npm run commitmsg // ここを追記 これで、commitメッセージ入力後にチェックしてくれるようになりました。 お疲れ様です! (番外編)VScodeの自動保存で整形するようにする。 ESLintやStyleLint、PrettierをVScodeで上手に活用するための VScodeの設定をここに記述します。 VScodeを利用している人はsetting.jsonに以下を記述しておくといいでしょう。 setting.json { // デフォルトのフォーマッタを prettier に設定 "editor.defaultFormatter": "esbenp.prettier-vscode", // ファイル保存時、prettier による自動フォーマット "editor.formatOnSave": true, // ファイル保存時、ESLint による自動フォーマット "editor.codeActionsOnSave": { "source.fixAll": true }, // css, scss ファイルは Stylelint でフォーマットするため、Pretteir によるフォーマットを回避 "[css]": { "editor.formatOnSave": false }, "[scss]": { "editor.formatOnSave": false }, // VS Code の CSS, SCSS の Lint オフ // Stylelint とのバッティング回避 "css.validate": false, "scss.validate": false } sassの導入 パッケージをインストールするだけです。楽ちん。 npm install -D sass dependabotの導入 dependabotはプロジェクトで利用している依存ライブラリで 新しいバージョンが出たら自動でPRを作ってくれるという優れもの。 長い間npm auditしてなかったけど、気づいたら脆弱性がたっぷり ということにならないためにも、自動で管理してくれるdependabotを導入しておくのは いい選択肢なのではないでしょうか。 また、作成されたPRを条件付きの自動マージすることもできるので、 その設定方法についても記して行きます。 導入もシンプルなので、早速やって行きましょう。 dependabotの導入手順 dependabotに関してはパッケージを入れるではなく、 GitHubで設定するという方法で導入することになります。 導入は至って簡単。何よりもこのGif動画が一番分かりやすい。 一応画像でも示しておく。 1. GitHubへ飛んで、画面上部にあるinisightsのタブへ。 Dependency graphを選択し、Dependabotをクリックする。 Enable Dependabotをクリック→Create config fileをクリック→適当にコミットメッセージを入れてCommit new fileをクリック。 (おまけ)dependabotで作成されたPRを自動マージしよう .github/workflows/のディレクトリ配下に以下のファイルを作成することで、 GitHub Actions で自動でdependabotにより作られたPRをマージしてくれます。 今回はパッチバージョンの更新だけ自動マージするように設定しましょう。 パッチバージョンとは ver. 1.12.09 ← バージョン番号の右端。 dependabot-auto-merge.yml name: Dependabot auto-merge on: pull_request # PRが上がったら実行するで permissions: pull-requests: write contents: write jobs: dependabot: runs-on: ubuntu-latest  # ubuntuで実行するよ if: ${{ github.actor == 'dependabot[bot]' }} # PRあげたのがdependabotの時じゃないと実行しない steps: - name: Dependabot metadata id: metadata # このstepを`metadata`って名前で参照できるようにしとこ uses: dependabot/fetch-metadata@v1.1.1 # Dependabotが作成したPRから依存関係の情報を取得! with: github-token: '${{ secrets.GITHUB_TOKEN }}' - name: Enable auto-merge for Dependabot PRs if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' }} # パッチversionの更新じゃないなら処理しない。 run: gh pr merge --auto --merge "$PR_URL" # mergeするよ env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 参考 - dependabot/fetch-metadata@v1.1.1とは - GitHub Actionsのワークフロー構文 MaterialUIの導入 新しくアプリを作成する度、モーダルやボタンを一から自分で実装するのは 面倒なのですでに作成されているコンポーネントを使いまわせたらいいのになぁ そんな欲望の末に生み出されたモンスターがMaterialUIです。(違う) 基本的なコンポーネントの集まりで、必要に応じて自分でスタイルを変更できるので、 「あ、こいつ 既存のコンポーネント使いまわしてるのバレバレw」ともなりません。 似たようなものにChakuraUIというものもあるみたいで、 こちらはコンポーネント数は少ないけどよりカスタム性に特化したライブラリのようです。 こちらも興味あるのですが、今回は私が使い慣れているMateriaUIを入れていこうと思います。 MaterialUIのインストール 以下のコマンドでインストールしましょう。 npm install @mui/material @emotion/react @emotion/styled @mui/icons-material MaterialUIのスタイルを上書きした時に、上書いたCSSを優先する 自分で新たに追加したCSSよりもMaterialUIの持つスタイル情報が優先されて、 思ったように表示されないなんてことがあります。 そのようなことを防ぐために、上書きしたCSSを優先してね!という設定を行う必要があります。 やり方は簡単です。 pagesディレクトリの下にある_app.tsxを以下のように書き換えましょう。 _app.tsx import '@/styles/globals.css'; import React from 'react'; import type { AppProps } from 'next/app'; import { StyledEngineProvider } from '@mui/material/styles'; // 追加 function MyApp({ Component, pageProps }: AppProps) { return ( <StyledEngineProvider injectFirst> // 追加 <Component {...pageProps} /> </StyledEngineProvider> // 追加 ); } export default MyApp; <Component {...pageProps} /> を<StyledEngineProvider>というタグで囲っているだけですね。 これをするだけで、全てのコンポーネントで カスタムCSSを優先するというルールが適用されることになります。 (おまけ)themeの設定 MaterialUIにあるコンポーネントはデフォルトで 色や文字サイズが適当な値に設定されています。 例えば、デフォルトでButtonコンポーネントはこんな色です。 前述した通り、スタイルは自分で上書きできるのですが、 全てのコンポーネントで使用しているデフォルトの色を変更してあげれば、 わざわざ上書き用のCSSを書く必要が無くなるので嬉しい訳です。 そのような時に使えるのがthemeという訳です。 themeはデフォルトの値が詰まっている箱みたいなもので、 この箱の値を変更してあげることで、全てのコンポーネントでそれが適用されます。 それでは実際にデフォルト値を変更してみます。 まずは好きな場所にtheme.tsというファイルを作りましょう。 私はstylesディレクトリ直下に作ることにします。 theme.ts import { createTheme } from '@mui/material/styles'; // Create a theme instance. const theme = createTheme({ palette: { primary: { // ベースカラーを真っ黒にしちゃおう。 main: '#000000' } } }); export default theme; 次にこの定義したthemeが全てのコンポーネントで適用されるようにしておきましょう。 _app.tsxに以下を追加して下さい。 _app.tsx import '@/styles/globals.css'; import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; // ThemeProvider を追加 import type { AppProps } from 'next/app'; import React from 'react'; import theme from '@/styles/theme'; // 追加 function MyApp({ Component, pageProps }: AppProps) { return ( <StyledEngineProvider injectFirst> <ThemeProvider theme={theme}> //追加 <Component {...pageProps} /> </ThemeProvider> // 追加 </StyledEngineProvider> ); } これだけです。 すると先ほどのボタンは真っ黒になっていると思います。 他にもテキストの大きさなども変えることができるので、必要ろあらばいじってみて下さい。 Jestの導入 言わずと知れたテストライブラリです。 私の個人開発だとテストは後回しになりがちですが、 いつか書きたいとい気持ちも込めて入れておきます。 結局やらないのであれば消すだけでOKなので...。 ここで紹介する導入ステップは以下の通りです。 Jestのインストール Jestの設定 仮テストの作成 Jestのインストール 以下のコマンドを実行しましょう npm install -D jest @testing-library/react @types/jest @testing-library/jest-dom @testing-library/dom babel-jest @testing-library/user-event jest-css-modules 色々インストールしていますが、ざっくり言うと 「Jestで必要になるパッケージ諸々」+「テストでCSSファイルが悪さしないようにモック化するパッケージ」 をインストールしています。 Jestの設定 .babelrcを作成する .babelrcという名前でファイルを作成し、以下を記入して下さい。 これは、Jestに対して、「Next.jsのプロジェクトにテストするよ」と伝えるために必要みたいです。 { "presets": ["next/babel"] } package.jsonに設定を追加する package.jsonに以下の設定を追加しておきましょう。 コメントの部分は削除して下さい。 package.json "jest": { "testPathIgnorePatterns": [ // テストの対象外となるディレクトリを記載 "<rootDir>/.next/", "<rootDir>/node_modules/" ], "moduleNameMapper": { "\\.(scss)|(css)$": "<rootDir>/node_modules/jest-css-modules", // styleファイルに関しては`jest-css-modles`を使ってモックしますよ! "^@/(.*)$": "<rootDir>/src/$1" // import文が@で始まっていたら、それはsrcを示しているんだよ! } } package.jsonにテスト実行用のコマンドを追加 package.jsonのscriptに以下のコマンドを追加しておきましょう。 package.json "scripts": { ..., "test": "jest --env=jsdom --verbose" } オプションは「テストファイルに対してテストが通ったかどうか?」ではなく、 「テストファイル内の全てのテストケースの結果を表示する」ために必要なものになります。 仮テストの作成 設定がうまくいっているかを確認するために、仮の簡単なテストを書いておきましょう。 まずは___tests___というディレクトリをルートに作成しましょう。 その中に仮のテストファイルHome.test.tsxというファイルを作成し、以下をコピペして下さい。 Home.test.tsx import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import Home from '@/pages/index'; it('Should render hello text ', () => { render(<Home />); expect(screen.getByText('Next.js Template')).toBeInTheDocument(); }); Jestの話もしだすと長くなってしまうので、ざっくり説明すると、 <Home>コンポーネントの中にNext.js Templateという文字列が あるかどうかを確認するよ!というテストコードになります。 新しく作ったNext.jsのプロジェクトにはindex.tsxという名前のファイルが あると思いますが、その中にHomeコンポーネントが定義されているはずです。 その中にNext.js Templateという文字列を含めておきましょう。 私は以下みたいな感じにしておきます。 index.tsx ... <main className={styles.main}> <h1 className={styles.title}> Next.js Template /* ←ここに追加しました */ </h1> <p className={styles.description}> Get started by editing <code className={styles.code}>pages/index.tsx</code> </p> ... これでnpm run testを実行してみて下さい。テスト通るはずです。 逆にNext.js Templateの文字を削除してテストすると エラーが表示されると思います。 これでJestの導入は完了です! お疲れ様でした! StoryBookの導入 StoryBook。それは、デザイナーとの意思疎通を容易にするべく 作成したコンポーネントのデザインを確認できるカタログを自動で生成してくれる人。 「個人開発で一人でやっているから、オイラは関係ないや。グヘヘェ」 と私は思いました。 ですがこいつは見た目のテストをするためにも役立つ優れものだとか。 規模が大きくなり、1つのコンポーネントを複数の場所で使っていると いつの間にかスタイルおかしくなっている!なんてことがあります。 それを防ぐことができるというのは嬉しいもの。 導入の手順は以下の通りです。 StoryBookのインストール StoryBookの設定変更 カタログを作ってみる Chromaticのインストール GitHub Actions の設定 StoryBook のインストール とっても簡単です。以下のコマンドを実行するだけ! npx sb init --builder webpack5 こちらを実行すると以下が行われます。 パッケージのインストール コマンドのエイリアスをpackage.jsonに登録 .storybookディレクトリと設定ファイルの作成 storiesディレクトリとサンプルファイルの作成 StoryBookの設定変更 設定を少しだけいじります。 .storybookというディレクトリの中に.main.jsというファイルがあるはずです。 こちらに以下を追記してあげてください。 main.js const path = require('path'); // 追加 module.exports = { stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: ['@storybook/addon-links', '@storybook/addon-essentials'], core: { builder: 'webpack5', }, // ここから webpackFinal: async (config) => { config.resolve.alias = { ...config.resolve.alias, '@': path.resolve(__dirname, '../src'), }; return config; } // ここまで追加 }; これは、import文が@/...というように@から始まっている場合でも パスを認識できるようにするための設定です。 カタログを作ってみる カタログを作ってみましょう。 既存のindex.tsxで定義されているHomeコンポーネントをカタログに登録してみましょう。 まずはstoriesディレクトリの中にhome.stories.tsxというファイルを作成し、 以下をコピペしてあげてください。 home.stories.tsx import Home from '@/pages/index'; const home = { title: 'Pages/Home', component: Home, }; export const HomePage = () => <Home />; export default home; 続いてカタログをローカルサーバーで表示させるために以下のコマンドを実行します。 npm run storybook そしてlocalhost:6006を見てみると、 画像のようにHomeコンポーネントがカタログに表示されいるのが確認できるでしょう。 デフォルトでは、サンプルのファイルが存在しているので、画像とは違ってHomeコンポーネント以外も表示されていると思います。この画像ではそれを消した状態のものです。 ここまででひとまずStoryBookのカタログ機能に入門することができました。 やったね☆ Chromaticのインストール この説ではChromaticと呼ばれるものをインストールします。 これはStoryBookと連携して、コンポーネントの見た目の変化を検知し、 比較検証を簡単にできるようにするツールです。 まずはChromatic公式へ飛んでGet Started nowをクリック! そのまま案内に従ってポチポチしていくと以下のような画面に行くと思うので、 自分のリポジトリを選択してさらに進みます。 少し進むと画面に表示されている2つのコマンドを実行してくれよな! と言われると思います。以下の2つのコマンドです。 npm install --save-dev chromatic npx chromatic --project-token=プロジェクトごとの固有の値 素直に実行してみましょう。しばらく待つと画面遷移し、以下のような画面になると思います。 すると今度は、「スタイルの違い検知してみせるから何かstyleいじってみろよ!」 と言われます。いじってやりましょう。なんでもいいのですが、 Home.module.cssをいじってみることにします。 Home.module.css .main { align-items: center; display: flex; flex: 1; flex-direction: column; justify-content: center; min-height: 100vh; padding: 8rem 0; /* 変化させてみる 4rem -> 8rem */ } そして再度、以下のコマンドを実行します。 npx chromatic --project-token=プロジェクトごとの固有の値 すると、Chromaticが変更を検知してみやすく表示してくれます。 以下のような感じです。素敵です。 GitHub Actions の設定 これまでStoryBook&Chromaticのカッコ良いところを見てきました。 でも私の理想的なフローは以下なので、もう少し設定を頑張ります。 GitHubでPR出した時に自動でチェック 意図していない時に変更が検知されたら、それをチェックする。 問題なければapproveしてmerge ! そのためにはGitHub Actionsでワークフローを作成する必要があります。 以下を.github/workflows/の直下に作成しましょう! chromatic-ui-test.yml name: Chromatic on: push: # pushされたら実行するよ branches: # ブランチ名が以下じゃなければ実行しないよ - with_storybook # ここは適宜変更して下さい jobs: build: runs-on: ubuntu-latest # ubuntu環境で実行します steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - run: npm ci - uses: chromaui/action@v1 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} このファイルで定義されているCHROMATIC_PROJECT_TOKENという変数の値は、 GitHubに自分で定義する必要があります。 簡単にできるので、以下の手順で進めていきましょう! まずはGitHubのSettingタブへ移動して下さい。 続いて、画面左側にあるSecretsをクリック! さらにさらに、New repository secretをクリック。 NameにCHROMATIC_PROJECT_TOKENと入力し、 valueに自分のトークンを入力してください。 これで準備は完了です! 先ほど作ったchromatic-ui-test.ymlで定義したブランチ名でpushすれば、 ChromaticUIのチェックが実行されていることが確認できるはずです。 お疲れ様です!これでUIテストを行う環境もできました! 最後に 長かったですね。お疲れ様です。 この記事の通りに実行して行ってもnodeやnpm,yarnの バージョンの違いやOSの違いなどでエラーが出てしまって「まぢムリ」 となってしまうこともあるかもしれません。 私はよくそういうことあります。 エラーが出て、先人の奮闘記からコードをコピペしたけど やっぱり直らん。ということが。 いつになったら英語の公式ドキュメントに臆さない キラキラスーパーエンジニアになれるのかは不明です。 今は個人開発などで色々奮闘しているので、 よかったら他の記事も見たりTwitterをフォローしてみましょう。 私のTwitter
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メッセージを送信後、画面最下部まで自動でスクロールする(React)

何をやるのか LINEのように、チャット画面で新規メッセージを送信後、画面最下部まで自動でスクロールする機能の実装方法について解説します。 Reactで作ったSPAが前提の方法です。 1. チャット画面を実装 まずはサンプルとなるチャット画面を実装。 ChatScreen.jsx import React, { useState } from 'react'; import './ChatScreen.css'; // ダミーデータを生成 const dummyMessages = Array(30) .fill({}) .map((_, index) => ({ id: index, content: `ダミーメッセージ #${index + 1}`, })); export const ChatScreen = () => { const [messages, setMessages] = useState(dummyMessages); const [inputValue, setInputValue] = useState(''); const onSendMessage = () => { setMessages(messages.concat({ id: messages.length + 1, content: inputValue })); setInputValue(''); }; return ( <div className='container'> <ul className='messages-container'> {messages.map((message, index) => ( <li key={message.id} className='message'> <p>{message.content}</p> </li> ))} </ul> <div className='input-container'> <input type='text' className='input' value={inputValue} onChange={({ target }) => setInputValue(target.value)} /> <button className='button' onClick={onSendMessage}> 送信 </button> </div> </div> ); }; ChatScreen.css .container { overflow-y: overlay; display: inline-block; } .messages-container { display: flex; flex-direction: column; padding: 50px; margin-bottom: 100px; } .message { padding: 5px 20px; background-color: #dcf8c6; display: block; border-radius: 10px; margin-top: 10px; margin-bottom: 10px; clear: both; } .input-container { display: flex; flex-direction: column; justify-content: space-around; height: 100px; padding: 5px; width: 100%; position: fixed; bottom: 30px; } .input { width: 60%; margin: auto; border: 1px silver solid; font-size: 15px; line-height: 40px; } .button { width: 100px; margin: auto; } 仕上がりはこんな感じです。 2. Reactのrefを使って末尾のメッセージを管理 (1) useRefでrefを定義 ChatScreen.tsx const footRef = useRef(null) React HookのuseRefでfootRefオブジェクトを定義。 デフォルト値はnull。 (2) 末尾のメッセージへrefを付与 ChatScreen.jsx {messages.map((message, index) => ( <li key={message.id} className='message'> <p>{message.content}</p> {/* 以下の一行を追加 */} {index === messages.length - 1 && <div ref={footRef}></div>} </li> ))} messages配列の末尾の要素、つまり最下部に表示されるメッセージへfootRefを付与。 3. useLayoutEffectで自動スクロール ChatScreen.jsx useLayoutEffect(() => { footRef.current.scrollIntoView(); }, [messages]); useLayoutEffect関数を記述し、第二引数の配列に渡された値(messages)の変更があるたびに, "footRef.current.scrollIntoView()"で最下部の要素を画面上に表示。 最終的なコードは以下。 ChatScreen.jsx import React, { useLayoutEffect, useRef, useState } from 'react'; import './ChatScreen.css'; // ダミーデータ const dummyMessages = Array(30) .fill({}) .map((_, index) => ({ id: index, content: `ダミーメッセージ #${index + 1}`, })); export const ChatScreen = () => { const [messages, setMessages] = useState(messagesData); const [inputValue, setInputValue] = useState(''); const footRef = useRef(null); const onSendMessage = () => { setMessages(messages.concat({ id: messages.length + 1, content: inputValue })); setInputValue(''); }; useLayoutEffect(() => { footRef.current.scrollIntoView(); }, [messages]); return ( <div className='container'> <ul className='messages-container'> {messages.map((message, index) => ( <li key={message.id} className='message'> <p>{message.content}</p> {/* 以下の一行を追加 */} {index === messages.length - 1 && <div ref={footRef}></div>} </li> ))} </ul> <div className='input-container'> <input type='text' className='input' value={inputValue} onChange={({ target }) => setInputValue(target.value)} /> <button className='button' onClick={onSendMessage}> 送信 </button> </div> </div> ); }; こんな感じで自動スクロールが実現できました。 今回はチャット画面を題材として扱いましたが、こちらのテクニックは割といろんな場面で応用できるのではないかなと思います。 こちらあくまで一方法となりますのでご参考までに。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

expoでソケット通信する記事が日本語で1記事もない件

記事を書こうと思った理由 実務で、ネイティブアプリを作ることになりました。前のプロジェクトではVue.jsを使っていたため、スマホアプリでもReactNativeを選択しました。 今回は、httpプロトコルが使えないので、まずは、ソケット通信する必要があります。いつものようにやり方をグーグル先生に聞くと、日本語の記事が一つもない!? 私と同じように何日も沼にはまらないようにこの記事を書こうと思った次第です。 前提 ReactNative初学者の人は、まず間違いなくexpoを使って開発すると思います。入門書やUdemyもほぼexpo前提で構成されています。 expoはコマンド一発で、各デバイス用のビルドができるし、ストアへのリリースもとても簡単であることが理由だと思います。 しかしこいつは、ネイティブのモジュールが使えません。便利ゆえの欠点ですね。社内にスマホエンジニアがいないので、ビルド周りでエラーが出たら、なおせる気がしなかった私は、どうしてもexpoを使いたかったわけです。 解決方法 react-native-tcp-socketというものを使えば、どうやらソケット通信できます。しかしこいつはネイティブのモジュールであるがゆえにexpoでは使用できません。 そこで使用するのはeas(Expo Application Services)。これを使えば、ネイティブのモジュールをexpoで使えます。環境構築は、公式ドキュメントを参考にしてください。↓ npmのドキュメントに書いてある通りに関数を作り、IPとportを入れて頂ければ、ソケット通信完了です! sample const onclick = () => { // Create socket try { const client = TcpSocket.createConnection( { port: port, host: IP } , () => { // Write on the socket client.write('Hello server!'); alert("成功") // Close socket client.destroy(); }); } catch (e) { alert("失敗", e) } } ///////////////////////// tcp/ip react-native-tcp-socket expo eas
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Harmoware-VISの紹介

Harmoware-VIS とは Harmoware-VIS は JST OPERA ( 産学共創プラットフォーム共同研究推進プログラム ) の支援を受けて、人間機械協奏技術コンソーシアム ( HMHS: Human Machine Harmonization System ) (http://hmhs.jp) において、主に名古屋大学河口研究室が中心になって開発したものです。 UBER が開発した deck.GL ( https://github.com/visgl/deck.gl ) を利用し、その上に移動体とその付加情報を可視化する機能を 追加しています。 Harmoware-VIS の仕組み UBER の deck.gl は、マップ上で位置を緯度経度で指定し、様々な3Dや2Dオブジェクトを配置することが可能です。 しかし、配置したオブジェクトをマップ上で移動させる機能はありません。 そこで Harmoware-VIS ではオブジェクトの位置を変化させたデータを、次々に deck.gl で表示させることで、オブジェクトが移動するアニメーションを実現しています。 移動データの構造 例として下図のような2つの移動体を想定します。 ひとつの移動体データは「始点」「終点」「移動経路」で構成されます。 移動体データは複数定義すること可能です。 2点間の位置は、最短距離での移動を想定し、始点からの経過時間を元に計算します。 移動データファイル 下に2つの移動体の定義例を示します。 移動データ(json形式)の定義例. [ {"operation":[ // 1つ目の移動体のデータ {"position":[136.945255, 35.190691, 0], // 経度、緯度、高度を指定(高度省略時はゼロ) "elapsedtime":1551575400}, // 上記地点の通過時間をUNIX時間で指定 {"position":[136.936864, 35.191591, 0],"elapsedtime":1551575460}, {"position":[136.945255, 35.190691, 0],"elapsedtime":1551575520}]}, {"operation":[ // 2つ目の移動体のデータ {"position":[136.900947, 35.143257, 0],"elapsedtime":1551577140}, {"position":[136.901769, 35.134622, 0],"elapsedtime":1551577260}, {"position":[136.906622, 35.127823, 0],"elapsedtime":1551577380}]} ] 移動データはjson形式で定義します。 operation に始点から順に終点までの移動経路を位置と通過時間で定義します。 Harmoware-VIS を使ったアプリ作成について Harmoware-VIS は以下で公開しています。 https://github.com/Harmoware/Harmoware-VIS 上記のgithubレジストリ内のexamplesアプリも参考にしてください。 https://github.com/Harmoware/Harmoware-VIS/tree/develop/1.7x/examples/visualize-sample その他、Harmoware-VIS を使った、簡単なサンプルを公開しています。 https://github.com/Harmoware/Harmoware-VIS-Demo https://github.com/Harmoware/Harmoware-VIS-xband https://github.com/Harmoware/Harmoware-VIS-SUMO-FCD https://github.com/Harmoware/Harmoware-VIS-Tile3DLayer https://github.com/Harmoware/Harmoware-VIS-Geojson Harmoware-VIS は react、redux 及び react-redux を使用しています。 画面更新のトリガーは基本的にルートからの props に含まれる action に定義済みの関数で行います。 アプリのカスタマイズで action に関数を追加することも可能です。 以下に Harmoware-VIS のリファレンスドキュメントを公開しています。 https://harmoware-vis.gitbook.io/harmoware-vis-documents/ その他の用例などは、Qiita内に記事を随時追加していきます。 よろしくお願いします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

bladeからreactにデータを渡す

概要 laravelからReactにpropsでデータを渡したいが、vueほど簡単にいかなかったのでメモ。 もっといい簡単な方法がありそう。 (この記事を執筆時点で私はreact歴1週間しかないです。vueは一年くらいは。) 手順 '/' を生成するルートを作る。 ドメインはこれで取得できるがプロトコル部分がまだない。 web.php Route::get('/',[\App\Http\Controllers\Web\TopController::class, 'index'])->name('web.top'); http: か https: を判別して頭につける。 blade内に適当にidつけた任意のタグに任意のプロパティでURLを記述する。 Reffererからとれるかと思ったけど初回こけるのでだめだった。 getElementByIdとgetAttributeでbladeから上記の値をとってくる。 とれた値をReact.renderのprops部分にあてこむ。 main.tsx import React from "react"; import {render} from "react-dom"; import {Accordion} from "../components/accordion"; const baseUrl = (document.getElementById('base_url')?.getAttribute('baseUrl')) console.log(baseUrl); // http://localhost:3000 render(<Accordion baseUrl={baseUrl}></Accordion>,document.getElementById("member_accordion")); これでreactコンポーネント内部にベースURLが送れたので、他のデータはコンポーネント内でベースURLにサブディレクトリを足してajaxでとってくるか、同様に流し込む。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む