teratail header banner
teratail header banner
質問するログイン新規登録
TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

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

Q&A

1回答

1744閲覧

Reactでインプット要素を持つ 大量のリストがあった時の再レンダリング防止策について

keisei-001

総合スコア15

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

React.js

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

0グッド

0クリップ

投稿2022/03/01 23:32

編集2022/03/02 00:30

0

0

前提・実現したいこと

インプット要素を持つ 大量のリストがあり、こちらの入力を行うと入力をしたインプット要素だけでなく、他の入力を行っていないインプット要素も再レンダリングをされてしまう状態です。
こちらをどうにか入力を行ったインプット要素だけを再レンダリングさせるようにできないかな?と考えているのですが、いい方法はないでしょうか?
なお、親のコンポーネントのインプット要素を入力を行った時の再レンダリング対策はReact.memoやuseCallbackで対応済みです。

参考画像

イメージ説明

参考MOVIE

https://gyazo.com/a8ba2cd0e58e98c305f0fcb76d5a2472

該当のソースコード

TypeScript

1// Page.tsx 2import React, { useCallback, useEffect, useState } from 'react'; 3 4import { Users } from '@/components/organisms/Users'; 5 6import { PageLayout } from '../templates'; 7 8type User = { 9 id: number; 10 name: string; 11 inputName: string; 12}; 13 14const Page: React.FC = () => { 15 const sampleUsers = [...Array(100)].map((index) => ({ 16 id: index + 1, 17 name: `name${index + 1}`, 18 inputName: '', 19 })); 20 21 const [input, setInput] = useState<string>(''); 22 const [users, setUsers] = useState<User[]>([]); 23 24 const handleInput = useCallback( 25 (newInput: string, index: number) => { 26 const copyusers = [...users]; 27 copyusers[index].inputName = newInput; 28 setUsers(copyusers); 29 }, 30 [users] 31 ); 32 33 useEffect(() => { 34 setUsers(sampleUsers); 35 }, []); 36 37 return ( 38 <PageLayout> 39 親のインプット 40 <input 41 type="text" 42 style={{ 43 border: '1px solid black', 44 }} 45 value={input} 46 onChange={(e) => setInput(e.target.value)} 47 /> 48 <Users {...{ users, handleInput }} /> 49 </PageLayout> 50 ); 51}; 52 53export default Page; 54 55

TypeScript

1// Users.tsx 2import React from 'react'; 3 4import { User } from '@/components/organisms/User'; 5 6type User = { 7 id: number; 8 name: string; 9 inputName: string; 10}; 11 12type UsersProps = { 13 users: User[]; 14 handleInput: (newInput: string, index: number) => void; 15}; 16 17export const Users: React.FC<UsersProps> = React.memo( 18 ({ users, handleInput }): JSX.Element => { 19 console.log('ここはUsersです'); 20 return ( 21 <ul style={{ marginTop: '100px' }}> 22 {users.map((user, index) => { 23 return <User key={index} {...{ index, handleInput, user }} />; 24 })} 25 </ul> 26 ); 27 } 28); 29 30Users.displayName = 'UsersComponent'; 31

TypeScript

1// User.tsx 2import React from 'react'; 3 4type User = { 5 id: number; 6 name: string; 7 inputName: string; 8}; 9 10type Props = { 11 user: User; 12 index: number; 13 handleInput: (newInput: string, index: number) => void; 14}; 15export const User: React.FC<Props> = React.memo( 16 ({ user, index, handleInput }): JSX.Element => { 17 console.log('ここはuserのindexは', index); 18 19 return ( 20 <li key={user.id}> 21 Userコンポーネント 22 <input 23 style={{ 24 margin: '10px', 25 border: '1px solid black', 26 }} 27 type="text" 28 onChange={(e) => { 29 handleInput(e.target.value, index); 30 }} 31 value={user.inputName} 32 /> 33 inputName:{user.inputName} 34 </li> 35 ); 36 }, 37 (prevProps: Props, nextProps: Props) => { 38 const prevInputName = prevProps.user; 39 const nextInputName = nextProps.user; 40 // ここで前の状態で比較をしても変わっていない状態になってしまう。。 41 return prevInputName === nextInputName; 42 } 43); 44 45User.displayName = 'UserComponent'; 46

試したこと

User.tsxの中にinput要素があり、ここでReact.memoの第二引数を使って前のPropsの状態と今のPropsの状態を比較できると調べて、それを実施してみたが、うまく動作をしなくなってしまう状態です(こちらの第二引数をなくせば動きます)

補足情報(FW/ツールのバージョンなど)

"react": "^17.0.1"
"typescript": "^4.0.3"

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

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

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

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

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

guest

回答1

0

メモ化が効果を発揮するしない以前の問題として、useMemomapのループの中に入れてはいけません。

