前提
サンプルをご確認いただけますでしょうか。
サンプル
テキストフォームに文字を入力すると、そのテキストフォームを有するコンポーネントが再レンダーされます。
再レンダーされる度に、何のコンポーネントが何回レンダーされたかをコンソールに表示します。
実現したいこと
再レンダーされる要素をテキストフォームだけにしたいです。
発生している問題
現状はテキストフォームだけではなくヘッダーコンポーネントや、テキストのチェックボタンも再レンダーされてしまいます。
ヘッダーコンポーネントやボタンは再レンダーの必要が無いかと思いますので、ヘッダーコンポーネントやボタンは再レンダーしないようにしたいです。
該当のソースコード
jsx
1import React, { useState, memo } from "react"; 2 3let parent = 0, 4 parentHeader = 0, 5 child = 0, 6 childHeader = 0; 7 8const App = () => { 9 parent++; 10 console.log("parent: " + parent); 11 12 const [parentText, setParentText] = useState(""); 13 const [open, setOpen] = useState(false); 14 const check = () => { 15 if (parentText === "open") { 16 setOpen(true); 17 return; 18 } 19 alert("The text is incorrect."); 20 }; 21 22 return ( 23 <> 24 <ParentHeader /> 25 <input 26 type="text" 27 value={parentText} 28 onChange={e => setParentText(e.target.value)} 29 /> 30 <button onClick={check}>check text</button> 31 <div> 32 {open && <Child setOpen={setOpen} setParentText={setParentText} />} 33 </div> 34 </> 35 ); 36}; 37 38const ParentHeader = () => { 39 parentHeader++; 40 console.log("ParentHeader: " + parentHeader); 41 42 return ( 43 <> 44 <h1>Parent</h1> 45 <p> 46 "open"と入力し"check 47 text"ボタンを押すと、子コンポーネントをマウントします。 48 </p> 49 </> 50 ); 51}; 52 53const Child = memo(({ setOpen, setParentText }) => { 54 child++; 55 console.log("child: " + child); 56 const [childText, setChildText] = useState(""); 57 const checkChildText = () => { 58 if (childText === "close") { 59 setParentText(""); 60 setOpen(false); 61 return; 62 } 63 alert("The text is incorrect."); 64 }; 65 66 return ( 67 <> 68 <ChildHeader /> 69 <input 70 type="text" 71 value={childText} 72 onChange={e => setChildText(e.target.value)} 73 /> 74 <button onClick={checkChildText}>check text</button> 75 </> 76 ); 77}); 78 79const ChildHeader = () => { 80 childHeader++; 81 console.log("childHeader: " + childHeader); 82 83 return ( 84 <> 85 <h2>Child</h2> 86 <p> 87 "close"と入力し"check 88 text"ボタンを押すと、子コンポーネントをアンマウントします。 89 </p> 90 </> 91 ); 92}; 93 94export default App; 95
試したこと
useMemoを使用することで、ヘッダーコンポーネントの再レンダーは防げました。
ヘッダーを再レンダーしないサンプル
jsx
1const memoParentHeader = useMemo(() => { 2 return <ParentHeader />; 3}, []); 4 5const memoChildtHeader = useMemo(() => { 6 return <ChildHeader />; 7}, []);
ただし、上記のサンプルではボタンは再レンダーされます。
jsx
1const memoButton = useMemo(() => { 2 return <button>...<button/>; 3}, []);
上記のようにボタンもメモ化すれば再レンダーを防げるかと思いますが、この方法ですと再レンダーを防ぎたいコンポーネントや要素を一つずつメモ化しなければいけなくなります。
例えばヘッダーの他に本文や画像などを含むコンポーネントで、再レンダーの必要がある要素と必要が無い要素が入り組んでいる場合は、上記のようなメモ化する方法は効率的ではないかと思います。
また、仮にコンポーネントや要素をメモ化したとしても、コンポーネントの背景に画像やグラデーションなどを設定している場合はそれらも再レンダーされてしまいます。
理想ですが、基本的にレンダーは初回の一回だけ、再レンダーが必要な要素のみ別個に何かしらの処理、という形にしたいです。
【追記】親テキストフィールドを子で操作しない場合のサンプル
サンプル2
追記前の2つのサンプルは子コンポーネントで親テキストフィールドをリセットしていますが、親コンポーネントでuseEffectを利用し、親コンポーネントで親テキストフィールドをリセットするようにしました。
jsx
1useEffect(() => { 2 if (!open) setParentText(""); 3}, [open]);
また、親子ともテキストフィールドを別のコンポーネントに切り分け、切り分けたコンポーネント内でテキストフィールドのstateを管理するようにしました。
これにより、再レンダー適用部分がテキストフィールドコンポーネントのみになりました。
サンプル2の問題点
入力されたテキストの正誤を判定する方法につきまして、チェックボタンをテキストフィールドコンポーネントに含める方法しか思いつかなかったため、テキストフィールドのonChangeの度にチェックボタンも再レンダーされます。
また、子コンポーネントのアンマウント時に親テキストフィールドコンポーネントが2回レンダーされてしまいます。
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/01/27 10:42
2020/01/28 03:18