• 主页
  • 标签
  • 归档
  • 搜索
  • Github

March 23, 2021

react Hook之useMemo、useCallback及memo

本文为转载文章, 仅用于自己的知识管理收集, 如果涉及侵权,请联系 suziwen1@gmail.com,会第一时间删除
收集该文章,并非代表本人支持文中观点,只是觉得文章内容容易引起思考,讨论,有它自有的价值

转载自: https://juejin.cn/post/6844903954539626510

注意:hooks只能在函数(无状态组件)中使用

useMome、useCallback用法都差不多,都会在第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。

  1. 1const value = useMemo(fnM, [a]); 

  2. 2 

  3. 3const fnA = useCallback(fnB, [a]); 

1、memo的应用

React.memo 为高阶组件。它与React.PureComponent非常相似,但它适用于函数组件,但不适用于 class 组件。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。这与shouldComponentUpdate 方法的返回值相反。

  1. 1function MyComponent(props) { 

  2. 2 /* 使用 props 渲染 */ 

  3. 3} 

  4. 4function areEqual(prevProps, nextProps) { 

  5. 5 /* 

  6. 6 如果把 nextProps 传入 render 方法的返回结果与 

  7. 7 将 prevProps 传入 render 方法的返回结果一致则返回 true, 

  8. 8 否则返回 false 

  9. 9 */ 

  10. 10} 

  11. 11export default React.memo(MyComponent, areEqual); 

当父组件引入子组件的情况下,往往会造成组件之间的一些不必要的浪费,下面我们通过例子来了解一下场景

  1. 1const Child = (props) => { 

  2. 2 console.log('子组件?') 

  3. 3 return( 

  4. 4 <div>我是一个子组件</div> 

  5. 5 ); 

  6. 6} 

  7. 7const Page = (props) => { 

  8. 8 const [count, setCount] = useState(0); 

  9. 9 return ( 

  10. 10 <> 

  11. 11 <button onClick={(e) => { setCount(count+1) }}>加1</button> 

  12. 12 <p>count:{count}</p> 

  13. 13 <Child /> 

  14. 14 </> 

  15. 15 ) 

  16. 16} 


每次父组件更新count,子组件都会更新。如下版本使用memo,count变化子组件没有更新

  1. 1const Child = (props) => { 

  2. 2 console.log('子组件?') 

  3. 3 return( 

  4. 4 <div>我是一个子组件</div> 

  5. 5 ); 

  6. 6} 

  7. 7const ChildMemo = memo(Child); 

  8. 8 

  9. 9const Page = (props) => { 

  10. 10 const [count, setCount] = useState(0); 

  11. 11 

  12. 12 return ( 

  13. 13 <> 

  14. 14 <button onClick={(e) => { setCount(count+1) }}>加1</button> 

  15. 15 <p>count:{count}</p> 

  16. 16 <ChildMemo /> 

  17. 17 </> 

  18. 18 ) 

  19. 19} 


2、使用useCallback

当父组件传递状态给子组件的时候,memo好像没什么效果,子组件还是执行了,这时候我们就要引入hooks的useCallback、useMemo这两个钩子了。

  1. 1//子组件会有不必要渲染的例子 

  2. 2interface ChildProps { 

  3. 3 name: string; 

  4. 4 onClick: Function; 

  5. 5} 

  6. 6const Child = ({ name, onClick}: ChildProps): JSX.Element => { 

  7. 7 console.log('子组件?') 

  8. 8 return( 

  9. 9 <> 

  10. 10 <div>我是一个子组件,父级传过来的数据:{name}</div> 

  11. 11 <button onClick={onClick.bind(null, '新的子组件name')}>改变name</button> 

  12. 12 </> 

  13. 13 ); 

  14. 14} 

  15. 15const ChildMemo = memo(Child); 

  16. 16 

  17. 17const Page = (props) => { 

  18. 18 const [count, setCount] = useState(0); 

  19. 19 const [name, setName] = useState('Child组件'); 

  20. 20 

  21. 21 return ( 

  22. 22 <> 

  23. 23 <button onClick={(e) => { setCount(count+1) }}>加1</button> 

  24. 24 <p>count:{count}</p> 

  25. 25 <ChildMemo name={name} onClick={(newName: string) => setName(newName)}/> 

  26. 26 </> 

  27. 27 ) 

  28. 28} 

