最適化のアイデア
2つの主な最適化の方向性があります。
- 再レンダリングの数を減らします。Reactで最も重い(そして最も長い)部分は再結合(単にdiffとして理解される)であるため、レンダリングしない場合、再結合はありません。
- 計算量を減らします。主な目的は、繰り返される計算を減らすことです。機能コンポーネントの場合、各レンダリングは関数呼び出しを最初から再実行します。
クラスコンポーネントを使用する場合、使用されるReact最適化APIは主にshouldComponentUpdateとPureComponentです。
では、機能コンポーネントでは、パフォーマンスの最適化をどのように行うのでしょうか。最適化するには、主に次の方法を使用します
- React.memo
- useCallback
- useMemo
React.memo
例を参照してください。
親コンポーネントにボタンを配置して、サブタイトルを変更し、子サブコンポーネントを導入します
ご覧のとおり、初めてアクセスすると、サブコンポーネントはconsole.log('I am a subcomponent')を出力します。
クリックしてサブタイトルを変更すると、子サブコンポーネントも印刷されるため、レンダリング時間が不必要に繰り返されます。
//父组件
import {useState} from 'react'
import Child from "./Child";
const Index = ()=>{
const [subTitle, setSubTitle] = useState('我是子标题')
const updateSubTitle = ()=>{
setSubTitle('修改子标题')
}
return (
<div>
<div>函数式组件性能优化</div>
<div>{subTitle}</div>
<button onClick={updateSubTitle}>修改子标题</button>
<Child/>
</div>
);
}
export default Index;
//子组件Child.js
const Child = ()=>{
console.log('我是子组件')
return (
<div>我是子组件</div>
)
}
export default Child
复制代码
最適化し、React.memoを使用して子コンポーネントをラップします
import React from "react";
const Child = ()=>{
console.log('我是子组件')
return (
<div>我是子组件</div>
)
}
export default React.memo(Child)
复制代码
もう一度観察して、子サブコンポーネントが繰り返しレンダリングされていないことを確認します。
useCallback
ここで再度モデル化し、子サブコンポーネントにonclickイベントを追加し、[サブタイトルの変更]ボタンをクリックすると、子サブコンポーネントが再レンダリングされていることがわかります。これは主に、handlerClick関数が再レンダリングするためです。サブタイトルを変更すると変更され、子コンポーネントが再レンダリングされます
// 父组件
const Index = ()=>{
const [subTitle, setSubTitle] = useState('我是子标题')
const updateSubTitle = ()=>{
setSubTitle('修改子标题')
}
const handlerClick = ()=>{
console.log('子组件点击')
}
return (
<div>
<div>函数式组件性能优化</div>
<div>{subTitle}</div>
<button onClick={updateSubTitle}>修改子标题</button>
<Child onClick={handlerClick}/>
</div>
);
}
// Child子组件
const Child = (props)=>{
console.log('我是子组件')
return (
<div>
<div>我是子组件</div>
<button onClick={props.onClick}>子组件按钮</button>
</div>
)
}
export default React.memo(Child)
复制代码
最適化し、useCallbackを使用してサブコンポーネントのhandlerClick関数をラップし、updateSubTitleをもう一度クリックしてサブタイトルを変更し、子サブコンポーネントが再レンダリングされないことを確認します。
// 父组件
const Index = ()=>{
const [subTitle, setSubTitle] = useState('我是子标题')
const updateSubTitle = ()=>{
setSubTitle('修改子标题')
}
const handlerClick = useCallback(()=>{
console.log('子组件点击')
},[])
return (
<div>
<div>函数式组件性能优化</div>
<div>{subTitle}</div>
<button onClick={updateSubTitle}>修改子标题</button>
<Child onClick={handlerClick}/>
</div>
);
}
export default Index;
复制代码
useCallbackの使用法は次のとおりです
const callback = () => {
doSomething(a, b);
}
const memoizedCallback = useCallback(callback, [a, b])
复制代码
関数と依存関係をパラメーターとしてuseCallbackに渡すと、コールバック関数のメモ化されたバージョンが返されます。このmemoizedCallbackは、依存関係が変更された場合にのみ更新されます。
useMemo
計算結果のキャッシュにuseMemo
我们先看个例子,在之前基础上添加一个calcCount计算函数,然后点击updateSubTitle更新子标题,发现calcCount重新计算了,也就是每次渲染都会造成重复计算,如果是计算量比较大的情况下,会极大的影响性能
// 父组件
const Index = ()=>{
const [subTitle, setSubTitle] = useState('我是子标题')
const updateSubTitle = ()=>{
setSubTitle('修改子标题')
}
const handlerClick = useCallback(()=>{
console.log('子组件点击')
},[])
const calcCount = ()=>{
let totalCount = 0
for(let i=0;i<10000;i++){
totalCount+=i
}
console.log('totalCount',totalCount)
return totalCount
}
const count = calcCount()
return (
<div>
<div>函数式组件性能优化</div>
<div>{subTitle}</div>
<button onClick={updateSubTitle}>修改子标题</button>
<div>count:{count}</div>
<Child onClick={handlerClick}/>
</div>
);
}
复制代码
优化一下,使用useMemo缓存计算结果,我们再次点击updateSubTitle修改子标题按钮,可以发现calcCount函数不再重复计算
const calcCount = ()=>{
let totalCount = 0
for(let i=0;i<10000;i++){
totalCount+=i
}
console.log('totalCount',totalCount)
return totalCount
}
const count = useMemo(calcCount,[])
复制代码
最后,需要注意的是不能盲目的使用useMemo,要根据具体的场景,比如对于一个数据计算量比较大,那么使用是比较适用的,而对于普通的一些值得计算,可以不使用,因为本身useMemo也是会消耗一些性能,盲目使用反而会适得其反
参考阅读
文章最后
本文作者阿健Kerry,高级前端工程师,转载请注明出处。如果觉得本文对你有帮助,记得点赞三连哦,也可以扫码关注我新建立的前端技术公众号【有你前端】,之后我所有文章会同步发到这个公众号上面。另外,我建了一个可以帮助咱们程序员脱单的公众号,每周都会推送几个优秀的单身小姐姐,如果你是程序员技术好又正好是单身,那你可以下面扫码关注【缘来你是程序猿】公众号开启你的脱单之旅。