20211010のReactに関する記事は6件です。

SSR経験者ならReact Server ComponentsでSPA開発がめちゃくちゃ捗る

はじめに 今からReactを始めるなら、React Server Components がおすすめです。 パラダイムチェンジが起き、ここ5年ほどでSSR(Server Side Rendering:PHPやRuby on Railsなど)からSPA(Single Page Application:ReactやVueなど)に変わっていきました。 今後はさらに、SPAとSSRの良いとこ取りに移行しようとしています。 自己紹介 5年前(React v0.1の時代)に、下の記事を書いたものです。VTeacherというサービスを開発するエンジニアをしています。社長もやっています。昔はCyberAgentやGREEにいました。 出来る限り短く説明するReactJS入門 https://qiita.com/rgbkids/items/8ec309d1bf5e203d2b19 開発しているサービス(VTeacher) VTeacherは↓の動画のように、ライブ配信中に簡単にQRコードを挿入できるサービスです。例えば、QRコードから自分のショップに誘導できたり、PAY系サービスのQRコードを表示できるので、ライブ配信のマネタイズを容易にします。 https://www.youtube.com/watch?v=eRAD3haXXzc 今までやってきたこと 分野 内容 内容 Front Mobile Objective-C, Swift, Java, Kotlin, Android NDK(C++), RxSwift, RxJava, VIPER, Realm Web HTML, CSS, JavaScript, Node.js, npm, yarn, Babel, Webpacker, JQuery VR/AR/MR Unity(C#), C, C++, Java, Three.js, WebGL, Oculus(Rift/GearVR/Go/Quest), THETA(S/V) Back PHP PHP(Ethna/Codeigniter/Zend Framework), Linux, Apache, Nginx, MySQL, Postgresql, WordPress, WebAPI(REST), PHPUnit, Selenium, Codeception JVM Java(JSP/EJB/Struts/Velocity/Seasar2), Scala, JavaScript, Oracle, MySQL, Solaris, Linux(RedHat/CentOS), JRun, HitachiCosmiNexus, Web Logic, Tomcat, Play, Apache, Nginx, WebAPI(REST), JUnit, Jmeter Infrastructure AWS(EC2, Lambda, Batch, ECR, ECS, EKS, S3, RDS, VPC, CloudFront, Route 53, API Gateway, Cloud9, MediaLive, MediaPackage, MediaStore, Elastic Transcoder, IAM, Certificate Manager, Key Management Service, Simple Email Service) DevOps Docker, Jenkins, CircleCI, GitHub Actions 他 Shell, Ruby, Perl, VB, VBA, VB.NET, ASP 今回はこの投稿の続編となります。 前回と同様、「少し早めのキャッチアップ」がコンセプトです。 Reactのバージョン 2020年12月にFacebookからReact Server Componentsのデモが公開されました。 現在のReactのバージョンは18ですが、React Server Componentsの正式導入は19以降と予想されています。これまでにReact Server Componentsのための布石と言えるような実験的な機能がリリースされてきました。業界人の予想通り、全てがReact Server Componentsのためであれば、今までの常識も変わるでしょうから、先入観がないほうが受け入れやすいと思います。 Reactチームが出したデモコードを解析しながら、チームで便利なちょっとしたWebアプリを作ってみたらいかがでしょうか? DBはPostgreSQLを使います。目指すところは React Server Components + Relay + GraphQL になりますが、Relay+GraphQLはReact Server Componentsのデモでは利用できません。 注1. TypeScriptの導入欲求はいったん抑えておきましょう。 React Server Componentsは複雑に入り組んでいるため、tsファイル化に時間を取ることはあまりおすすめできません。代わりに、VS Codeを使用した //@ts-check コメントを検討しましょう。ファイルの先頭に記述すると、普通のJavaScriptであっても型チェックをしてくれます。 注2. Next.jsの採用欲求はいったん抑えておきましょう。 Next.js用のReact Server Componentsのデモが別に出ています。本家(Facebook)がNext.jsの得意領域に進出したとも見え、共存方法を模索中であるような段階です。 デモのインストール デモのインストール方法はREADMEを参照してください。Dockerの利用が手早いと思います。 React Server Components デモのコード(GitHub) https://github.com/reactjs/server-components-demo Dockerのインストール ※ちなみにMac版のDockerはdocker-composeを含みます。 https://www.docker.com/ またはこちらの記事を参考に。 参考:React Server Components (demo) を Amazon Linux 2 で試す (Docker編) https://zenn.dev/rgbkids/articles/8025b3297e07d4 参考:React Server Components (demo) を Amazon Linux 2 で試す (PostgreSQL編) https://zenn.dev/rgbkids/articles/fa1d3eb1e9b065 localhostで確認ができたら、次に進みましょう。 http://localhost:4000/ このデモをスケルトンとして、自作のコンポーネントを追加していきます。 必要なファイル以外を削除 src以下は下記のものを残して、残りのファイルは削除で大丈夫です。 App.server.js Root.client.js Cache.client.js db.server.js LocationContext.client.js index.client.js 予習・復習 Reactの書き方。初めての方、久しぶりの方に向けて。基本構文はこちらです。 export default function Hoge() { return ( <div> This is Hoge. </div> ); } Hogeというファイル名で、これを定義することにより、 <Hoge /> のようにタグ表記できるようになります。<Hoge /> の中身は、returnに記述されているHTMLで、Webブラウザから見ると、それが表示されます。このあたりの技術をJSXと言い、Facebookが開発しています。returnには他のコンポーネントも記述できます。 React Sever Components の種類 React Sever Components は通称です。利用にあたり3種類のファイルを使用します。 サーバーコンポーネント ファイル名の命名規則は .server.js サーバー側でレンダリング 他資源へのアクセス(react-fetchからREST API等、react-pgからDB参照、将来的にはRelay+GraphQL等) クライアントコンポーネント ファイル名の命名規則は .client.js クライアント側でレンダリング 他資源へのアクセス(react-fetchからREST API等) 通常のReactコンポーネントと同じく、stateが使えます。 共通コンポーネント ファイル名の命名規則は .js サーバー側でもクライアント側でも利用可能なコンポーネントです。共通処理。 ネーミング(命名規約)の注意点 ToDOというコンポーネントを考えた時、つい、下記のようなファイル構成にしてしまいました。 ToDo.server.js ToDo.client.js ToDo.js しかし、これはimportの際、defaultの名前が重複してしまうので(この場合はToDo。import時に名前を設定できるけど)、おすすめしません。Facebookのデモもこのような構成になっていません。 コンポーネントをきちんと設計して、コンポーネント単位で分けましょう。 サーバーコンポーネントしか許されていない処理をクライアントコンポーネントで実行すると、エラーになります。 例:クライアントコンポーネントでdb(react-pg)を利用する場合、実行時に TypeError: Cannot read property 'db' of undefined になってしまう。 import {db} from './db.server' (略) const notes = db.query( `select * from notes where title ilike $1`,['%%'] ).rows; 最初はサーバーコンポーネントだけを用意したほうがやりやすいです。 その中でクライアントコンポーネントにできるもの・する必要があるものは変更します。 ここから始まるApp.server.js React Server Componentsは、ここからはじまります。このファイルにサーバーコンポーネントを記述します。 現時点では、とりあえず、このようにしておきましょう。 App.server.jsの修正 export default function App({selectedId, isEditing, searchText}) { return ( <div> </div> ); } 最初にサーバーコンポーネントを作成 自作のコンポーネントを追加していきましょう。 まずはサーバーコンポーネントを用意しましょう。先ほど説明した通り、最初はサーバーコンポーネントだけを用意して、その後にクライアントコンポーネントにできるもの・する必要があるものを探していきましょう。 srcディレクトリ直下に Hoge.server.js を作成し、下記のコードをコピーしてください(サーバーコンポーネントなので、規則に沿って server.js になります)。 src/Hoge.server.js(新規作成します) export default function Hoge() { return ( <div> This is Hoge.server.js! </div> ); } このHoge(Hoge.server.js)をApp.server.jsに記述します。 src/App.server.js(既に存在するので変更して保存) import Hoge from './Hoge.server'; export default function App({selectedId, isEditing, searchText}) { return ( <div className="main"> <Hoge /> </div> ); } サーバーコンポーネントはサーバー側でレンダリングされます。現時点では通常のSSR(PHPやRuby on Rails)と変わりません(のちほどクライアントコンポーネントを作成します)。 他資源へのアクセス サーバーコンポーネントは、db(react-pg)にアクセスできます(ただしdbへの直接アクセスはアプリ設計上は推奨されていません)。 REST APIの利用にあたってはfetch(react-fetch)が利用できます。fetchはクライアントコンポーネントからも利用できますが、重い処理となりそうなところはサーバーコンポーネントで処理することでクライアントに返すデータ量を削減できます(React Server Componentsの目標であるバンドルサイズゼロ)。 Hoge.server.jsを下記のように変更してみましょう。 Webブラウザで確認するとdb・fetchで取得した値が表示されると思います。 src/Hoge.server.js(変更してみましょう) import {db} from './db.server'; // db(react-pg) import {fetch} from 'react-fetch'; // fetch(react-fetch) export default function Hoge() { // db const notes = db.query( `select id from notes` ).rows; // fetch const note = fetch(`http://localhost:4000/notes/1`).json(); let {id, title, body, updated_at} = note; return ( <div> <p>db:</p> <ul> {notes.map((note) => ( <li>{note.id}</li> ))} </ul> <p>fetch:</p> {id}{title}{body}{updated_at} </div> ); } 「実験」 Hoge.server.jsをコピーしてHoge.client.jsを作成してみましょう。 App.server.jsのimportをHoge.clientにしてみましょう。 実行時に TypeError: Cannot read property 'db' of undefined になります。 (fetchは可能です) 実験後は元に戻しておきましょう(App.server.jsのimportをHoge.serverに戻す)。 クライアントコンポーネントを作成 サーバーコンポーネントとクライアントコンポーネントを入れ子にして記述してみましょう。React Server Components は、原則、サーバーコンポーネントから始まります。 下記のようなコンポーネント設計をしてみます。 - ServerComponentHello (Hello.server.js) ∟ ClientComponentLeft (Left.client.js) - ServerComponentWorld (World.server.js) ∟ ClientComponentRight (Right.client.js) src/App.server.js(変更してみましょう) import Hello from './Hello.server'; import World from './World.server'; export default function App({selectedId, isEditing, searchText}) { return ( <div className="main"> <Hello /> <World /> </div> ); } src/Hello.server.js(新規作成します) サーバーコンポーネント。dbから値を取得し、子となるクライアントコンポーネント(Left)に引き継ぎます。 import {db} from './db.server'; import Left from './Left.client'; export default function Hello() { const notes = db.query( `select id from notes` ).rows; let text = ""; notes.map((note) => { text += `${note.id},`; }); return ( <Left text={text} /> ); } src/World.server.js(新規作成します) サーバーコンポーネント。fetchで値を取得し、子となるクライアントコンポーネント(Right)に引き継いでいます。 import {fetch} from 'react-fetch'; import Right from './Right.client'; export default function World() { const note = fetch(`http://localhost:4000/notes/1`).json(); let {id, title, body, updated_at} = note; let text = `${id}${title}${body}${updated_at}`; return ( <Right text={text} /> ); } src/Left.client.js(新規作成します) クライアントコンポーネント。渡された値を左側に表示します(cssで設定)。 export default function Left({text}) { return ( <div className="left"> {text} </div> ); } src/Right.client.js(新規作成します) クライアントコンポーネント。渡された値を右側に表示します(cssで設定)。 export default function Right({text}) { return ( <div className="right"> {text} </div> ); } public/style.css(既存ファイルを変更します。※末尾に追記) .left { float: left; width: 50%; } .right { float: right; width: 50%; } Webブラウザから確認しましょう。 http://localhost:4000/ 下記のように表示されると思います。 1,2 ...(略) 1Meeting ...(略) 「補足」 ちなみにClientComponentの子にServerComponentを置いてもエラーにはなりませんが、そのServerComponentからはdbへのアクセスができません(fetchはできます)。 - ServerComponentHello (Hello.server.js) ∟ ClientComponentLeft (Left.client.js) ∟ ServerComponentWorld (World.server.js) ※dbアクセス不可能 ∟ ClientComponentRight (Right.client.js) React Server Components のメリット SSRとSPAの良いとこどり。 React Server Components は「レンダリングのパフォーマンス改善(目標バンドルサイズゼロ)」がメリットです。 (React Server Components を利用するだけで表示が軽くなるわけではなく、SPAにおけるWarterFall問題など、コンポーネント設計はきちんとする必要があります) 参考: RFC https://github.com/reactjs/rfcs/pull/188 > Reactサーバーコンポーネントの利点の1つは、開発者が単一の言語とフレームワークを使用してアプリケーションを記述し、サーバーとクライアント間でコードを共有できることです。 「実験」 わざと遅延を発生させてみましょう。 React Server Componentsのデモにはfetch用のsleepが用意されています。 これを実行して、わざと遅延を生じさせます。 src/World.server.js(変更しましょう) import {fetch} from 'react-fetch'; import Right from './Right.client'; export default function World() { let _ = fetch(`http://localhost:4000/sleep/3000`); // 3秒の遅延 const note = fetch(`http://localhost:4000/notes/1`).json(); let {id, title, body, updated_at} = note; let text = `${id}${title}${body}${updated_at}`; return ( <Right text={text} /> ); } Webブラウザで確認してみましょう。 3秒後に表示されるようになったと思います。 http://localhost:4000/ 「検証」 WebブラウザにChromeを使用し、Chromeの開発ツール(右クリックで検証)を開き、Networkタブを選択し、react?location=... の Previewを見ると、サーバー側からクライアント側に返されるデータを見ることができます。 Suspense 今までの実験的機能はReact Server Componentsのために用意されてきたとも言われています。それらの実験的機能はデモに利用されています。これをTIPSとして紹介していきます。 Suspense(サスペンス)はReact16で導入された実験的な機能です。 コードのロードを「待機」して宣言的にロード中状態(スピナーのようなもの)を指定することができます。 https://ja.reactjs.org/docs/concurrent-mode-suspense.html デモに従い、 <Suspense /> を利用しましょう。 import {Suspense} from 'react'; を行い、3秒の遅延処理がある <World /> を、 <Suspense> ... </Suspense> で囲みます。<Suspense> のfallbackには待ち時間に表示しておくタグを渡しておきます。 参考 New Suspense SSR Architecture in React 18 #37 https://github.com/reactwg/react-18/discussions/37 src/App.server.js(変更してみましょう) import {Suspense} from 'react'; import Hello from './Hello.server'; import World from './World.server'; import Right from "./Right.client"; export default function App({selectedId, isEditing, searchText}) { return ( <div className="main"> <Hello /> <Suspense fallback={<Right text={"This is suspense."} />}> <World /> </Suspense> </div> ); } Webブラウザで確認してみましょう。 今度は、最初に This is suspense. が表示され、3秒後に完全なページが表示されるようになったと思います。 http://localhost:4000/ トランジション ボタンを押した時など、画面が再表示される際、一瞬だけ白い画面がチラっと見えたり、さっきまで表示されていた情報が見られなくなったりなど、画面の更新のタイミングを調整したい場合があります。 このような「見せたくない処理」をスキップして、新しい画面に切り替え (transition) する前に新しいコンテンツがロードされるのを調整(待機)できます。 参考 並列的 UI パターン(実験的機能) トランジション https://ja.reactjs.org/docs/concurrent-mode-patterns.html 実際にやってみると一目瞭然です。 再描画の処理を入れてみましょう。トランジションを使うパターンと使わないパターンを用意して、比較してみます。 src/Left.client.js(変更してみましょう) import {useTransition} from 'react'; import {useLocation} from './LocationContext.client'; export default function Left({text}) { const [location, setLocation] = useLocation(); const [, startTransition] = useTransition(); let idNext = location.selectedId + 1; return ( <div className="left"> <p>id={location.selectedId}</p> <button onClick={() => { setLocation((loc) => ({ selectedId: idNext, isEditing: false, searchText: loc.searchText, })); }}> Next id={idNext} </button> <button onClick={() => { startTransition(() => { setLocation((loc) => ({ selectedId: idNext, isEditing: false, searchText: loc.searchText, })); }); }}> Next id={idNext} (Transition) </button> <p>{text}</p> </div> ); } トランジションを使ったほうが、より自然な画面遷移になると思います。 トランジションを使用しない場合だと、Rightコンポーネントが、Nextボタンを押すたびに「This is suspense.」と表示されてしまいます。 Rightコンポーネントは意図的に3秒遅延処理を入れているため、トランジションの使用にかかわらず、新しいデータが表示されるまでに3秒待つことになります。 クライアントコンポーネントからサーバーコンポーネントに値を渡す サーバー側で値を引き継ぐ方法です。 Facebookのデモでは、Appが3つの引数( {selectedId, isEditing, searchText} )を取っています。 これは、先程のトランジションについてのクライアントコンポーネントのコード(LocationContext.clientのsetLocation関数)と関連しています。 setLocation((loc) => ({ selectedId: idNext, isEditing: false, searchText: loc.searchText, })); これにより、クライアントからサーバーへ値を引き渡すことができます。 サーバーコンポーネントの <Hello /> と <World /> に、selectedIdを引き継ぎましょう。 selectedId={selectedId} のように記述します。 src/App.server.js(変更します) import {Suspense} from 'react'; import Hello from './Hello.server'; import World from './World.server'; import Right from "./Right.client"; export default function App({selectedId, isEditing, searchText}) { return ( <div className="main"> <Hello selectedId={selectedId} /> <Suspense fallback={<Right text={"This is suspense."} />}> <World selectedId={selectedId} /> </Suspense> </div> ); } <Hello /> と <World /> もselectedIdが参照できるように変更します。せっかくselectedIdが参照できるようになったので、fetch・dbに利用しましょう。 src/Hello.server.js(変更します) import {db} from './db.server'; import Left from './Left.client'; export default function Hello({selectedId}) { const notes = db.query( `select id from notes where id=$1`, [selectedId] ).rows; let text = selectedId; notes.map((note) => { text = note.id; }); return ( <Left text={text} /> ); } src/World.server.js(変更します) import {fetch} from 'react-fetch'; import Right from './Right.client'; export default function World({selectedId}) { let _ = fetch(`http://localhost:4000/sleep/3000`); // 3秒の遅延 if (!selectedId) { return ( <Right /> ); } let note = fetch(`http://localhost:4000/notes/${selectedId}`).json(); let {title, body, updated_at} = note; let text = `${selectedId}${title}${body}${updated_at}`; return ( <Right text={text} /> ); } Webブラウザで確認してみましょう。 Nextを押すと、idに応じたデータが表示されるようになったと思います。 http://localhost:4000/ 注:このままだと、存在しないidを指定した場合にシンタックスエラーになって落ちてしまうため、デモのAPIを修正(暫定対応)してください。 server/api.server.js(変更します) 177行目、 res.json(rows[0]); を res.json(rows[0] || "null"); に変更します。 app.get( '/notes/:id', (略) res.json(rows[0] || "null"); // ←変更 (略) ); "null" にした理由はこちらを参照ください。 この件、reactjs/server-components-demo に Pull Request を出しました。 fetchによるREST API処理 PostgreSQLにレコードを登録してみましょう。 デモで用意されているAPI( server/api.server.js に実装されています )を使います。 server/api.server.js には登録の他に、更新・削除のAPIも用意されています。 デモのコードを参考に登録処理を実装してみましょう。 新規登録(idは新しく付与)されます。Nextボタンを押して、新規作成されたデータを確認してみましょう。一番最後に追加されています。 onClickにトランジションを入れても大丈夫です。 src/Former.server.js(新規作成します) import {fetch} from 'react-fetch'; import FormerClient from './Former.client'; export default function Former({selectedId}) { const note = selectedId != null ? fetch(`http://localhost:4000/notes/${selectedId}`).json() : null; if (!note) { return <FormerClient id={null} initialTitle={""} initialBody={""} />; } let {id, title, body} = note; return <FormerClient id={id} initialTitle={title} initialBody={body} />; } src/Former.client.js(新規作成します) import {useState, useTransition} from 'react'; import {useLocation} from './LocationContext.client'; import {createFromReadableStream} from 'react-server-dom-webpack'; import {useRefresh} from './Cache.client'; export default function Former({id, initialTitle, initialBody}) { const [title, setTitle] = useState(initialTitle); const [body, setBody] = useState(initialBody); const [location, setLocation] = useLocation(); const [, startNavigating] = useTransition(); const refresh = useRefresh(); function navigate(response) { const cacheKey = response.headers.get('X-Location'); const nextLocation = JSON.parse(cacheKey); const seededResponse = createFromReadableStream(response.body); startNavigating(() => { refresh(cacheKey, seededResponse); setLocation(nextLocation); }); } // 登録処理 async function handleCreate() { const payload = {title, body}; const requestedLocation = { selectedId: "", isEditing: false, searchText: location.searchText, }; const endpoint = `http://localhost:4000/notes/`; const method = `POST`; const response = await fetch( `${endpoint}?location=${encodeURIComponent(JSON.stringify(requestedLocation))}`, { method, body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json', }, } ); console.log(response); navigate(response); } // 更新処理 async function handleUpdate() { const payload = {title, body}; const requestedLocation = { selectedId: location.selectedId, isEditing: false, searchText: location.searchText, }; const endpoint = `http://localhost:4000/notes/${location.selectedId}`; const method = `PUT`; const response = await fetch( `${endpoint}?location=${encodeURIComponent(JSON.stringify(requestedLocation))}`, { method, body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json', }, } ); console.log(response); navigate(response); } // 削除処理 async function handleDelete() { const payload = {title, body}; const requestedLocation = { selectedId: location.selectedId, isEditing: false, searchText: location.searchText, }; const endpoint = `http://localhost:4000/notes/${location.selectedId}`; const method = `DELETE`; const response = await fetch( `${endpoint}?location=${encodeURIComponent(JSON.stringify(requestedLocation))}`, { method, body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json', }, } ); console.log(response); navigate(response); } return ( <form onSubmit={(e) => e.preventDefault()}> <input type="text" value={title} onChange={(e) => { setTitle(e.target.value); }} /> <input type="text" value={body} onChange={(e) => { setBody(e.target.value); }} /> <button onClick={() => { handleCreate(); }}> Create </button> <button onClick={() => { handleUpdate(); }}> Update id={location.selectedId} </button> <button onClick={() => { handleDelete(); }}> Delete id={location.selectedId} </button> </form> ); } src/App.server.js(変更します) 作成したFormer(サーバーコンポーネント)を記述します。 <Former /> の親となる要素には、keyを与えてください。keyは、どの要素が変更・追加・削除されたのかを、Reactが識別する際に必要となります。 下記では <section></section> を使いましたが、 <div></div> でも大丈夫です。 import {Suspense} from 'react'; import Hello from './Hello.server'; import World from './World.server'; import Right from "./Right.client"; import Former from "./Former.server"; export default function App({selectedId, isEditing, searchText}) { return ( <div className="main"> <Hello selectedId={selectedId} /> <Suspense fallback={<Right text={"This is suspense."} />}> <World selectedId={selectedId} /> </Suspense> <section key={selectedId}> <Former selectedId={selectedId} isEditing={isEditing} /> </section> </div> ); } 外部のDBを利用する credentials.js を変更します。 credentials.js 例:ec2-18-181-96-11.ap-northeast-1.compute.amazonaws.com のDBを利用する。 module.exports = { host: 'ec2-18-181-96-11.ap-northeast-1.compute.amazonaws.com', database: 'notesapi', user: 'notesadmin', password: 'password', port: '5432', }; Webサーバー(express)のポートを変更する 80番にする例です。 server/api.server.js を80に変更します。 const PORT = 80; Dockerを使用している場合、docker-compose.ymlの設定も80に変更します。 ports: - '80:80' environment: PORT: 80 その他、REST APIを使用している箇所(エンドポイント)を80に変更します。 fetch(`http://localhost:80/notes/...`) ※80番なので、省略してもかまいません。 スケールアウトについて 簡単な検証を行ってみました。 結論から言うと一般的な方法でスケールアウトできます。 検証 React Server Components のデモを Amazon Linux2 (EC2) 3台にデプロイ。 DB ec2-18-181-96-11.ap-northeast-1.compute.amazonaws.com:5432 Webサーバー1 http://ec2-52-192-75-244.ap-northeast-1.compute.amazonaws.com:4000/ ※DB接続先を変更しています module.exports = { host: 'ec2-18-181-96-11.ap-northeast-1.compute.amazonaws.com', database: 'notesapi', user: 'notesadmin', password: 'password', port: '5432', }; Webサーバー2 http://ec2-54-238-209-222.ap-northeast-1.compute.amazonaws.com:4000/ ※DB接続先を変更しています module.exports = { host: 'ec2-18-181-96-11.ap-northeast-1.compute.amazonaws.com', database: 'notesapi', user: 'notesadmin', password: 'password', port: '5432', }; 次に Route 53 を使って、リクエストを振り分けるように設定します(DNSラウンドロビン)。 rsc-demo.cmsvr.live レコードタイプ A 値 52.192.75.244 54.238.209.222 これでアクセスしてみます。 例 http://rsc-demo.cmsvr.live:4000/ 期待通りの動作をすると思います。 これは通常のSSRのように、クライアントの状態をサーバーに送信しているためです。 具体的には、Appの引数にある下記の値を、URLのqueryとHeaderのX-Locationに設定して整合性を保っています。 {selectedId, isEditing, searchText} ただし、デモにあるキャッシュの処理は工夫が必要かもしれません。 演習 React Server Components のWebアプリを作ってみましょう。 いきなり1から作ることは大変ですので、ちょっとしたWebアプリを作成しておきました。 エンジニアが気軽に React Server Components を試すことができ、それをすぐに公開できるWebアプリです。操作はとても簡単です。Fork して、デプロイ用の docker-compose.yml を上書きするだけです。 ソースコードはGitHubにあります(MIT License)。 https://github.com/rgbkids/server-components-serverless Dockerを使って、エフェメラルポート(ポート番号49152〜65535)を割り振り、個々のアプリを起動する仕組みです。チーム内のデモ用開発などのご利用にどうぞ。 const portDynamicMin = 49152; const portDynamicMax = 65535; 課題 リファクタリングをしてみましょう。 body はデモコードのままの変数名です。 body ではなく、 port にしてみましょう。 title はデモコードのままの変数名です。title ではなく、 url にしてみましょう。 vteacher.cmsvr.live をあなたが取得しているドメインにしてみましょう。 サーバー側のレンダリング処理が Former.client.js を利用しているだけです。さらに、クライアントコンポーネントからfetchをする作りになっています。SPAらしい動きではありますが、現状の作りですと、React Server Components の恩恵を受けていない形なので、改善してみましょう。 ☕️Coffee break 1 SSR経験者ならReact Server ComponentsでSPAがめちゃくちゃ簡単に作れる話 弊社が開発しているVTeacherの一部にReact Server Componentsを使用しています。YouTube等にライブ配信をするサービスなので、視聴用にとても便利です。 ぜひサインイン(Googleアカウント)して、FilterやPIN留めの操作をしてみて、SPAらしい挙動を確認してください。余計なあたまからのレンダリングによる白い画面のチラツキがなく、PCにインストールするソフトウェアや、スマホのアプリのような操作体験ができるものです。 ソースコードは コチラ です。 ※説明のため、ベースとした reactjs/server-components-demo のコードをできるだけ残し、必要に応じてコードを追加する形にしています(ファイルの先頭に //@ts-check を追加しています)。 重要なことは「SPA」感が出ていることです。 ☕️Coffee break 2 JavaScriptを巡る主導権争い 直接 React Server Componetns とは関係ありませんが、47歳さんの漫画をみて、思うことがありました。昨今の「JavaScriptはバックエンドエンジニアのものか、フロントエンドエンジニアのものか?」というような、まるでDevOpsのような問題がJavaScriptでも起きています。 (C) tome_ura さん これはCさんチーム(おそらくAPI担当?)とアプリチームの話だと思いますが、DevOpsのような争いが実際の職場でも発生していると思います。 Wikipedia https://ja.wikipedia.org/wiki/DevOps DevOps(デブオプス)は、ソフトウェア開発手法の一つ。開発 (Development) と運用 (Operations) を組み合わせたかばん語であり、開発担当者と運用担当者が連携して協力する(さらに両担当者の境目もあいまいにする)開発手法をさす。ソフトウェアを迅速にビルドおよびテストする文化と環境により、確実なリリースを、以前よりも迅速に高い頻度で可能とする組織体制の構築を目指している。 React Server Componetnsは、フロントエンドとバンクエンドで共通言語を利用できます。 フロントエンドとバックエンドのJavaScriptを巡る主導権争いも、解決できるかもしれません。 FacebookのLauren Tan(poteto)さんもRFC内でこれについて回答しています。 (翻訳) アーキテクチャに関しては、チームが選択した言語でバックエンドサービスを作成し続けることも期待しています。フロントエンドチームは、React Server Componentsを採用することを選択した場合、それらのバックエンドサービスを構成するnodejsで記述されたフロントエンドAPI / BFF(フロントエンドのバックエンド)を作成できます。ここでの良い点は、これをすべて1つの言語と1つの技術スタックで実行できることです。 ☕️Coffee break 3 今までのSPA SPAについて、今までは下記のアーキテクチャが多かったのではないでしょうか。 React + (Next) + API(REST/GraphQL) MVCフレームワーク(PHPやRuby on Rails等)のViewにReactやVue 2について、 5年くらい前でしたら「ReactはMVCのViewを補うライブラリ」程度の役割でしたが、昨今の進化を見る限りでは、Viewの一部では恩恵を受け切れていない状態になっていると思います。 1について、 1が最近のモダンなアーキテクチャ・スキルセットでしょう。しかしSSGにできるサイト/サービスには限りがあり、結局のところSSRの検討が必要になることが多いと思います。 さらに、これらはWaterfall問題が発生しやすく、その対策が必要になります(Facebookの中の人いわく、React + Relay + GraphQLでクライアント側のWaterfallはあっさり解決するのだが、Facebookのエコシステムを使っていない人は、React Server Components の方法は効果的だと説明しています)。 ☕️Coffee break 4 ウォーターフォール(Waterfall)問題とは? 最初のコンポーネントのfetchが完了した後に、次のコンポーネントのfetchが開始するような コンポーネント設計上のアンチパターンです。※開発手法のWaterfallとは異なります(上から下に水が流れるイメージは同じです)。 具体的には下記のような例です。 <Hello /> がレンダリングを完了するまで、 <World /> はfetchを開始できない。 <Hello> <World /> </Hello> // <Hello /> function Hello() { const result = fetchHello(...); // Helloに関するfetch処理 return ( ... ); } // <World /> function World() { const result = fetchWorld(...); // Worldに関するfetch処理 return ( ... ); } ☕️Coffee break 5 React Server Components は どの立ち位置か そもそもReact Server Componentsとは何なのか? React Server Componentsを少し勉強した人ほど、React Server Componentsの立ち位置がよくわからなくなってくると思います。 ☕️Coffee break 6 Facebookのフロントエンドエンジニアを知る React Server Componentsの誕生の経緯を知るために、先にFacebookのことを知ってみましょう。これは米国カリフォルニア州メンローパーク(Menlo Park)の求人です。 Facebookにおける、フロントエンドエンジニアとは? (翻訳) フロントエンドエンジニアの責任 1.多くのエンジニアが関与する複雑な技術的または製品的取り組みを主導する 2.同僚に技術的なガイダンスとメンターシップを提供する 3.ニュースフィードなどのFacebook製品の機能とユーザーインターフェイスを実装する 4.複雑なWebアプリケーションを駆動する効率的で再利用可能なフロントエンドシステムを設計する 5.プロダクトデザイナー、プロダクトマネージャー、ソフトウェアエンジニアと協力して、魅力的なユーザー向け製品を提供します 6.パフォーマンスとスケーラビリティの問題を特定して解決する 「React Server Components」の提案は、これらのうち4と6に該当すると思います。 ☕️Coffee break 7 React Server ComponentsのRFC 下記がReact Server ComponentsのRFCです。 ( RFC = Request for Comments = 意見募集中 ) ここでFacebookのDan Abramov(React Core)さんとLauren Tan(React Data)さんが回答をしています。この2人はdemoの動画でも話している2人です。 重要なキーワードとして下記のことを挙げています。 バンドルサイズゼロ (クライアント側の)Waterfall問題 バンドルサイズゼロとは FacebookのLauren Tan(poteto)さんもRFC内でこれについて回答しています。 (翻訳) バンドルサイズが大きいということは、ダウンロード、解析、実行するJavaScriptの数が多いということです。ローエンドのデバイスでは、この作業を迅速に行うための計算能力が低いため、これは大きな問題となります。 React Server Componentsをひとことで説明するなら「パフォーマンス改善のための技術」でしょう。 ですが、副産物的なメリットもあります。それを次に説明します。 ☕️Coffee break 8 React Server Componentsの副産物的なメリット React Server Componentsは、SSR経験者にとってSPAをつくるときに「React Server Components」ですと、とても作りやすいと思います。 ※ここでいうSSRとは、いわゆる広義のSSR(PHPやRuby on Railsなど)を指します。 Lauren Tan(poteto)さんも「状態管理」について回答しています。 (翻訳) 状態管理をほとんど行う必要がありませんでした。したがって、多くの点で、React Server Componentsは、インタラクティブなクライアントコンポーネントからデータフェッチを分離するのに実際に役立つと思います。 いままではSPAを作る際、コンポーネントに分けたあと、fetchでサーバーからデータを取得し、コンポーネント同士でデータの整合性を保ちながら、状態を管理するなど、少し敷居が高かったのです。でもReact Server Componentsなら、まるでPHPやRoRを書くように、SPAを書けるのではないでしょうか。 ☕️Coffee break 9 React Server Componentsの重要なキーワードを整理 ゼロバンドルサイズを目指す クライアント側のWaterfallを解消 コンポーネントの状態管理が楽 ☕️Coffee break 10 基本はSSR(PHPやRails等)の考えでつくることができる SSRは広義(PHPやRialsなど)の意味とします。 SSRの基本的な動作は、たとえばbuttonでclickイベントが発生したら、いったんサーバにリクエストをし、サーバーはそれに応じてhtmlを返すものです。原則、requestのたびにページ全体がレンダリング(描画)されます。 React Server Components はサーバーが名称にはいっていることもあり、このようにバックエンド(と日本では定義している)処理もできます。 React Server Componentsはフレームワークですが、CakePHPやRailsのようにフルな機能はありません。都度、機能を実装、またはライブラリを使うことになります。 重要なポイントはSSRの感覚で書いているのに、SPAらしく動作することです。 ☕️Coffee break 11 React Server Components 作り方の基本 今から作ろうとするWebアプリケーションに状態を持たせましょう。 聞きなじみのある言葉で説明するなら、オブジェクト指向で言うところのImutableに設計するということです。 Immutable Wikipedia https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB React https://ja.reactjs.org/tutorial/tutorial.html#detecting-changes 例: userId: "u001", token: "12345abcde", lang: "en", JavaScriptのonClickイベントで次のように記述すると、状態を変更できます。 これをReact Server Components では「Locationを変更する」と言います。 setLocation((loc) => ({ userId: "", token: "", lang: "", })); onClickのイベント発生時に、アプリケーションの状態を変更(setLocation)すると、いったんサーバーにいきます(このあたりは差分だけレンダリングするなど、全部レンダリングしない仕組みになっています。イベントのシーケンスは同じイメージになります)。 これはまるでPHPやRoRなどの、SSRのようです。 各引数には{}を忘れないようにしてください。propsを指定の変数名で展開してくれます。 propsをまるごと受け取りたい、そして子供に渡したい場合は、{}なしでpropsなどの変数名で良いと思います。 function App(props) { console.log(props); console.log(`props.id=${props.id}`); console.log(`props.id=${props.name}`); return (<div></div>); } { id: a name: aaa } props.id=a props.name=aaa ☕️Coffee break 12 React Server Componentsの認証 認証について Firebaseを使った認証を考えます。 ただしあくまで認証のみの利用とします。 FirebaseのCloudストレージなどは、基本的にクライアント側での非同期処理となり(結果としてuseEffect=副作用の多様となり)、React Server Componentsのせっかくのサーバーサイド処理(RDBに対しての処理)が整合性が取りづらくなり、書きづらくなります。 なので、基本はFirebaseのAuthのみを利用することにします。ログイン後にuidとtoken(30分ごとにリフレッシュされる)を取得し、RDBに保存します。 このuidとtokenは、fetchの際、認証されます。 ちなみにFirebaseのバージョンが古い例なので、ただFirebaseの最新版にするだけだと物足りないと思うので、話題のSupabaseに挑戦してみたらいかがでしょう。 Authのコード - https://github.com/rgbkids/server-components-demo/blob/main/src/Note.server.js import Auth from "./Auth.client"; <Auth /> これによりAuth(Firebaseの認証)がはいります。 Authのコードです。Firebaseを使っているので必ずclient.js(クライアントコンポーネント)にしてください。 https://github.com/rgbkids/server-components-demo/blob/main/src/Auth.client.js 個別の情報は settings.js に記述しておきます。 https://github.com/rgbkids/server-components-demo/blob/main/settings.js.default export const firebase_config = { apiKey: "", projectId: "", authDomain: "", databaseURL: "", }; https://github.com/rgbkids/server-components-demo/blob/main/src/firebase.js import firebase from 'firebase'; export const useSignIn = () => { let provider = new firebase.auth.GoogleAuthProvider(); firebase.auth().signInWithRedirect(provider); }; export const useSignInPopup = () => { let provider = new firebase.auth.GoogleAuthProvider(); firebase.auth().signInWithPopup(provider); }; export const useSignOut = () => { firebase.auth().signOut(); }; export const useCurrentUser = () => { return firebase.auth().currentUser; }; export const useFirebase = () => { return firebase; } if (firebase.apps.length == 0) { const firebase_config = require(__dirname + '/../settings'); const firebaseConfig = firebase_config["firebase_config"]; firebase.initializeApp(firebaseConfig); } さいごに いかがでしたでしょうか? オリジナルのコンポーネントを作成し、データの登録・更新・削除までを行うことができました。 TIPSで説明したような、React Server Components のためと言われている、実験的な機能も体験してみました。 ブック以外で閲覧するには 情報の新鮮さを重視しています。投稿記事は定期的に削除しています。どうしても過去記事を読みたい場合は、VTeacherの「サブスク加入者のページ」からご閲覧ください。 https://apps.apple.com/app/vteacher/id1435002381
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactを基本からまとめてみた【13】【React Context】

