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
sum1
とsum2
の場合、内容は全く同じですが、互いに等しいチェックをすると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
関数は、レンダリングごとに新しい関数を生成します.つまり、機能面では同じですが、それぞれが異なる関数です!(このように構成部品に新しい関数を作成するコストは高くありません.したがって、パフォーマンスの面でレンダリングごとに関数を作成し続けるのは大きな問題ではありません.)ただし、関数は同じでなければならない場合があります.
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);
リスト上のアイテムが多いため、不要な再レンダリングにはコストがかかります.それを防ぐために、MyBigList
をReact.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}
/>
);
}
両親はMyBigList
をterm
とonItemClick
フロプスに渡し、onItemClick
は関数です.ここで、
onItemClick
は、useCallback
を介して同じ関数オブジェクトとして保持される.したがって、term
が再生成されない限り、不要な再レンダリングは発生しない.onItemClick
がonItemClick
によって記憶されていない場合、useCallback
がレンダリングされるたびに新しいMyParent
が生成され、onItemClick
がレンダリングされる.結局、MyBigList
でReact.memo
をかばう意味は消えてしまう.最適化のためにコードが追加されているため、MyBigList
をかばわないほうがいい.Reference
この問題について(TIL:38,React:useMemo/useCallbackはmojo?), 我々は、より多くの情報をここで見つけました https://velog.io/@tonyk0901/TIL38-React-useMemo-useCallback-이-mojoテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol