[react]反応技術-第11章素子性能の最適化


10章で作成したtodo-appでデータが急増した場合、アプリケーションは遅くなるでしょう.パフォーマンスを最適化する前に、ラックに大量のデータを表示してみてください(lag).App.jsを以下のように修正することにより、2500個のデータを生成する関数がtodosに設定される.
に注意useState(createBulkTodos())回レンダリングするたびに関数が呼び出されます.
関数は、最初のレンダーuseState(createBulkTodos)でのみ呼び出されます.
//...
import TodoList from './components/TodoList';

function createBulkTodos() {
  const array = [];
  for (let i = 1; i <= 2500; i++) {
    array.push({
      id: i,
      text: `할 일 ${i}`,
      checked: false,
    });
  }
  return array;
}

const App = () => {
  const [todos, setTodos] = useState(createBulkTodos);

  const nextId = useRef(2501);

  const onInsert = useCallback(
//...

確かに遅くなったように見えます.でも原因は何ですか?
次の場合、構成部品は再レンダリングされます.
1.自分が受け取ったprops変更の場合
2.自分のstateが変わったとき
3.親構成部品を再レンダリングする場合
4.forceUpdate関数を実行する場合
この場合、項目をチェックするとstateが変更され、App要素が再レンダリングされるので、サブ要素TodoList要素も再レンダリングされ、その中の無数の要素も再レンダリングされます.チェックされていない構成部品も再レンダリングする必要がないので、このような状況を回避するために最適化する必要があります.

React.memoを使用して構成部品のパフォーマンスを最適化する


構成部品の再レンダリングを防止するには、shouldComponentUpdateというライフサイクルを使用します.しかし、それを使用できない関数型については、どうすればいいのでしょうか.
この場合に使用できるのはReact.memo関数です.構成部品のpropsが変更されていない場合は、これを再レンダリングしないように設定して、関数型構成部品の再レンダリング性能を最適化できます.
  • TodoListItem.js
  • import React from 'react';
    import {
        MdCheckBoxOutlineBlank,
        MdCheckBox,
        MdRemoveCircleOutline,
    } from 'react-icons/md';
    import cn from 'classnames';
    import './TodoListItem.scss';
    
    const TodoListItem = ({ todo, onRemove, onToggle }) => {
      (...)
    };
    
    export default React.memo(TodoListItem);
    TodoListItem要素をReact.memo()にカプセル化し、todoonRemoveonToggleを変更せずにレンダリングしないようにする.ただし,現在の項目ではtodos配列を更新するとonRemoveonToggle関数も更新されるため,React.memoでは不十分である.
    1.useStateの関数更新setTodosを使用する場合、新しい状態をパラメータとするよりも、関数型更新と呼ばれる状態更新の方法を定義する更新関数を加えることもできる.
    // 사용 예시 
    const [number, setNumber] = useState(0);
    const onIncrease = useCallback(
    	() => setNumber(prevNumber => prevNumber + 1),
      	[],
    );
    setNumber(number+1)ではなく、上記のコードのように更新方法を定義する更新関数を加える場合、useCallbackで使用する場合、2番目のパラメータ配列にnumberを入れる必要はありません.このように彼に和弦を変えてあげます.
    2.useReducer使用
    関数型更新の代わりにuseReducerを用いることも、onToggleおよびonRemoveが絶えず更新する問題を解決することができる.
  • App.js
  • import React, { useReducer, useRef, useCallback }from 'react';
    
    ...
    function todoReducer(todos, action) {
      switch (action.type) {
        case 'INSERT': // 새로 추가
          // { type: ‘INSERT‘, todo: { id: 1, text: ‘todo‘, checked: false } }
          return todos.concat(action.todo);
        case 'REMOVE': // 제거
          // { type: ‘REMOVE‘, id: 1 }
          return todos.filter(todo => todo.id !== action.id);
        case 'TOGGLE': // 토글
          // { type: ‘REMOVE‘, id: 1 }
          return todos.map(todo =>
            todo.id === action.id ? { ...todo, checked: !todo.checked } : todo,
          );
        default:
          return todos;
      }
    }
    
    const App = () => {
      const [todos, dispatch] = useReducer(todoReducer, undefined, createBulkTodos);
    
      const nextId = useRef(2501);
    
      const onInsert = useCallback( text => {
          const todo = {
            id: nextId.current,
            text,
            checked: false,
          };
          dispatch({ type: 'INSERT', todo });
          nextId.current +=1;
        }, []);
    
      const onRemove = useCallback( id => {
          dispatch({ type: 'REMOVE', id });
        }, []);
    
      const onToggle = useCallback( id => {
        dispatch({ type: 'TOGGLE', id });
        }, []);
    
      return(
    ...
    
    useReducerを使用する場合は、元の2番目のパラメータに初期状態を加えます.2番目のパラメータにはundefinedが加えられ、3番目のパラメータには初期状態を生成する関数createBulkTodosが加えられる.createBulkTodos関数は、構成部品が最初にレンダリングされた場合にのみ呼び出されます.useReducerを使用する方法の欠点は、多くの既存のコードを修正する必要があることであるが、その利点は、更新状態の論理を素子の外に集中させることができることである.

    react-仮想化によるレンダリングの最適化


    2500個の初期データを登録しましたが、実際の画面には9つの項目しかありません.残りはスクロールして見る必要があります.スクロールする前に非表示の構成部品をプリレンダリングするのは非効率といえます.react-virtualizedを使用すると、リスト要素をスクロールする前に表示されない要素をレンダリングせずに、そのサイズのみを占めることができます.react-virtualizedで提供されるListコンポーネントを使用して最適化するには、px単位で各プロジェクトの実際のサイズを事前に理解する必要があります.これは開発者ツールで確認できます.(やるべきこと1上枠がないので下に量る2)
  • TodoList.js
  • import React, { useCallback } from 'react';
    import { List } from 'react-virtualized';
    import TodoListItem from './TodoListItem';
    import './TodoList.scss';
    
    const TodoList = ({ todos, onRemove, onToggle }) => {
        const rowRenderer = useCallback(
            ({ index, key, style }) => {
                const todo = todos[index];
                return (
                    <TodoListItem
                        todo={todo}
                        key={key}
                        onRemove={onRemove}
                        onToggle={onToggle}
                        style={style}
                    />
                );
            },
            [onRemove, onToggle, todos],
        );
        
        
        return (
            <List
                className="TodoList"
                width={512} // 전체 크기
                height={513} // 전체 높이
                rowCount={todos.length} // 항목 개수
                rowHeight={57} // 항목 높이
                rowRenderer={rowRenderer} // 항목을 렌더링할 때 쓰는 함수
                list={todos} // 배열
                style={{ outline: 'none' }} // List에 기본 적용되는 outline 스타일 제거
            />
        );
    };
    
    export default React.memo(TodoList);
    List要素を使用するには、rowRendererという関数を書き直す必要があります.この関数は、react-virtualizedList要素上で各TodoItemをレンダリングするために使用されます.この関数は、List要素のpropsに設定する必要があります.この関数は、パラメータのindex, key, style値をオブジェクトタイプとして使用します.Listコンポーネントを使用する場合、このリストの全体的なサイズ、各アイテムの高さ、各アイテムをレンダリングする際に使用する関数、および配列をpropsに配置すると、渡されたpropsを使用して自動的に最適化できます.
  • TodoListItem.js
  • ...
    const TodoListItem = ({ todo, onRemove, onToggle, style }) => {
        const { id, text, checked } = todo;
    
        return (
            <div className="TodoListItem-virtualized" style={style}>
                <div className="TodoListItem">
    		...
                </div>
            </div>
        );
    };
    ...
    render関数の既存のコンテンツをdivで囲み、divTodoListItem-virtualizedに設定し、classNameで受信したpropsを使用する.
  • style既存の&+&、&:nth-child(偶数)を使用して、異なる背景色を提供するコードをクリアし、コードの上部に次のコードを挿入します.
  • .TodoListItem-virtualized {
        & + & {
            border-top: 1px solid #dee2e6;
        }
        &:nth-child(even) {
            background: #f8f9fa;
        }
    }