20210510のReactに関する記事は5件です。

【React】(親から子|子から親)の関数を呼び出す

親から子の関数を呼び出す 実装方法としては、子供側の参照を親から触れるようにしてやり、 参照を通じて子供側の関数を呼び出せれるようにしてやります。 子供側の実装 const ChildrenBase: React.ForwardRefRenderFunction< { printId: () => void }, { id: string; } > = ({ id }, ref) => { // 親から呼ばれる関数を定義 useImperativeHandle(ref, () => ({ printId() { console.log(id); }, })); return <div>Children</div>; }; const Children = forwardRef(ChildrenBase); forwardRefとuseImperativeHandleを使って実現します。 forwardRefでChildrenを参照し、useImperativeHandleで関数を参照に付与します。 親側の実装 const Parent: React.FC = () => { const childRef = useRef<{ printId: () => void }>(); useEffect(() => { childRef.current?.printId(); }, [childRef.current]); return <Children id={'1234567'} ref={childRef} />; }; export default Parent; 親側は参照を通して、子供側の関数を呼び出します。 子から親の関数を呼び出す 今度は逆のパターンで、子供側から親側の関数を呼び出すパターンです。 実装方法として、今回はpropsで渡す実装ではなく、全ての子供に親の関数を呼べるようにしてやります。 ユースケースとしては Layout 等の共通のコンポーネントで、子供が使おうと思ったら Layout の関数が使える ようなケースになるかと思います。 子供側の実装 type ChildrenDispatchProps = { printId?: (id: string) => void; }; const Children: React.FC<ChildrenDispatchProps & { id: string }> = ({ printId, id, }) => { return <button onClick={() => printId(id)}>ボタン</button>; }; 親から渡される関数は ? 付きで定義しないとTypeエラーになります。 親側の実装 const Parent: React.FC = ({ children }) => { const printId = (id: string) => { console.log(id); }; const childWithProps = React.Children.map(children, (child) => { if (React.isValidElement(child)) { const dispatch: ChildrenDispatchProps = { printId: printId, }; return React.cloneElement(child, { ...dispatch }); } return child; }); return <div>{childWithProps}</div>; }; const App: React.FC = () => { return ( <Parent> <Children id={'1'} /> <Children id={'2'} /> </Parent> ); }; cloneElement で子供に親側で定義した関数を付与してやります。 ↑のようにする事で、子供側は ChildrenDispatchProps を受け取るようにした場合、その子供は 親の関数 printId を使えるようになります。 ※ React.cloneElement や React.isValidElement 等は以下リンク先を参照 React の最上位 API - React 参考リンク React hooksで子コンポーネントの関数を実行する - Qiita forwardRef と useImperativeHandle - 30歳からのプログラミング ref のフォワーディング – React javascript — 親から子メソッドを呼び出す javascript - How to pass props to {this.props.children} - Stack Overflow
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ZustandとValtioを比較するサンプルコードをcodesandboxで書いてみた

zustandとvaltioはコーディングのスタイルは違いますが、用途は似ています。zustandをimmerとともに使う場合は、valtioの方があっていることが多いと思います。 そこで、簡単な例を書いてみました。 zustand+immerの場合 valtioの場合 どうでしょうか。これだけの比較で判断することは難しいですが、狙いが伝われば幸いです。 「React Fan」というコミュニティのSlackでzustandやvaltioに関する質問や雑談ができますので、よろしければご参加ください。 React開発者向けオンラインサロン「React Fan」の入り口ページ React開発者向けオンラインサロン「React Fan」のTwitterアカウント
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Go Unity React] 3Dでライフゲームを試せるサイト作りました