在上面代码基础上,父级调用子级时,在onClick参数上加上useCallback,参数为[],则第一次初始化结束后,不在改变。

  1. 1//子组件没有必要渲染的例子 

  2. 2interface ChildProps { 

  3. 3 name: string; 

  4. 4 onClick: Function; 

  5. 5} 

  6. 6const Child = ({ name, onClick}: ChildProps): JSX.Element => { 

  7. 7 console.log('子组件?') 

  8. 8 return( 

  9. 9 <> 

  10. 10 <div>我是一个子组件,父级传过来的数据:{name}</div> 

  11. 11 <button onClick={onClick.bind(null, '新的子组件name')}>改变name</button> 

  12. 12 </> 

  13. 13 ); 

  14. 14} 

  15. 15const ChildMemo = memo(Child); 

  16. 16 

  17. 17const Page = (props) => { 

  18. 18 const [count, setCount] = useState(0); 

  19. 19 const [name, setName] = useState('Child组件'); 

  20. 20 

  21. 21 return ( 

  22. 22 <> 

  23. 23 <button onClick={(e) => { setCount(count+1) }}>加1</button> 

  24. 24 <p>count:{count}</p> 

  25. 25 <ChildMemo name={name} onClick={ useCallback((newName: string) => setName(newName), []) }/> 

  26. 26 {/* useCallback((newName: string) => setName(newName),[]) */} 

  27. 27 {/* 这里使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数 

  28. 28 </> 

  29. 29 ) 

  30. 30} 

3、使用useMemo

  1. 1//子组件会有不必要渲染的例子 

  2. 2interface ChildProps { 

  3. 3 name: { name: string; color: string }; 

  4. 4 onClick: Function; 

  5. 5} 

  6. 6const Child = ({ name, onClick}: ChildProps): JSX.Element => { 

  7. 7 console.log('子组件?') 

  8. 8 return( 

  9. 9 <> 

  10. 10 <div style={{ color: name.color }}>我是一个子组件,父级传过来的数据:{name.name}</div> 

  11. 11 <button onClick={onClick.bind(null, '新的子组件name')}>改变name</button> 

  12. 12 </> 

  13. 13 ); 

  14. 14} 

  15. 15const ChildMemo = memo(Child); 

  16. 16 

  17. 17const Page = (props) => { 

  18. 18 const [count, setCount] = useState(0); 

  19. 19 const [name, setName] = useState('Child组件'); 

  20. 20 

  21. 21 return ( 

  22. 22 <> 

  23. 23 <button onClick={(e) => { setCount(count+1) }}>加1</button> 

  24. 24 <p>count:{count}</p> 

  25. 25 <ChildMemo  

  26. 26 name={{ name, color: name.indexOf('name') !== -1 ? 'red' : 'green'}}  

  27. 27 onClick={ useCallback((newName: string) => setName(newName), []) } 

  28. 28 /> 

  29. 29 </> 

  30. 30 ) 

  31. 31} 

更新属性name为对象类型,这时子组件还是一样的执行了,在父组件更新其它状态的情况下,子组件的name对象属性会一直发生重新渲染改变,从而导致一直执行,这也是不必要的性能浪费。

解决这个问题,使用name参数使用useMemo,依赖于State.name数据的变化进行更新

  1. 1interface ChildProps { 

  2. 2 name: { name: string; color: string }; 

  3. 3 onClick: Function; 

  4. 4} 

  5. 5const Child = ({ name, onClick}: ChildProps): JSX.Element => { 

  6. 6 console.log('子组件?') 

  7. 7 return( 

  8. 8 <> 

  9. 9 <div style={{ color: name.color }}>我是一个子组件,父级传过来的数据:{name.name}</div> 

  10. 10 <button onClick={onClick.bind(null, '新的子组件name')}>改变name</button> 

  11. 11 </> 

  12. 12 ); 

  13. 13} 

  14. 14const ChildMemo = memo(Child); 

  15. 15 

  16. 16const Page = (props) => { 

  17. 17 const [count, setCount] = useState(0); 

  18. 18 const [name, setName] = useState('Child组件'); 

  19. 19 

  20. 20 return ( 

  21. 21 <> 

  22. 22 <button onClick={(e) => { setCount(count+1) }}>加1</button> 

  23. 23 <p>count:{count}</p> 

  24. 24 <ChildMemo  

  25. 25 //使用useMemo,返回一个和原本一样的对象,第二个参数是依赖性,当name发生改变的时候,才产生一个新的对象 

  26. 26 name={ 

  27. 27 useMemo(()=>({  

  28. 28 name,  

  29. 29 color: name.indexOf('name') !== -1 ? 'red' : 'green' 

  30. 30 }), [name]) 

  31. 31 }  

  32. 32 onClick={ useCallback((newName: string) => setName(newName), []) } 

  33. 33 {/* useCallback((newName: string) => setName(newName),[]) */} 

  34. 34 {/* 这里使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数 

  35. 35 /> 

  36. 36 </> 

  37. 37 ) 

  38. 38} 

4、总结

在子组件不需要父组件的值和函数的情况下,只需要使用memo函数包裹子组件即可。而在使用函数的情况,需要考虑有没有函数传递给子组件使用useCallback。而在值有所依赖的项,并且是对象和数组等值的时候而使用useMemo(当返回的是原始数据类型如字符串、数字、布尔值,就不要使用useMemo了)。不要盲目使用这些hooks。

Tagged with 文章 | 转载 | 技术 | react | hooks
Time Flies, No Time for Nuts
Copyright © 2020 suziwen
Build with  Gatsbyjs  and  Sculpting theme