Hooksは描画のたびに同じ回数だけ呼び出さないといけないので、usersの個数が変わればエラーとなってしまいます(おまけに、「userが変わればメモ化を破棄する」という性質上、メモ化の効果もゼロどころか、むしろ悪影響していることすら考えられます)。

投稿2022/03/02 00:24

maisumakun

総合スコア146663

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

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

maisumakun

2022/03/02 00:27

あと、handleInputが外からusersを取る構造も妥当ではありません(usersが変化するたびにhandleInputも変化して、メモ化が無意味となります)。 setUsersに関数を渡すと、その引数として元の値が来ますので、そちらを使いましょう。外側のusersを参照しなければ、handleInputのインスタンスも同じままにできます。
keisei-001

2022/03/02 01:05

@maisumakun 色々と見ていただきありがとうございます。 > メモ化が効果を発揮するしない以前の問題として、useMemoをmapのループの中に入れてはいけません。 ご指摘ありがとうございます。こちら検証で仕込んでいたものの削除忘れでしたので編集し修正いたしました。 > usersが変化するたびにhandleInputも変化して、メモ化が無意味となります)。 こちら私の不勉強で申し訳ないのですが、useCallbackで第二引数にusersを渡しており、usersが変更されたときhandleInputは再生成される(されるべき?)なのかなと思っていたのですが、こちらについては誤った認識でしたでしょうか? > setUsersに関数を渡すと、その引数として元の値が来ますので、そちらを使いましょう。外側のusersを参照しなければ、handleInputのインスタンスも同じままにできます。 こちらについてもご指摘いただきありがとうございます。 下記のような形で修正を行っているのですが、イメージあっていますでしょうか? ``` const handleInput = useCallback( (newInput: string, index: number) => { setUsers((prevUsers) => { const copyUsers = [...prevUsers]; copyUsers[index].inputName = newInput; return copyUsers; }); }, [users] // ここの依存関係がないと動作しなくなる ); ```
maisumakun

2022/03/02 01:12

> usersが変更されたときhandleInputは再生成される(されるべき?)なのかなと思っていたのですが、こちらについては誤った認識でしたでしょうか? はい、子コンポーネントを再描画したくないのなら、usersの再作成=「1つでも要素が書き換われば全部」再描画、というのは求めるものではないですよね? > 下記のような形で修正を行っているのですが 依存関係の[users]は外して空配列にしてください。
keisei-001

2022/03/02 04:16

> はい、子コンポーネントを再描画したくないのなら、usersの再作成=「1つでも要素が書き換われば全部」再描画、というのは求めるものではないですよね? そうですね。。おっしゃる通りでした。 なお、依存配列を空配列にすると各インプット要素自体が動作をしなくなってしまうようです。。
maisumakun

2022/03/02 04:46 編集

あとは、Userコンポーネントmemo時の比較関数を外してみてはどうでしょうか?
keisei-001

2022/03/02 05:11

たびたびありがとうございます。 比較関数自体もはずしたんですが依存配列にusersはないとやはりダメのようです。 今外出してしまっており、動作検証が出来ないのですが、また帰宅後に調査してみます。
keisei-001

2022/03/02 07:10

inputのある列でそれぞれの更新されていないuser.tsxを更新させないようにしたいが、元のusersのstateが更新されるから再レンダリングは避けられないという気がしてきました。
maisumakun

2022/03/02 08:03 編集

> 元のusersのstateが更新されるから再レンダリングは避けられないという気がしてきました。 そんなことはないです。変更されていないUserに渡す値は同じインスタンスを継続することが可能です。
maisumakun

2022/03/02 08:05

> なお、依存配列を空配列にすると各インプット要素自体が動作をしなくなってしまうようです。。 別の問題があってここでトラブっているような印象を受けます。
keisei-001

2022/03/02 11:44 編集

> そんなことはないです。変更されていないUserに渡す値は同じインスタンスを継続することが可能です。 なるほど、ちょっとこのあたり理解ができていないので改めて学習いたします。 > 別の問題があってここでトラブっているような印象を受けます。 更新されていない原因と言っていいかわかりませんが、User.tsxのReact.memoを外すことで、handleInputの依存配列を空配列にして更新はできました。ただ、再レンダリングは未だに起きているようです。 またReact.memoを外すとUser.tsxが以前の状態を認識できずに再レンダリングを防ぐのは難しそうだと思いました。なので対応方法が良くなかったかもしれないです。
maisumakun

2022/03/02 12:34

sampleUsersもインスタンスが変化するので、そのあたりでなにか変な挙動になっていることも考えられます。 初期値はuseEffectでのセットではなく、useStateの引数として与えましょう(関数にすれば最初1回しか呼ばれないです)。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.30%

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

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

質問する

関連した質問