質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

395閲覧

コンポーネント全体の再レンダーを防ぎたい。

whoiwhoi

総合スコア48

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2020/01/27 05:37

編集2020/01/27 10:49

前提

サンプルをご確認いただけますでしょうか。
サンプル

テキストフォームに文字を入力すると、そのテキストフォームを有するコンポーネントが再レンダーされます。
再レンダーされる度に、何のコンポーネントが何回レンダーされたかをコンソールに表示します。

実現したいこと

再レンダーされる要素をテキストフォームだけにしたいです。

発生している問題

現状はテキストフォームだけではなくヘッダーコンポーネントや、テキストのチェックボタンも再レンダーされてしまいます。

ヘッダーコンポーネントやボタンは再レンダーの必要が無いかと思いますので、ヘッダーコンポーネントやボタンは再レンダーしないようにしたいです。

該当のソースコード

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回レンダーされてしまいます。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

私が提供できる手段としては以下の2通りあります。しかし、どちらも根本的な修正が必要で、あまり直接的な回答になっていないため、もしかしたらもっといい手段があるかもしれません。

  1. Redux を使う方法
  2. 根本的な設計を変える方法

この問題の難しい点

単純にテキストフィールドの更新時にはテキストフィールドのみレンダリングするというだけであれば、テキストフィールドのみ子コンポーネント化して、そこで状態を管理(useState 文を書く)すれば大丈夫です。
しかし、 Child コンポーネントで親のテキストフィールドに更新をかけているため、この方法を使うことができません。
useMemo の実装の部分でもおっしゃっていたように、更新をかける場所が入れ子になっていることが問題になります。

1. Redux を使う方法

1つ目の Redux を使う方法ですが、これは Redux が React の状態管理をするためのフレームワークなので、直接的な解決方法と言えると思います。
しかし、もともと Redux を使う前提の設計をしていないと、React のプロジェクトに途中から導入するにはそれなりの量の書き換えが必要になります。
以下、Redux の参考記事です。
Redux入門【ダイジェスト版】10分で理解するReduxの基礎

2. 根本的な設計を変える方法

Child コンポーネントから Parent のテキストフィールドに更新をかけるという設計は少し変な気がします。
そこが修正可能なのであれば、設計を変えて、親コンポーネントの更新を行わない設計にすれば、簡潔に書くことが出来るかと思います。

2つの方法を提示しましたが、冒頭に述べた通り、どちらも根本的な修正が必要な回答になってしまいました。
あまりいい手段を提示できなくてすみません。

----以下追記-----
2の方法で実践したいとのことで、いくつか問題点を上げていただいたので、その回答として追記します。

まず、checkボタンもレンダリングしてしまう問題についてはもう一つcheck用の変数を用意することで、そのcheckの真偽値によって分岐する関数を親コンポーネントで作ることによって、ボタンを親コンポーネントに持つことができます。

また、2回レンダリングをする件についても同じようにchildのcheck用変数を用意することで解決できます。
そもそも2回レンダリングをしてしまう原因ですが、childをアンマウントするタイミングと、openの変化によってparentのテキストフィールドを空にするタイミングの2回でレンダリングしてしまうからです。
これをcheck用の変数が切り替わるタイミングのみでのレンダリングにすることによって、1回のレンダリングにまとめることができます。
以下、ソースコードを載せます。雑に作ったので、関数内の変数名がbだったりしますが、ご了承ください。

const App = () => { parent++; console.log("parent: " + parent); const [open, setOpen] = useState(false); const [checkOpen, setCheckOpen] = useState(false); const [checkClose, setCheckClose] = useState(false); const checkParrentText = b => { if (b) { setOpen(true); return; } alert("The text is incorrect."); }; return ( <> <ParentHeader /> <ParentInput checkClose={checkClose} setCheckOpen={setCheckOpen} /> <button onClick={() => checkParrentText(checkOpen)}>check text</button> <div> {open && ( <Child setOpen={setOpen} check={checkClose} setCheck={setCheckClose} /> )} </div> </> ); }; const ParentInput = ({ checkClose, setCheckOpen }) => { parentInput++; console.log("ParentInput: " + parentInput); const [parentText, setParentText] = useState(""); useEffect(() => { if (checkClose) setParentText(""); }, [checkClose]); useEffect(() => { if (parentText === "open") { setCheckOpen(true); } }, [parentText, setCheckOpen]); return ( <> <input type="text" value={parentText} onChange={e => setParentText(e.target.value)} /> </> ); }; const Child = memo(({ setOpen, check, setCheck }) => { child++; console.log("child: " + child); const checkChildText = b => { if (b) { setOpen(false); return; } alert("The text is incorrect."); }; return ( <> <ChildHeader /> <ChildInput setCheck={setCheck} /> <button onClick={() => checkChildText(check)}>check text</button> </> ); }); const ChildInput = ({ setCheck }) => { childInput++; console.log("ChildInput: " + childInput); const [childText, setChildText] = useState(""); useEffect(() => { if (childText === "close") { setCheck(true); } }, [childText, setCheck]); return ( <> <input type="text" value={childText} onChange={e => setChildText(e.target.value)} /> </> ); }; export default App;

投稿2020/01/27 07:27

編集2020/01/28 03:13
nerianighthawk

総合スコア544

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

whoiwhoi

2020/01/27 10:42

ご回答いただきありがとうございます。Reduxに挫折した経験があるのと、コンポーネントの設計にこだわりはないので、ご提示いただいた2.の方策を取りたいです。 私なりに2.の方向を目指したサンプルを質問文に追記いたしましたので、何卒ご確認くださいませ。また、そのサンプルに別の問題点がありますので、併せてコメントやご回答いただけますと幸いです。 追記のサンプルはnerianighthawk様の想定される2.の設計になっていますでしょうか。
nerianighthawk

2020/01/28 03:18

質問の修正に際して、回答も修正しました。 修正に対する補足になってしまいますが、親でフラグだけ管理してレンダリングを少なくする手法は、私もReduxを使うようになる前まではそれなりによく使っていた気がします。 上のようなソースコードではせいぜい3つのフラグでできますが、プロジェクトが大きくなるとこのフラグ管理がとても大変なので、ゆくゆくはReduxを使えるようになることをおすすめします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問