今までの‘useCallback’の使用姿勢は間違っています.
11039 ワード
gitHubノートから整理する.
一、落とし穴:
関数コンポーネントを使用する時、しばしばいくつかの内部関数を定義しますが、これは関数コンポーネントの性能に影響すると思います. Aree Hook s slow because of creating functions in render?;
classより軽い関数コンポーネントとHOC、renderPropsなどの追加の階層を回避したおかげで、関数コンポーネントの性能はそこまで差がないです.
実は 二、 レンダリングのたびに内部関数 は、さらに、サブアセンブリ
3.1
3.2 JS内部関数の作成は非常に速いです。これは性能の問題ではありません。
上の例です.もしサブアセンブリが時間がかかると、問題が露呈します.
3.2.1 How to read an offten-change value from useCallback?
最適化の考え方:サブアセンブリ は、 は、 は、 は、 は、 3.2.2 は、 stateソリューション
React公式推奨は
一、落とし穴:
useCallback
は、関数構成要素の多すぎる内部関数による性能問題を解決するためのものである.関数コンポーネントを使用する時、しばしばいくつかの内部関数を定義しますが、これは関数コンポーネントの性能に影響すると思います.
useCallback
はこの問題を解決するものだと思っていますが、そうではないです.classより軽い関数コンポーネントとHOC、renderPropsなどの追加の階層を回避したおかげで、関数コンポーネントの性能はそこまで差がないです.
useCallback
を使用すると、追加の性能をもたらします.追加のdeps
変更判断が追加されたためです.useCallback
も、内部関数の再作成の問題を解決するわけではない.よく見ると、useCallback
を使用するかどうかにかかわらず、内部関数の再作成は避けられない.export default function Index() {
const [clickCount, increaseCount] = useState(0);
// `useCallback`,
const handleClick = () => {
console.log('handleClick');
increaseCount(clickCount + 1);
}
// `useCallback`, `useCallback`
const handleClick = useCallback(() => {
console.log('handleClick');
increaseCount(clickCount + 1);
}, [])
return (
{clickCount}
)
}
useCallback
解決された問題useCallback
は、実はmemoize
を利用して、不必要なサブアセンブリの再レンダリングを低減するものである.import React, { useState, useCallback } from 'react'
function Button(props) {
const { handleClick, children } = props;
console.log('Button -> render');
return (
)
}
const MemoizedButton = React.memo(Button);
export default function Index() {
const [clickCount, increaseCount] = useState(0);
const handleClick = () => {
console.log('handleClick');
increaseCount(clickCount + 1);
}
return (
{clickCount}
Click
)
}
React.memo
を使用してButton
コンポーネントを修正しても、【Click】btnをクリックするたびに、Button
コンポーネントがレンダリングされることになる.Index
コンポーネントstateが変化し、コンポーネントが再レンダリングされる.handleClick
を再作成し、Button
もレンダリングされる.useCallback
を使用して最適化する:import React, { useState, useCallback } from 'react'
function Button(props) {
const { handleClick, children } = props;
console.log('Button -> render');
return (
)
}
const MemoizedButton = React.memo(Button);
export default function Index() {
const [clickCount, increaseCount] = useState(0);
// `useCallback`
const handleClick = useCallback(() => {
console.log('handleClick');
increaseCount(clickCount + 1);
}, [])
return (
{clickCount}
Click
)
}
三、useCallback
の問題3.1
useCallback
の実パラメータ関数によって読み取られる変数は変化する(一般的にstate、propsから).export default function Index() {
const [text, updateText] = useState('Initial value');
const handleSubmit = useCallback(() => {
console.log(`Text: ${text}`); // BUG:
}, []);
return (
<>
updateText(e.target.value)} />
useCallback(fn, deps)
>
)
}
input
値を変更し、handleSubmit
の処理関数は依然として初期値を出力する.useCallback
の実パラメータ関数で読み取られた変数が変化している場合は、依存配列に記入してください.export default function Index() {
const [text, updateText] = useState('Initial value');
const handleSubmit = useCallback(() => {
console.log(`Text: ${text}`); //
}, [text]); // `text`
return (
<>
updateText(e.target.value)} />
useCallback(fn, deps)
>
)
}
問題が解決されましたが、プログラムは一番良くないです.input入力ボックスの変化が頻繁すぎるので、useCallback
の存在意義は必要ないです.3.2 JS内部関数の作成は非常に速いです。これは性能の問題ではありません。
上の例です.もしサブアセンブリが時間がかかると、問題が露呈します.
// :ExpensiveTree `React.memo` ,
const ExpensiveTree = React.memo(function (props) {
console.log('Render ExpensiveTree')
const { onClick } = props;
const dateBegin = Date.now();
// , ,
while(Date.now() - dateBegin < 600) {}
useEffect(() => {
console.log('Render ExpensiveTree --- DONE')
})
return (
,
)
});
export default function Index() {
const [text, updateText] = useState('Initial value');
const handleSubmit = useCallback(() => {
console.log(`Text: ${text}`);
}, [text]);
return (
<>
updateText(e.target.value)} />
>
)
}
問題:input値を更新して、カートンを比較することを発見しました.3.2.1 How to read an offten-change value from useCallback?
最適化の考え方:
useRef
の無効な再レンダリングを避けるためには、親コンポーネントre−renderのときのExpensiveTree
の属性値が不変であることを保証しなければならない.handleSubmit
属性値が不変である場合にも、最新のstateにアクセスできるようにする.export default function Index() {
const [text, updateText] = useState('Initial value');
const textRef = useRef(text);
const handleSubmit = useCallback(() => {
console.log(`Text: ${textRef.current}`);
}, [textRef]);
useEffect(() => {
console.log('update text')
textRef.current = text;
}, [text])
return (
<>
updateText(e.target.value)} />
>
)
}
原理:handleSubmit
は、以前の直接依存handleSubmit
によってtext
になりました.re-renderの度にtextRef
は不変であるため、textRef
は不変です.handleSubmit
の更新毎にtext
を更新する.このようにtextRef.current
は変わらないが、handleSubmit
を介して最新の値にアクセスすることもできる.textRef
+useRef
このような解決方法は、固定された「モード」を形成することができる.export default function Index() {
const [text, updateText] = useState('Initial value');
const handleSubmit = useEffectCallback(() => {
console.log(`Text: ${text}`);
}, [text]);
return (
<>
updateText(e.target.value)} />
>
)
}
function useEffectCallback(fn, dependencies) {
const ref = useRef(null);
useEffect(() => {
ref.current = fn;
}, [fn, ...dependencies])
return useCallback(() => {
ref.current && ref.current(); // ref.current
}, [ref])
}
useEffect
を介して変化の値を保持し、useRef
を介して変化の値を更新する.useEffect
を介して固定されたコールバックを返す.useCallback
ソリューションconst ExpensiveTreeDispatch = React.memo(function (props) {
console.log('Render ExpensiveTree')
const { dispatch } = props;
const dateBegin = Date.now();
// , ,
while(Date.now() - dateBegin < 600) {}
useEffect(() => {
console.log('Render ExpensiveTree --- DONE')
})
return (
{ dispatch({type: 'log' })}}>
,
)
});
function reducer(state, action) {
switch(action.type) {
case 'update':
return action.preload;
case 'log':
console.log(`Text: ${state}`);
return state;
}
}
export default function Index() {
const [text, dispatch] = useReducer(reducer, 'Initial value');
return (
<>
dispatch({
type: 'update',
preload: e.target.value
})} />
>
)
}
原理:useReducer
はdispatch
を持っています.re-renderの時には変化がありません.memoize
関数で最新のreducer
を取得することができる.React公式推奨は
context
を介してcalback方式を伝える代わりにprops
を使用する.上記の例をcontext
リレーcallback
関数に変更しました.function reducer(state, action) {
switch(action.type) {
case 'update':
return action.preload;
case 'log':
console.log(`Text: ${state}`);
return state;
}
}
const TextUpdateDispatch = React.createContext(null);
export default function Index() {
const [text, dispatch] = useReducer(reducer, 'Initial value');
return (
dispatch({
type: 'update',
preload: e.target.value
})} />
)
}
const ExpensiveTreeDispatchContext = React.memo(function (props) {
console.log('Render ExpensiveTree')
// `context` `dispatch`
const dispatch = useContext(TextUpdateDispatch);
const dateBegin = Date.now();
// , ,
while(Date.now() - dateBegin < 600) {}
useEffect(() => {
console.log('Render ExpensiveTree --- DONE')
})
return (
{ dispatch({type: 'log' })}}>
,
)
});