TIL:38,React:useMemo/useCallbackはmojo?


useMemo()次のアプリケーションを想定します.
import { useState } from 'react';
import styled from 'styled-components';

function App() {
  const [number, setNumber] = useState('');
  const [numberList, setNumberList] = useState([]);

  const handleChange = (e) => {
    setNumber(e.target.value);
  };

  const addToList = () => {
    setNumber('');
    setNumberList([...numberList, Number(number)]);
  };

  const getSum = (list) => {
    console.log('합계 계산중...');
    return list.reduce((acc, curr) => acc + curr, 0);
  };

  return (
    <AppContainer>
      <input type="number" onChange={handleChange} value={number} />
      <button onClick={addToList}>리스트에 추가</button>
      <ul>
        <span>숫자 리스트</span>
        {numberList.map((el, index) => {
          return <li key={index}>{el}</li>;
        })}
      </ul>
      <div>{`합계는 : ${getSum(numberList)}`}</div>
    </AppContainer>
  );
}

export default App;
これは簡単なページで、数字を入力する入力ウィンドウがあり、入力するたびにボタンを押すと、リストに入力した数字が追加されます.数値を追加すると、リストの数値の和が計算されて表示されます.
InputはonChangeイベントを検出し、そのイベントが発生するたびにsetNumberを実行する.setNumberによって再レンダリングされ、毎回getSum関数が実行され、合計が計算されます.
ただし、getSumの結果値は、ボタンをクリックして入力した数値をリストに含めるまで変化しません.すなわち,ページの構造上,同じ変化のない実行を繰り返す.
私たちの例では、これは簡単な関数なので問題ありませんが、コストの高い関数を実行すると、多くのコンピュータリソースが浪費されます.
したがって,listが変化した場合にのみ累積関数を実行するように最適化する必要がある.
いろいろな方法があります.useEffectによる方法の一例がある.
import { useState, useEffect } from 'react';
import styled from 'styled-components';

function App() {
  const [number, setNumber] = useState('');
  const [numberList, setNumberList] = useState([]);
  const [sum, setSum] = useState(0);

  const handleChange = (e) => {
    setNumber(e.target.value);
  };

  const addToList = async () => {
    setNumber('');
    setNumberList([...numberList, Number(number)]);
  };

  useEffect(() => {
    setSum(getSum(numberList));
  }, [numberList]);

  const getSum = (list) => {
    console.log('합계 계산중...');
    return list.reduce((acc, curr) => acc + curr, 0);
  };

  return (
    <AppContainer>
      <input type="number" onChange={handleChange} value={number} />
      <button onClick={addToList}>리스트에 추가</button>
      <ul>
        <span>숫자 리스트</span>
        {numberList.map((el, index) => {
          return <li key={index}>{el}</li>;
        })}
      </ul>
      <div>{`합계는 : ${sum}`}</div>
    </AppContainer>
  );
}

