React Hooks性能优化指南
前言
本文主要分享一下 React Hooks 性能优化可以从哪几个方面入手
Hooks的性能问题
要想解决性能问题,关键在于组件重复渲染的处理。在使用 React Hooks
后,很多人会抱怨渲染次数变多,比如我们会把不同的数据分成多个 state
变量,每个值的变化都会触发一次渲染。
举个????
现在有个父子组件
,子组件依赖父组件传入的 name
属性,但是父组件 name
属性和 text
属性变化都会导致 Parent
函数重新执行,所以即使传入子组件 props
没有任何变化,甚至子组件没有依赖于任何 props
属性,都会导致子组件重新渲染
const Child = ((props: any) => {
console.log("公众号:前端开发爱好者 的子组件,我更新了...");
return (
<div>
<h3>子组件</h3>
<div>text:{props.name}</div>
<div>{new Date().getTime()}</div>
</div>
)
})
const Parent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState("")
const handleClick = () => {
setCount(count + 1);
}
const handleInputChange = (e) => {
setText(e.target.value)
}
return (<div>
<input onChange={handleInputChange} />
<button onClick={handleClick}>+1</button>
<div>count:{count}</div>
<Child name ={text}/>
</div>)
}
上面的代码执行你会发现,不管是触发 handleInputChange
还是触发 handleClick
,子组件都会在控制台输出 我是前端开发爱好者的子组件,我更新了... 所以即使传入子组件 props 没有任何变化
,甚至子组件没有依赖于任何 props 属性
,子组件都会重新渲染
。
想要解决重复渲染的问题,可以使用 react 的亲手制造升级的儿子,他有三个方法用来做优化,分别是 React.memo useCallback useMemo
。
React.memo : 和 class 组件时期的 PureComponent 一样,做简单额数据类型比较
useMemo : 可以用来比较复杂类型的数据,不如 Object Array 等
useCallback : 升级版本,用于控制传递函数时候控制组件是否需要更新
React.memo
使用 memo 包裹子组件时,只有 props 发生改变子组件才会重新渲染。使用 memo 可以提升一定的性能。
const Child = React.memo((props: any) => {
console.log("公众号:前端开发爱好者 的子组件,我更新了..."); // 只有当props属性改变,集name属性改变时,子组件才会重新渲染
return (
<div>
<h3>子组件</h3>
<div>text:{props.name}</div>
<div>{new Date().getTime()}</div>
</div>
)
})
const Parent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState("")
const handleClick = () => {
setCount(count + 1);
}
const handleInputChange = (e) => {
setText(e.target.value)
}
return (<div>
<input onChange={handleInputChange} />
<button onClick={handleClick}>+1</button>
<div>count:{count}</div>
<Child name ={text}/>
</div>)
}
但如果传入的 props
包含函数
,父组件每次重新渲染都是创建新的函数,所以传递函数子组件还是会重新渲染
,即使函数的内容还是一样。如何解决这一问题,我们希望把函数也缓存起来,于是引入 useCallback
useCallback
useCallback
用于缓存函数,只有当依赖项改变时,函数才会重新执行返回新的函数,对于父组件中的函数作为 props 传递给子组件时,只要父组件数据改变,函数重新执行,作为 props 的函数也会产生新的实例,导致子组件的刷新
使用
useCallback
可以缓存函数。需要搭配memo
使用
const Child = React.memo((props: any) => {
console.log("公众号:前端开发爱好者 的子组件,我更新了...");
return (
<div>
<h3>子组件</h3>
<div>text:{props.name}</div>
<div> <input onChange={props.handleInputChange} /></div>
<div>{new Date().getTime()}</div>
</div>
)
})
const Parent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState("")
const handleClick = () => {
setCount(count + 1);
}
const handleInputChange = useCallback((e) => {
setText(e.target.value )
},[])
return (<div>
<button onClick={handleClick}>+1</button>
<div>count:{count}</div>
<Child name={text} handleInputChange={handleInputChange}/>
</div>)
}
useCallback
第二个参数依赖项什么情况下使用呢,看下面的例子
//修改handleInputChange
const handleInputChange =useCallback((e) => {
setText(e.target.value + count)
},[])
以上例子 count
改变会发生什么事呢?
count
改变,但handleInputChange
不依赖与任何项,所以handleInputChange
只在初始化的时候调用一次函数就被缓存起来,当文本改变时或者count
改变时函数内部的count
始终为0
,至于为什么需要看useCallback
源码后解答。
所以需要将
count
加入到依赖项,count
变化后重新生成新的函数,改变函数内部的count
值
const handleInputChange =useCallback((e) => {
setText(e.target.value + count)
},[count])
useMemo
useMemo
使用场景请看下面这个例子,getTotal
假设是个很昂贵的操作,但该函数结果仅依赖于count
和price
,但是由于color
变化,DOM 重新渲染也会导致该函数的执行,useMemo
便是用于缓存该函数的执行结果,仅当依赖项改变后才会重新计算
const Parent = () => {
const [count, setCount] = useState(0);
const [color,setColor] = useState("");
const [price,setPrice] = useState(10);
const handleClick = () => {
setCount(count + 1);
}
const getTotal = ()=>{
console.log("getTotal 执行了 ...") // 该函数依赖于count和price,但color变化也会导致该函数的执行
return count * price
}
return (<div>
<div> 颜色: <input onChange={(e) => setColor(e.target.value)}/></div>
<div> 单价: <input value={price} onChange={(e) => setPrice(Number(e.target.value))}/></div>
<div> 数量:{count} <button onClick={handleClick}>+1</button></div>
<div>总价:{getTotal()}</div>
</div>)
}
修改后如下,注意 useMemo
缓存的是函数执行的结果
const Parent = () => {
console.log("parent 执行了...")
const [count, setCount] = useState(0);
const [color,setColor] = useState("");
const [price,setPrice] = useState(10);
const handleClick = () => {
setCount(count + 1);
}
const getTotal = useMemo(()=>{
console.log("getTotal 执行了 ...")
return count * price
},[count, price])
return (<div>
<div> 颜色: <input onChange={(e) => setColor(e.target.value)}/></div>
<div> 单价: <input value={price} onChange={(e) => setPrice(Number(e.target.value))}/></div>
<div> 数量:{count} <button onClick={handleClick}>+1</button></div>
<div>总价:{getTotal}</div>
</div>)
}
至此重复渲染的问题的解决基本上可以告一段落。
在 React 中是极力推荐函数式编程,可以让数据不可变性作为我们优化的手段。我在 React class 时代大量使用了 immutable.js
结合 redux
来搭建业务,与 React 中 PureComponnet
完美配合,性能保持非常好。但是在 React hooks
中再结合 typescript
它就显得有点格格不入了,类型支持得不是很完美。这里可以尝试一下 immer.js
,引入成本小,写法也简洁了不少。
- 上一篇 »react hooks的缺点,针对状态不同步和没有生命周期
- 下一篇 »C++性能优化指南