React Hook 四种组件优化总结
前言
React Hook 已成为当前最流行的开发范式,React 16.8 以后基于 Hook 开发极大简化开发者效率,同时不正确的使用 React Hook也带来了很多的性能问题,本文梳理基于 React Hook 开发组件的过程中如何提高性能。
组件抽取
优化前
每次点击 Increase
都会引起子组件 Child
的渲染,哪怕子组件并没有状态变化
function Before(){
console.log('Demo1 Parent')
let [count,setCount] = useState(0)
let [name,setName] = useState('-')
const handleClick = ()=>{
setCount(count+1)
}
const handleInput = (e)=>{
setName(e.target.value)
}
return (
<div>
<div className='l50'>
<label>计数器:</label>
<span className='mr10'>{count}</span>
<button className='ml10' onClick={handleClick}>Increase</button>
</div>
<div className='l50'>
<label htmlFor="">改变子组件:</label>
<input type="text" onChange={handleInput}/>
</div>
<hr />
<Child name={name}/>
</div>
)
}
// 子组件
function Child(props){
console.log('Demo1 Child')
return (
<div className='l50'>
子组件渲染:{props.name}
</div>
)
}
优化后
只需要把 Increase
抽取成独立的组件即可。此时点击按钮,子组件并不会渲染。
function Increase(){
console.log('Child Increase')
let [count,setCount] = useState(0)
const handleClick = ()=>{
setCount(count+1)
}
return (
<div>
<div className='l50'>
<label>计数器:</label>
<span className='mr10'>{count}</span>
<button className='ml10' onClick={handleClick}>Increase</button>
</div>
</div>
)
}
function After(){
console.log('Demo1 Parent')
let [name,setName] = useState('-')
const handleInput = (e)=>{
setName(e.target.value)
}
return (
<div>
<Increase/>
<div className='l50'>
<label htmlFor="">改变子组件:</label>
<input type="text" onChange={handleInput}/>
</div>
<Child name={name}/>
</div>
)
}
// 子组件
function Child(props){
console.log('Demo1 Child')
return (
<div className='l50'>
子组件渲染:{props.name}
</div>
)
}
memo 优化组件
同样基于上述优化前代码,如果不抽取组件,使用 memo
优化后,当点击按钮后,也不会触发二次渲染。
// 优化前
function AfterMemo(){
console.log('Demo1 Parent')
let [count,setCount] = useState(0)
let [name,setName] = useState('-')
const handleClick = ()=>{
setCount(count+1)
}
const handleInput = (e)=>{
setName(e.target.value)
}
return (
<div>
<div className='l50'>
<label>计数器:</label>
<span className='mr10'>{count}</span>
<button className='ml10' onClick={handleClick}>Increase</button>
</div>
<div className='l50'>
<label htmlFor="">改变子组件:</label>
<input type="text" onChange={handleInput}/>
</div>
<Child name={name}/>
</div>
)
}
// 子组件
const Child = memo((props)=>{
console.log('Demo1 Child')
return (
<div className='l50'>
子组件渲染:{props.name}
</div>
)
})
React.memo 语法
React.memo 为高阶组件,与 React.PureComponent相似。
function TestComponent(props){
// 使用 props 渲染
}
function areEqual(prevProps,nextProps){
}
export default React.memo(TestComponent,areEqual)
与 class 组件中 shouldComponentUpdate() 方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。
useCallback 优化组件
如果已经用了 memo
,当遇到下面这种场景时,同样会触发子组件渲染。比如,给 Child
绑定一个 handleClick
,子组件内部增加一个按钮,当点击子组件的按钮时,更改 count
值,即使没有发生 name
变化,也同样会触发子组件渲染,为什么?memo
不是会判断 name
变化了,才会更新吗?
function Before(){
console.log('Demo1 Parent')
let [count,setCount] = useState(0)
let [name,setName] = useState('-')
const handleClick = ()=>{
setCount(count+1)
}
const handleInput = (e)=>{
setName(e.target.value)
}
const handleChange = ()=>{
setCount(count+1)
}
return (
<div>
<div className='l50'>
<label>计数器:</label>
<span className='mr10'>{count}</span>
<button className='ml10' onClick={handleClick}>Increase</button>
</div>
<div className='l50'>
<label htmlFor="">改变子组件:</label>
<input type="text" onChange={handleInput}/>
</div>
<Child name={name} handleClick={handleChange}/>
</div>
)
}
// 子组件
const Child = memo((props)=>{
console.log('Demo1 Child')
return (
<div className='l50'>
子组件渲染:{props.name}
<button onClick={props.handleClick}>更改count</button>
</div>
)
})
并不是 memo
没有生效,是因为当状态发生变化时,父组件会从新执行,导致从新创建了新的handleChange
函数,而 handleChange
的变化导致了子组件的再次渲染。
优化后
点击父组件的Increase
按钮,更改了 count
值,经过 useCallback
包裹 handleChange
函数以后,我们会发现子组件不再渲染,说明每当父组件执行的时候,并没有创建新的 handleChange
函数,这就是通过 useCallback
优化后的效果。 即使我们点击子组件的按钮,也同样不会触发子组件的渲染,同样 count
会进行累加。
function After(){
console.log('Demo1 Parent')
let [count,setCount] = useState(0)
let text = useRef();
let [name,setName] = useState('-')
const handleClick = ()=>{
setCount(count+1)
}
const handleInput = (e)=>{
setName(e.target.value)
}
const handleChange = useCallback(()=>{
// 为了让 count 能够累加,我们使用ref 获取值
let val = parseInt(text.current.textContent);
setCount(val+1)
},[])
return (
<div>
<div className='l50'>
<label>计数器:</label>
<span className='mr10' ref={text}>{count}</span>
<button className='ml10' onClick={handleClick}>Increase</button>
</div>
<div className='l50'>
<label htmlFor="">改变子组件:</label>
<input type="text" value={name} onChange={handleInput}/>
</div>
<Child name={name} handleClick={handleChange}/>
</div>
)
}
useCallback 作用
// 用法
useCallback(()=>{
// to-do
},[])
// 示例
function App(){
// 点击按钮调用此函数,但返回被缓存
const onClick = useCallback(() => {
console.log('我被缓存了,怎么点击都返回一样');
}, []);
return (
<button onClick={onClick}>点击</button>
);
}
useCallback
接收 2 个参数,第一个为缓存的函数,第二个为依赖值- 主要用于缓存函数,第二次会返回同样的结果。
useMemo 优化
我们定义了一个total
函数,内部使用 1 填充了100次,通过 reduce
计算总和,经过测试发现点击 Increase
按钮后,只会执行 total1
,不会执行 total2
,假设total
计算量巨大,就会造成内存的浪费,通过 useMemo
可以帮我们缓存计算值。
function Before(){
console.log('Demo1 Parent')
let [count,setCount] = useState(0)
const handleClick = ()=>{
setCount(count+1)
}
const total1 = ()=>{
console.log('计算求和1')
let arr = Array.from({ length:100 }).fill(1)
return arr.reduce((prev,next)=>prev+next,0)
}
// 缓存对象值
const total2 = useMemo(()=>{
console.log('计算求和2')
let arr = Array.from({ length:100 }).fill(1)
return arr.reduce((prev,next)=>prev+next,0)
},[count])
return (
<div>
<div className='l50'>
<label>计数器:</label>
<span className='mr10'>{count}</span>
<button className='ml10' onClick={handleClick}>Increase</button>
</div>
<div>
<label>总和:</label>
<span>{total1()}</span>
<span>{total2}</span>
</div>
</div>
)
}
useMemo 语法
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- 传入一个函数进去,会返回一个
memoized
值,需要注意的是,函数内必须有返回值 - 第二个参数会依赖值,当依赖值更新时,会从新计算。
useCallback 和 useMemo 区别
他们都用于缓存,useCallback
主要用于缓存函数,返回一个 缓存后 函数,而 useMemo
主要用于缓存值,返回一个缓存后的值。
到此这篇关于React Hook 四种组件优化总结的文章就介绍到这了,更多相关React Hook 组件优化内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341