本文为转载文章, 仅用于自己的知识管理收集, 如果涉及侵权,请联系 suziwen1@gmail.com,会第一时间删除
收集该文章,并非代表本人支持文中观点,只是觉得文章内容容易引起思考,讨论,有它自有的价值
注意:hooks只能在函数(无状态组件)中使用
useMome、useCallback用法都差不多,都会在第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
- 1const value = useMemo(fnM, [a]);
- 2
- 3const fnA = useCallback(fnB, [a]);
1、memo的应用
React.memo 为高阶组件。它与React.PureComponent非常相似,但它适用于函数组件,但不适用于 class 组件。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。这与shouldComponentUpdate 方法的返回值相反。
- 1function MyComponent(props) {
- 2 /* 使用 props 渲染 */
- 3}
- 4function areEqual(prevProps, nextProps) {
- 5 /*
- 6 如果把 nextProps 传入 render 方法的返回结果与
- 7 将 prevProps 传入 render 方法的返回结果一致则返回 true,
- 8 否则返回 false
- 9 */
- 10}
- 11export default React.memo(MyComponent, areEqual);
当父组件引入子组件的情况下,往往会造成组件之间的一些不必要的浪费,下面我们通过例子来了解一下场景
- 1const Child = (props) => {
- 2 console.log('子组件?')
- 3 return(
- 4 <div>我是一个子组件</div>
- 5 );
- 6}
- 7const Page = (props) => {
- 8 const [count, setCount] = useState(0);
- 9 return (
- 10 <>
- 11 <button onClick={(e) => { setCount(count+1) }}>加1</button>
- 12 <p>count:{count}</p>
- 13 <Child />
- 14 </>
- 15 )
- 16}
- 1const Child = (props) => {
- 2 console.log('子组件?')
- 3 return(
- 4 <div>我是一个子组件</div>
- 5 );
- 6}
- 7const ChildMemo = memo(Child);
- 8
- 9const Page = (props) => {
- 10 const [count, setCount] = useState(0);
- 11
- 12 return (
- 13 <>
- 14 <button onClick={(e) => { setCount(count+1) }}>加1</button>
- 15 <p>count:{count}</p>
- 16 <ChildMemo />
- 17 </>
- 18 )
- 19}
2、使用useCallback
当父组件传递状态给子组件的时候,memo好像没什么效果,子组件还是执行了,这时候我们就要引入hooks的useCallback、useMemo这两个钩子了。
- 1//子组件会有不必要渲染的例子
- 2interface ChildProps {
- 3 name: string;
- 4 onClick: Function;
- 5}
- 6const Child = ({ name, onClick}: ChildProps): JSX.Element => {
- 7 console.log('子组件?')
- 8 return(
- 9 <>
- 10 <div>我是一个子组件,父级传过来的数据:{name}</div>
- 11 <button onClick={onClick.bind(null, '新的子组件name')}>改变name</button>
- 12 </>
- 13 );
- 14}
- 15const ChildMemo = memo(Child);
- 16
- 17const Page = (props) => {
- 18 const [count, setCount] = useState(0);
- 19 const [name, setName] = useState('Child组件');
- 20
- 21 return (
- 22 <>
- 23 <button onClick={(e) => { setCount(count+1) }}>加1</button>
- 24 <p>count:{count}</p>
- 25 <ChildMemo name={name} onClick={(newName: string) => setName(newName)}/>
- 26 </>
- 27 )
- 28}
在上面代码基础上,父级调用子级时,在onClick参数上加上useCallback,参数为[],则第一次初始化结束后,不在改变。
- 1//子组件没有必要渲染的例子
- 2interface ChildProps {
- 3 name: string;
- 4 onClick: Function;
- 5}
- 6const Child = ({ name, onClick}: ChildProps): JSX.Element => {
- 7 console.log('子组件?')
- 8 return(
- 9 <>
- 10 <div>我是一个子组件,父级传过来的数据:{name}</div>
- 11 <button onClick={onClick.bind(null, '新的子组件name')}>改变name</button>
- 12 </>
- 13 );
- 14}
- 15const ChildMemo = memo(Child);
- 16
- 17const Page = (props) => {
- 18 const [count, setCount] = useState(0);
- 19 const [name, setName] = useState('Child组件');
- 20
- 21 return (
- 22 <>
- 23 <button onClick={(e) => { setCount(count+1) }}>加1</button>
- 24 <p>count:{count}</p>
- 25 <ChildMemo name={name} onClick={ useCallback((newName: string) => setName(newName), []) }/>
- 26 {/* useCallback((newName: string) => setName(newName),[]) */}
- 27 {/* 这里使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数
- 28 </>
- 29 )
- 30}
3、使用useMemo
- 1//子组件会有不必要渲染的例子
- 2interface ChildProps {
- 3 name: { name: string; color: string };
- 4 onClick: Function;
- 5}
- 6const Child = ({ name, onClick}: ChildProps): JSX.Element => {
- 7 console.log('子组件?')
- 8 return(
- 9 <>
- 10 <div style={{ color: name.color }}>我是一个子组件,父级传过来的数据:{name.name}</div>
- 11 <button onClick={onClick.bind(null, '新的子组件name')}>改变name</button>
- 12 </>
- 13 );
- 14}
- 15const ChildMemo = memo(Child);
- 16
- 17const Page = (props) => {
- 18 const [count, setCount] = useState(0);
- 19 const [name, setName] = useState('Child组件');
- 20
- 21 return (
- 22 <>
- 23 <button onClick={(e) => { setCount(count+1) }}>加1</button>
- 24 <p>count:{count}</p>
- 25 <ChildMemo
- 26 name={{ name, color: name.indexOf('name') !== -1 ? 'red' : 'green'}}
- 27 onClick={ useCallback((newName: string) => setName(newName), []) }
- 28 />
- 29 </>
- 30 )
- 31}
更新属性name为对象类型,这时子组件还是一样的执行了,在父组件更新其它状态的情况下,子组件的name对象属性会一直发生重新渲染改变,从而导致一直执行,这也是不必要的性能浪费。
解决这个问题,使用name参数使用useMemo,依赖于State.name数据的变化进行更新
- 1interface ChildProps {
- 2 name: { name: string; color: string };
- 3 onClick: Function;
- 4}
- 5const Child = ({ name, onClick}: ChildProps): JSX.Element => {
- 6 console.log('子组件?')
- 7 return(
- 8 <>
- 9 <div style={{ color: name.color }}>我是一个子组件,父级传过来的数据:{name.name}</div>
- 10 <button onClick={onClick.bind(null, '新的子组件name')}>改变name</button>
- 11 </>
- 12 );
- 13}
- 14const ChildMemo = memo(Child);
- 15
- 16const Page = (props) => {
- 17 const [count, setCount] = useState(0);
- 18 const [name, setName] = useState('Child组件');
- 19
- 20 return (
- 21 <>
- 22 <button onClick={(e) => { setCount(count+1) }}>加1</button>
- 23 <p>count:{count}</p>
- 24 <ChildMemo
- 25 //使用useMemo,返回一个和原本一样的对象,第二个参数是依赖性,当name发生改变的时候,才产生一个新的对象
- 26 name={
- 27 useMemo(()=>({
- 28 name,
- 29 color: name.indexOf('name') !== -1 ? 'red' : 'green'
- 30 }), [name])
- 31 }
- 32 onClick={ useCallback((newName: string) => setName(newName), []) }
- 33 {/* useCallback((newName: string) => setName(newName),[]) */}
- 34 {/* 这里使用了useCallback优化了传递给子组件的函数,只初始化一次这个函数,下次不产生新的函数
- 35 />
- 36 </>
- 37 )
- 38}
4、总结
在子组件不需要父组件的值和函数的情况下,只需要使用memo函数包裹子组件即可。而在使用函数的情况,需要考虑有没有函数传递给子组件使用useCallback。而在值有所依赖的项,并且是对象和数组等值的时候而使用useMemo(当返回的是原始数据类型如字符串、数字、布尔值,就不要使用useMemo了)。不要盲目使用这些hooks。