export default App;
加算関数は、useEffectにおいて実行され、依存配列には[list]が含まれる.したがって、関数の実行がlistで変化した場合にのみ発生する.
しかしこれを行うには総数を含むstateを追加生成する必要がある.
新しいものを追加しなければ、上記の最適化を実現するために既存のもののみを使用し、useMemoフックを使用することができる.
ここで、Memoとは、Memoizationを意味する.
In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. 
Memoizationは、コストの高い関数に結果値(キャッシュ)を予め保存し、同じ入力があれば保存した値を返し、関数を再実行するのではなく速度を向上させる最適化技術です.
我々の例では、ボタンを押してリストに数字を入れる前に、平均計算に入る入力量は同じであるため、結果値を保存した後(入力量が同じ場合)、保存した値を返すとより効果的である.useMemoの使い方は以下の通りです.const memoizedValue = useMemo(()=> {},[]);1番目のパラメータはmemorizationを適用する関数を受け入れ,2番目のパラメータは依存配列である.
たとえば、
const getSum = (list) => {
    console.log(‘합계 계산중...');
    return list.reduce((acc, curr) => acc + curr, 0);
}
   
const memoizedValue = useMemo(() => getSum(numberList), [numberList]);
useMemoの合計の関数を第1のパラメータとし、依存配列にgetSumの関数のパラメータであるnumberListを含めると、numberListが変化した場合にのみgetSumの関数の評価が実行され、numberListが変化しない場合には、関数を再実行せずに保存した値を返します.useCallback()(以下、このブログページの翻訳です.)
FunctionEquality Checkについて
まず、関数について、等式検査の方法を理解する必要があります.
function factory() {
  return (a, b) => a + b;
}

const sum1 = factory();
const sum2 = factory();

sum1(1, 2); // => 3
sum2(1, 2); // => 3

sum1 === sum2; // => false
sum1 === sum1; // => true
sum1sum2の場合、内容は全く同じですが、互いに等しいチェックをするとfalseが出力されます.
jsでは関数もオブジェクトなので、生成時に異なるメモリアドレスがあります.したがって、2つの関数が同じ内容を持っているが、独立して生成されている場合、Equality checkではfalseとなります.
useCallback()を使用する目的
反応器では,コードの内容が同じだが異なる関数がよく見られる.
たとえば、
import React from 'react';

function MyComponent() {
  // handleClick is re-created on each render
  const handleClick = () => {
    console.log('Clicked!');
  };
  // ...
}
hancleClick関数は、レンダリングごとに新しい関数を生成します.つまり、機能面では同じですが、それぞれが異なる関数です!(このように構成部品に新しい関数を作成するコストは高くありません.したがって、パフォーマンスの面でレンダリングごとに関数を作成し続けるのは大きな問題ではありません.)
ただし、関数は同じでなければならない場合があります.
  • 関数が他のhookの依存配列に含まれる場合、
  • (第1の概念と同様)関数は、React.memoによって囲まれた要素に伝達される.
  • あります.すなわち,関数は比較の対象となる.
    たとえば、関数がuseEffectの依存配列に含まれ、同じ関数を保持しない場合、各レンダリングで関数が再生成されるため、関数は変化し続けるため、常に使用効果が得られます.すなわち,依存性配列に関数を加える意味がなくなる.
    この場合、useCallbackフックを使用できます.useCallbackフックは、レンダリングごとに新しい関数を作成する必要はなく、既存の関数を再利用することができます.したがって,関数は一定に保たれ,依存配列に含まれることが意味される.
    たとえば、
    import React, { useCallback } from 'react';
    
    function MyComponent() {
      // handleClick is the same function object
      const handleClick = useCallback(() => {
        console.log('Clicked!');
      }, []);
      // ...
    }
    上記の場合、MyComponentがレンダリングされると、handleClickは常に同じ関数オブジェクトである.

    コンポーネントが長いアイテムのリストをレンダリングするとします.
    import React from 'react';
    import useSearch from './fetch-items';
    
    function MyBigList({ term, onItemClick }) {
      const items = useSearch(term);
    
      const map = item => <div onClick={onItemClick}>{item}</div>;
    
      return <div>{items.map(map)}</div>;
    }
    
    export default React.memo(MyBigList);
    リスト上のアイテムが多いため、不要な再レンダリングにはコストがかかります.それを防ぐために、MyBigListReact.memoに囲みます.React.memoもまた、useMemo/useCallbackに類似する概念を最適化するためである.propsに渡される値を観察します.props値が変化しない場合は、再レンダリングを回避できます.再レンダリングはprops値が変化した場合にのみ行います.MyBigListの親コンポーネントを以下に示します.
    import React, { useCallback } from 'react';
    
    export default function MyParent({ term }) {
      const onItemClick = useCallback(event => {
        console.log('You clicked ', event.currentTarget);
      }, [term]);
    
      return (
        <MyBigList
          term={term}
          onItemClick={onItemClick}
        />
      );
    }
    両親はMyBigListtermonItemClickフロプスに渡し、onItemClickは関数です.
    ここで、onItemClickは、useCallbackを介して同じ関数オブジェクトとして保持される.したがって、termが再生成されない限り、不要な再レンダリングは発生しない.onItemClickonItemClickによって記憶されていない場合、useCallbackがレンダリングされるたびに新しいMyParentが生成され、onItemClickがレンダリングされる.結局、MyBigListReact.memoをかばう意味は消えてしまう.最適化のためにコードが追加されているため、MyBigListをかばわないほうがいい.