はじめに ライフゲーム(海外だとGame of Life)という生物学的モデルシュミレーションを3次元で作成してみました。 サーバーサイド Golang, クライアントUnity & React使用です。 ゲームリンク↓ そもそもライフゲームとは 聞いたことのない方が多いと思いますが、ライフゲームとは格子を生命とみなしたモデルシュミレーションです。 ユーザー自身がルールを定めてどのような生命活動が行われるかシュミレーションすることができます。 基本的には「縦 × 横」の二次元で作成されることが多いですが、 今回は「高さ」を加えた三次元で作ってみました! ゲーム詳細 ゲームリンクはこちらです。 アピールポイント いい感じに生命循環が見られたモデルを保存できるようにしたところ 他ユーザーが作成したモデルにいいねを付けられるようにして、ランキングの高いモデルマップを実際に動かせるようにしたところ ユーザーがシュミレーションを楽しめるようにするため、生命活動のルール変更や、ステージ編集をできるようにしたところ 使用技術 アーキテクチャ図 サーバーサイド系 環境 docker Golang version1.16.2 postgres, heroku GitHubです↓ 綺麗な依存関係を意識してアーキテクチャを組みました。 こちらのgo-clean-archを参考にしています。 DIツールのwireを入れました。また、Postgresを使った理由としてはherokuの無料枠が優秀だったからです。 クライアント系 環境 Unity version2019 JavaScript(react) Vercel(デプロイ先) GitHubです↓ ゲーム部分はUnityで、 webGLビルドしたファイルをreactコンポーネントとして使用できるreact-unity-webglを使いました。 Javasciptでunityの関数も動かすことができるので非常に便利なライブラリだと思います。   頑張ったこと 無料枠を意識した herokuやVercelを利用してなんとか全部無料で永続化できました。 ユーザーがモデルを保存できるところ 3Dのライフゲームをネットで検索してみると、保存が効かないものが多く考察があまりできないことが多かったので頑張りました 改善したい箇所 ルールなどの考察が開発者自身も不十分 作成することに頭が行きがちだったので今後考察して改善していきたいです。 UI... 改善したいです。 おわりに ここまで読んでくださりありがとうございました! 実はこの製作物は友人とハッカソンのために面白そうだから作ったのですがあまり受けませんでした。。。(少し尖っていたかも) 本ゲームの不具合、改善点、技術的な疑問点などありましたらご気軽に質問ください! また、よろしければ遊んでみてください!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Grid.jsで単位ありのsortがしたい。