React Contextとは React Context は配下の子コンポーネントにデータを渡すための便利な方法で、従来は props を使用することで、子コンポーネントにデータを渡していたが、コンポーネントのネストが深くなると非常に面倒で複雑になる。 Context を使用することで 認証 や UI テーマ など多くのコンポーネントが使用する情報を共有して保持・取得できる。 なぜ Context を使うのか React Context を使うと、コンポーネントツリーの指定した部分で、共通に使えるデータストアを簡単に実装でき、ステート (state, 状態) をそのまま使うこともできる。 しかし、例えば、親ノードで定義したステートを孫ノードで変更しようと思ったら、 変更の要求を孫から親の親へと伝えないといけない場合が出てくる。これは煩わしく、仕組みも複雑になり、メンテナンスしにくいコードになる場合が多い。 そこで、Context を用意する。Context からデータをとって使い、変更が必要ならそこで行うようにする。 Context API で登場するモノと役割 React では Context を使えるようにするための仕組みが、『Context API』 として用意されている。Context オブジェクトを作るには createContext() 関数を呼ぶ。 Context には、関連付けされた『プロバイダ (Provider)』と『コンスーマ (Consumer)』がある。 Provider の役割は大きく二つあり、『コンテキストの適用範囲を決めること』『コンテキストにデータを置くこと』である。 Consumer はコンテキストからデータを取り出して使うために使う。コンスーマでコンテキストからデータ変更用の情報も渡されれば、データの更新をすることもできるようになる。 Provider の基本的な実装 Context オブジェクトの作成と Provider の基本形は次のようになる。 sample.js import React, { createContext } from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import './index.css'; const fruits = ['Apple', 'Orange', 'Banana']; export const FruitContext = createContext(); ReactDOM.render( <React.StrictMode> <FruitContext.Provider value={{ fruits }}> <App /> </FruitContext.Provider> </React.StrictMode>, document.getElementById('root') ); 7 行目で useContext() 関数を使って Context オブジェクトを作成する。 ※後で Consumer で参照するので export する。 11 行目と 13 行目で App 要素を取り囲む。 これによりコンテキストのスコープが、コンポーネントツリーにおける App 以下になる。 また 11 行目で、 value プロパティに、データソースとなる配列を渡す。 Consumer の基本的な実装 プロバイダ側でコンテキストにセットしたデータ (ここでは fruits) は、Consumer で受け取ることができる。 sample.js import React from 'react'; import './App.css'; import { FruitContext } from './index'; function App() { return ( <FruitContext.Consumer> {(value) => { const { fruits } = value; console.log(fruits); // ["Apple", "Orange", "Banana"] return ( <h1>...</h1> ) }} </FruitContext.Consumer> ); } export default App; 7 行目から 15 行目で Consumer を使う。 8 行目のアロー関数のパラメータとして、Provider 側で value プロパティに渡したオブジェクトが渡される。 9 行目で分割代入を使って、value から fruits を取り出す。 useContext フックを使った Consumer の基本的な実装 さらに useContext フックを使うと、Consumer がシンプルにかくことができる。 sample.js import React, { useContext } from 'react'; import './App.css'; import { FruitContext } from './index'; function App() { const { fruits } = useContext(FruitContext); console.log(fruits); // ["Apple", "Orange", "Banana"] return ( <h1>...</h1> ); } export default App; FruitContext.Consumer 要素を作成しなくても、useContext フックを使うことで、Consumer に渡される value が直ちに取得できる。 参考サイト 【レタ返】React Contextについて Everything you need to know about React’s Context API AWS Amplify での Cognito アクセスは React Context.Provider を使って認証処理を Hooks 化しよう React Context とは?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npmとyarnとNode.jsについて

はじめに VueやReactを使ってWeb開発をする際、npmやyarnといったコマンドを使ってプロジェクトの作成やパッケージのインストール等を行っていると思います。 自分はこのnpmやyarnについてなんとなく使っていたので理解を深めるため、主に以下の3点について調べてみました。 そもそもnpm, yarnとは何か Node.jsとは何か npmとyarnで何か違いはあるのか npm, yarn とは 共に、Node.js上で動くJavaScriptのパッケージマネージャーのことを指します。 まず 「パッケージマネージャー」とは コンピュータに何のソフトウェアがインストールされたかを記録し、新しいソフトウェアのインストール・新しいバージョンへのソフトウェアの更新・以前インストールしたソフトウェアの削除を容易に行えるようにするプログラムのこと 参考 パッケージマネージャとは 特徴 パッケージAのバージョン1.5にはパッケージBのバージョン5.0以上が必要といった依存関係を解決する情報や現時点でインストールされているパッケージの情報をもつ パッケージのインストール・アンインストール・アップグレード・ダウングレードを行う インストール・アップグレート時には依存関係にあるパッケージを自動でインストール・アップグレートしてくれる 言語ごとにパッケージマネジャーが分かれている Linux, Windows, macOSなど, OSを管理するパッケージマネジャーもある パッケージマネージャーを使わないと大変 無数のソフト(パッケージ、ライブラリ)をパッケージマネージャーを使わず、ソフトのホームページから手動でインストールするのは骨の折れる作業です。また、それぞれのソフトにはバージョンの依存関係が発生し、それらを確認しながら必要なバージョンをインストールする必要があるためかなり時間がかかります。 パッケージマネージャーの例 パッケージマネジャー 言語 or OS pip Python gem Ruby composer PHP glide Go npm JavaScript yarn JavaScript Homebrew macOS apt-get Linux (Debian系) 次に 「Node.js上で動く」とは そもそもNode.jsとは Node.jsとはサーバ上でJavaScriptを実行するための環境のことです。 もともとJavaScriptはブラウザ上で動く言語ですが、Node.jsをインストールすることでサーバ上でJavaScriptが使えるようになります。「Node.js上で動く」とは「サーバ上でJavaScriptを実行するための環境で動作する」ということになります。 つまり、npm, yarnはNode.jsという環境で動くJavaScriptでできたツール(パッケージやモジュール)を管理するプログラムということになります。 サーバ上で実行する環境とは Node.jsをインストールすることで、RubyやPythonのようなバックエンド言語としてJavaScriptを使うことができ、今までできなかったファイルの読み書きや、ネットワーク通信等のOSの機能にアクセスできるようになるということです。 Pythonを動かしたいとき、PythonをインストールしてPythonの実行環境をつくる必要があるように、サーバ上でJavaScriptを動かしたいときはNode.jsをインストールしてJavaScript実行環境を作る必要があるといったイメージです。 ちなみに、ブラウザ上で動いているJavaScriptはブラウザ自体が実行環境の役割を担ってくれています。 なぜReactで開発を行う際Node.jsが必要になるのか 「ReactはJavaScriptのUIライブラリであり、ブラウザ上で動くものなのでNode.js(サーバ上でのJavaScript実行環境)は不要ではないか」と思っていました。 調べてみると、Node.jsがないと色々と不便なことがあり、Node.jsのインストールは基本的に必要とされています。 ただ以下の記事で紹介されている通り、Node.jsがなくてもReact自体は使えます。 正真正銘のReactだけの不純物なしでReact入門 ブラウザだけで動作するNode.jsなしReactの使い方 Node.jsが必要な理由 npmもしくはyarnを使用したい Reactを使った大規模なアプリケーション開発となると、様々なパッケージをインストールする必要があります。前述しましたが、それらのパッケージを依存関係を意識しながらインストールするにはnpmがあると嬉しくてNode.jsが必要になるということです。 babelを使用したい JavaScriptの新しい書き方から古い書き方に変換する 新しいJS文法に対応していない古いブラウザでも使えるようになる webpackを使用したい JavaScript, CSS, 画像等を一つのjsファイルにまとめる 機能ごとにモジュールを細かく分けられる HTTPリクエストの回数を減らすことができる その他様々なビルドツールや開発補助ツールを使用したい 用途の遷移 つまり、サーバーサイドでJavaScriptのパッケージを管理するnpmでしたが、ReactやVue等のフロントエンド用のパッケージを提供するのにも使われるようになったということですね。 流れとしては以下のようなイメージです。 1. JavaScriptは元々ブラウザで動作するプログラミング言語だった 2. Node.jsの誕生によってサーバでJavaScriptが使えるようになった。 3. 最近ではnpm, babel, webpack等のパッケージを提供する「クライアントサイドのJavaScript実行環境」として使われることが多くなった npmとyarnの特徴 npm Node.jsがリリースされた翌年(2010年)リリース Node Package Managerの略 package-lock.jsonファイルを自動的に生成する Node.jsをインストールすれば自動的にインストールされる yarn 2016年リリース Facebook、Google、Exponent、Tildeによって開発された新しいJavaScriptパッケージマネージャー npmと互換性がある 同じpackage.jsonが使える npmより厳密にモジュールのバージョンを固定できる npmよりインストールが速い yarnの方が優れているように見えますが、最近ではnpmがアップデートされて機能の差はあまりないようです。 参考 パッケージマネージャがパッケージをインストールする仕組み パッケージ管理システムとは? Node.jsとはなにか?なぜみんな使っているのか? Node.js入門編 JSのモジュールとbabelとwebpackとは何かまとめてみる(初心者向け)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

これからReact始めたい人のための今日だけでできるTODO#20 useRefとuseState

useRefとは? 要素の参照を行うためのフックで、DOMへのアクセスで利用される。 コンポーネント内で変数に値を保持させることができ、値が更新されても再レンダリングされないのが特徴です。 値を保持するという点ではuseState()と同じですが、useState()は値が更新された時に再レンダリングされるのでその点が異なっています。 コード例 useEffectによってレンダリングされるたびにconsole.logにレンダリングの文字が表示されるようにしています。 App.js import React, { useState, useRef, useEffect } from 'react'; const SampleUseRef = () => { const inputRef = useRef(null); const [text, setText] = useState(''); useEffect(() => { console.log('レンダリング'); }); const handleClick = () => { setText(inputRef.current.value); }; const inputReset = () => { setText(''); inputRef.current.value = ''; }; return ( <div> <input ref={inputRef} type='text' id='target' /> <button onClick={handleClick}>set text</button> <button onClick={inputReset}>reset</button> <p>text: { text }</p> </div> ); }; export default function App() { return ( <> <SampleUseRef /> </> ); }; buttonをクリックしてテキストを更新するとconsoleにレンダリングが表示されます。 useRef currentプロパティ 下記の記述はuseRef()オブジェクトの引数に0を指定しています。 const ref = useRef(0); これはref.currentの値に0をセットしていることになります。 なのでref.currentに対して変更することが可能です。 また、呼び出しもref.currentで可能です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactを始めたい人へ。今日だけでできるTODO #20 useRefとuseState

useRefとは? 要素の参照を行うためのフックで、DOMへのアクセスで利用される。 コンポーネント内で変数に値を保持させることができ、値が更新されても再レンダリングされないのが特徴です。 値を保持するという点ではuseState()と同じですが、useState()は値が更新された時に再レンダリングされるのでその点が異なっています。 コード例 useEffectによってレンダリングされるたびにconsole.logにレンダリングの文字が表示されるようにしています。 App.js import React, { useState, useRef, useEffect } from 'react'; const SampleUseRef = () => { const inputRef = useRef(null); const [text, setText] = useState(''); useEffect(() => { console.log('レンダリング'); }); const handleClick = () => { setText(inputRef.current.value); }; const inputReset = () => { setText(''); inputRef.current.value = ''; }; return ( <div> <input ref={inputRef} type='text' id='target' /> <button onClick={handleClick}>set text</button> <button onClick={inputReset}>reset</button> <p>text: { text }</p> </div> ); }; export default function App() { return ( <> <SampleUseRef /> </> ); }; buttonをクリックしてテキストを更新するとconsoleにレンダリングが表示されます。 useRef currentプロパティ 下記の記述はuseRef()オブジェクトの引数に0を指定しています。 const ref = useRef(0); これはref.currentの値に0をセットしていることになります。 なのでref.currentに対して変更することが可能です。 また、呼び出しもref.currentで可能です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactのRefをTypeSafeに独自実装する際のまとめ

タイプセーフにReactのRefを独自実装する際、毎回忘れて調べなおしているので覚書 import { forwardRef, ForwardRefRenderFunction, useImperativeHandle } from 'react'; export interface HogeElement { bar: string; buzz: () => void; } export interface HogeProps { bizz: string; } const HogeRefFunction: ForwardRefRenderFunction<HogeElement, HogeProps> = ({ bizz, }, ref)=> { useImperativeHandle(ref, () => { return { bar: '参照等', buzz: () => { // 処理群 } } }) return <div>{bizz}</div>; } export const Hoge = forwardRef(HogeRefFunction); 各種命名規則 HTMLライクにするため、文末にElementを記述 HogeElement HTMLDivElement的な勢い Propsを文末に記述 備考 RefFunctionとコンポーネントを命名しておき、無名化を防ぐ 最終的に型は求まるため、forwardRefでwrapすれば完成
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む