経緯 Grid.jsのsortオプションでは、以下の関数が動いている if (a > b) { return 1; } else if (b > a) { return -1; } else { return 0; } しかし、これだと200円や10個といった単位付きでの比較はできない。 表示されるDataを変更するオプションは存在するが、a,bには変更後のデータが格納されてしまうため、そこから数値のみを取り出して比較していく。 単位付きのままsortをかけてみる ソースコードは画像の下にあります。 index.js <Grid data={[ {id: '001', fruite: 'りんご', price: 200, sum: 20}, {id: '002', fruite: 'バナナ', price: 150, sum: 10}, {id: '003', fruite: 'ぶどう', price: 800, sum: 3}, {id: '004', fruite: 'メロン', price: 2000, sum: 1}, ]} columns={[ { id: 'id', name: 'ID' }, { id: 'fruite', name: 'フルーツ', sort: false, }, { id: 'price', name: '価格', data: (row)=> row.price + '個', }, { id: 'sum', name: '個数', data: (row)=> row.sum + '個', }, ]} sort={true} search={true} /> 数値のみを取り出してsortをかける index.js <Grid data={[ {id: '001', fruite: 'りんご', price: 200, sum: 20}, {id: '002', fruite: 'バナナ', price: 150, sum: 10}, {id: '003', fruite: 'ぶどう', price: 800, sum: 3}, {id: '004', fruite: 'メロン', price: 2000, sum: 1}, ]} columns={[ { id: 'id', name: 'ID' }, { id: 'fruite', name: 'フルーツ', sort: false, }, { id: 'price', name: '価格', data: (row)=> row.price + '個', sort: { compare: (a,b) => { // a,bから数値のみを取り出す正規表現        // 結果を比較して1, -1, 0のいずれかを返す } } }, { id: 'sum', name: '個数', data: (row)=> row.sum + '個',      sort: { compare: (a,b) => { // a,bから数値のみを取り出す正規表現        // 結果を比較して1, -1, 0のいずれかを返す } } }, ]} sort={true} search={true} /> うまくいくと こうなります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React の Context を分割することで不要な再レンダリングを防ぐ 【constate の紹介】

要点 React の Context の更新による不要な再レンダリングを防ぐ方法として、React.memo や useMemo を利用する方法以外に、Context(Context オブジェクト)を分割するという方法がありますが、constate というライブラリを使うと、Context(Context オブジェクト)を簡単に分割できます。 constate は React Context 周りの API のラッパーを提供する小さな(1KB未満の)ライブラリです。 constate の類似ライブラリとして、unstated-next が挙げられますが、こちらのライブラリでは、Context の分割を行っていないので、「Context(Context オブジェクト)の分割」により再レンダリングを防ぐことはできません。 React の Context を分割するとはどういうことか、分割すると何が良いのか ※ Context の分割に関しては、既に 参考記事1 にて説明されており、このセクション(見出し1)は その記事と重複する内容になっていますが、当記事は constate の README からリンクされているサンプルコード をベースとしたコードで説明しており、そのサンプルコードと比較しやすい形にしています。 Context が分割されていない場合 下図のような、数字(初期値は 10 )を表示するコンポーネントと +ボタン(インクリメントするだけ)を表示するコンポーネント を作ります。 この程度のコンポーネントにしては少し大袈裟なコードですが説明用のためご容赦ください。 全てのコードはこちら(CodeSandbox) App.jsx import * as React from "react"; // Context const Context = React.createContext({}); // カスタムフック function useCounter({ initialCount = 0 } = {}) { const [count, setCount] = React.useState(initialCount); const increment = React.useCallback(() => setCount((c) => c + 1), []); return { count, increment }; } // カスタムフック を ラップ する プロバイダ const CounterProvider = ({ children, initialCount }) => { const value = useCounter({ initialCount }); return <Context.Provider value={value}>{children}</Context.Provider>; }; // プロバイダ から count を取り出す カスタムフック const useCount = () => { const value = React.useContext(Context); return value.count; }; // プロバイダ から increment を取り出す カスタムフック const useIncrement = () => { const value = React.useContext(Context); return value.increment; }; // ボタンコンポーネント function IncrementButton() { console.log("render IncrementButton"); const increment = useIncrement(); return <button onClick={increment}>+</button>; } // カウントコンポーネント function Count() { console.log("render Count"); const count = useCount(); return <span>{count}</span>; } function App() { return ( <CounterProvider initialCount={10}> <Count /> <IncrementButton /> </CounterProvider> ); } export default App; 2回 +ボタンを押下すると、本来 再レンダリング されるべきでない(※) ボタンコンポーネント(IncrementButton)が2回レンダリング されていることがわかります。 ※ ボタンコンポーネント(IncrementButton)は 参照が変更されるcountに依存する必要はなく、React.useCallback でメモ化された参照が変更されない increment関数のみに依存しているためです。 Context が分割されている場合 全てのコードはこちら(CodeSandbox) App.jsx import * as React from "react"; // Context を、参照が変更する count ステイト用 と 参照が変わらない increment 関数用 の2つ作成する const CountContext = React.createContext({}); const IncrementContext = React.createContext({}); // useCounter 変更なし // 2つのプロバイダーをネストして1つのプロバイダーにする const CounterProvider = ({ children, initialCount }) => { const { count, increment } = useCounter({ initialCount }); return ( <CountContext.Provider value={count}> {/* count ステイト用 */} <IncrementContext.Provider value={increment}> {/* increment 関数用*/} {children} </IncrementContext.Provider> </CountContext.Provider> ); }; const useCount = () => { return React.useContext(CountContext); // count ステイト用のコンテクストを使う }; const useIncrement = () => { return React.useContext(IncrementContext); // increment 関数用のコンテクストを使う }; // IncrementButton 変更なし // Count 変更なし // App 変更なし export default App; 2回 +ボタンを押下すると、ボタンコンポーネント(IncrementButton)は再レンダリングされないことがわかります。 constate を使うと何が良いのか Context の分割に constate を使った場合 全てのコードはこちら(CodeSandbox) App.jsx import * as React from "react"; import constate from "constate"; // 追加 // ❗ React.createContext は不要 // useCounter 変更なし // constate の利用 const [CounterProvider, useCount, useIncrement] = constate( useCounter, (value) => value.count, (value) => value.increment ); /* constate の使い方: 第一引数:カスタムフック 残余引数:セレクター関数(第一引数に渡したカスタムフックの戻り値から欲しい値を取り出す関数) 戻り値:配列(第一要素はプロバイダー、第二要素以降はカスタムフックの戻り値をセレクターで取り出した値) */ // IncrementButton 変更なし // Count 変更なし // App 変更なし export default App; 前述の「Context が分割されている場合」と比較すると、ボタンコンポーネントの再レンダリングを防ぐという結果は変わりませんが、コードがシンプルになります。Context が増えてもプロバイダーをたくさんネストさせて書かなくてもよくなります。 コードの差分(Github) constate は型もバッチリ 前述のコード(CodeSandbox) は App.tsx(TypeScript)ではなく、App.jsx(JavaScript) ですが、CodeSandbox のエディタで(もちろんVSCodeでも)しっかりコード補完が効きます。 類似ライブラリ(unstated-next)との比較 (2021/5/10 現在) constate の類似ライブラリとして、unstated-next が挙げられます。unstated-next は Qiita にて複数の記事で取り上げられている一方、constate 主体で記述されている記事は本記事執筆時にはありません。 しかし、npm trends によれば、ダウンロード数において constate は unstated-next を上回っています。 また unstated-next には、Context を分割する機能はありません。 なぜ React の Context を分割すると再レンダリングを防ぐことができるのか プロバイダーを使っているコンポーネントが再レンダリングされた時、React は<Context.Provider value={something}> の something の参照の変更をチェックし、変更されている場合にコンシューマ(※)を再レンダリングするからです。 ※ useContex(Context)等で Context からデータを取得しているコンポーネント 上述の「Context が分割されている場合」だと<IncrementContext.Provider value={increment}>となっており、increment はメモ化されて参照が変更されないため、コンシューマであるボタンコンポーネント(IncrementButton)は再レンダリングされません。 一方、「Context が分割されていない場合」だと <Context.Provider value={value}> の value は、count がインクリメントされれば参照が変わるので、ボタンコンポーネント(IncrementButton)は再レンダリングされます。 // useCounter の戻り値は increment される度に参照が変わる function useCounter({ initialCount = 0 } = {}) { const [count, setCount] = React.useState(initialCount); const increment = React.useCallback(() => setCount((c) => c + 1), []); return { count, increment }; // オブジェクトリテラル を使っているので実行毎に useCounter の戻り値の参照が変わる //return React.useMemo(() => { count, increment }, [count, increment]) // ↑ このようにしても、increment されれば count の参照が変わるので useCounter の戻り値の参照が変わる // ↑ count を useMemo 第二引数(Dependency List)から除去すると、 // Count コンポーネントは再レンダリングされなくなり、カウンター機能が失われる } // useCounter の戻り値(value)は increment されるたびに参照が変わるので // Context のコンシューマである Count と IncrementButton もそのたびに再レンダリングされる const CounterProvider = ({ children, initialCount }) => { const value = useCounter({ initialCount }); return <Context.Provider value={value}>{children}</Context.Provider>; }; 補足:Context の分割 vs React.memo / React.useMemo (私見) 参考記事1 で説明されている通り、Context を分割せずとも React.memo や React.useMemo を利用して、コンシューマの不要な再レンダリングを防ぐことができます。 私は Context の分割で React.memo / React.useMemo が不要になるのなら、積極的に Context を分割すべきと考えています。上流のコンポーネントのプロバイダーの参照比較を数個に増やしただけで、下流の数十個以上のコンシューマにおける参照比較を省略することが期待できるからです(分割後の Context の数とそれらのコンシューマの数が同じような数であることが確定的である場合はこの限りではありませんが)。コンシューマの数が増えれば増えるほど React.memo や React.useMemo 自体のコスト(参照比較のコスト)が増大し、無視できない遅延が発生するケースがあります。(私が Context をちゃんと学習しようと思ったきっかけがまさにこのケースでした。) なお、参考記事1 でも 何らかの理由で Context を分割できない場合、後述のReact.memoかuseMemoを利用した方法で再レンダリングを防ぐ。 と記載されており、Context の分割を優先すべきことが示唆されています。 参考 React の Context の更新による不要な再レンダリングを防ぐ 〜useContext を利用した時に発生する不要な再レンダリングを防ぐ方法に関して〜 Reactのレンダリングに関する完全ガイド #context-の基本 React の Context の更新による不要な再レンダリングを防ぐ 〜useContext を利用した時に発生する不要な再レンダリングを防ぐ方法に関して〜 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む