React.memo()、useCallback()、useMemo()区别及基本使用

共 6656字,需浏览 14分钟

 ·

2021-11-19 20:55

来源 | https://www.fly63.com/


先来看个简单的例子
// Parent.jsximport react, { useState } from 'react';import Child from '../Child';
function Parent() { const [parentCount, setParentCount] = useState(0); console.log('父組件重新渲染--------------'); return ( <div style={{ background: 'lightseagreen' }}> <Child /> <button type="button" onClick={() => { setParentCount(parentCount + 1); }}>父组件 +1</button> </div> );}
export default Parent;


// Child.jsximport React from 'react';
function Child() { console.log('------------子組件重新渲染'); return ( <div style={{ background: 'pink', margin: '50px 0' }}> <button type="button">子組件</button> </div> );}
export default Child;
当点击父组件按钮时,父组件的状态父组件会被更新,导致这些父组件重渲染,子组件也会重渲染;而此时我们的子组件和父组件之间并没有依赖关系,因此重复渲染是优化掉的,可以使用React.memo 包裹子组件
// Child.jsximport React from 'react';// ...other codeexport default React.memo(Child);

React.memo(Comp[, fn])

用于减少子组件的渲染

React.memo 是一个高阶组件(参数为组件,返回的是新组件的函数即为高阶组件)

对外部来说,React.memo 会检查道具的变更,只有当核心的道具发生变化时组件才会重新成型,纽扣我们再点击父组件,子就不会膨胀了。

React.memo 对复杂对象做浅层对比,可以通过过程第二个参数来控制对比

第二个参数为一个增强渲染细节的函数。

function MyComponent(props) {  /* 使用 props 渲染 */}function areEqual(prevProps, nextProps) {  /*  如果把 nextProps 传入 render 方法的返回结果与  将 prevProps 传入 render 方法的返回结果一致则返回 true,  否则返回 false  */}export default React.memo(MyComponent, areEqual);

useMemo(fn[, DependentArray])

用于每次组件重新渲染时重复进行复杂的计算,参数为一个函数和依赖项数组,返回函数和函数的调用结果。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo作用类似于VUE的计算(计算属性),不同之处在于需要手动传入依赖项,依赖当项变更时会重新调用传入的函数,返回计算值。
依赖项为空数组时则直接返回上次的计算结果。
不依赖项时,每次组件刷新都会重新计算,应该在代码能正常运行的情况下将其作为一种优化策略策略。
修改下我们的例子,请注意这里包裹了子组件,保证测试时子组件重重渲染受重球的道具变化的影响。
// Parent.jsximport React, { useState, useMemo } from 'react';import Child from '../Child';
function Parent() { const [parentCount, setParentCount] = useState(0); const [otherCount, setOtherCount] = useState(0); console.log('父組件重新渲染--------------');
// 一个复杂的计算 const computedFn = (a, b) => { console.log('----重新执行了计算----'); return a + b; };
const computedValue = useMemo(() => { return computedFn(parentCount, 1); }, [parentCount]);
return ( <div style={{ background: 'lightseagreen' }}> <Child parentCount={parentCount} computedValue={computedValue} /> <button type="button" onClick={() => { setParentCount(parentCount + 1); }}>父组件 +1</button> <button type="button" onClick={() => { setOtherCount(otherCount + 1); }}>父组件 otherCount+1</button> </div> );}
点击第一个按钮,依赖项更改,输出重执行了计算,点击第二个按钮,更改更改的不是计算值,因为依赖项,因此不会重计算,组件也不会重渲染。

useCallback(fn[, DependentArray])

需要传递给子组件的函数,连接子组件的重复渲染,参数为一个函数和任选的依赖项数组,返回出入函数的记忆版本。
// Parent.jsximport React, { useState } from 'react';import Child from '../Child';
function Parent() { const [parentCount, setParentCount] = useState(0); const [otherCount, setOtherCount] = useState(0); console.log('父組件重新渲染--------------');
const computedFn = () => { return parentCount + 1; };
return ( <div style={{ background: 'lightseagreen' }}> <Child parentCount={parentCount} computedFn={computedFn} /> <button type="button" onClick={() => { setParentCount(parentCount + 1); }}>父组件 +1</button> <button type="button" onClick={() => { setOtherCount(otherCount + 1); }}>父组件 otherCount+1</button> </div> );}
export default Parent;
// Child.jsximport React from 'react';
function Child(props) { const { computedValue, computedFn } = props; console.log('------------子組件重新渲染'); return ( <div style={{ background: 'pink', margin: '50px 0' }}> <div> 父组件传入的计算结果: {computedValue} </div> <button type="button" onClick={computedFn}>子組件</button> </div> );}
export default React.memo(Child);
当点击第二个按钮时,子组件就可以重新渲染
给computedFn 加上useCallBack
// Parent.jsximport React, { useState, useCallback } from 'react';
// ...other code
const computedFn = useCallback(() => { console.log(parentCount); return parentCount + 1; }, [parentCount]) ;
// ...other code
export default Parent;
一次再点击父组件第二个按钮子组件,子组件不会变大,使用Callback的依赖项没有变化,返回的是上一次渲染的函数,因此组件子组件的道具没有变,组件不会重渲染。
需要是,被useCallback保存的函数内部作用域也不会发生变化,因此,当项数组为注意的时候,依赖useCallback的函数的内部通过闭包取的组件内的要素值始终不变。
import React, { useState, useCallback } from 'react';import Child from '../Child';
let a = 0;function Parent() { const [parentCount, setParentCount] = useState(0); const [otherCount, setOtherCount] = useState(0); console.log('父組件重新渲染--------------');
const computedFn = useCallback(() => { // 依赖项为空,这里的打印值始终不变; // 因为组件state变化时会重新渲染整个组件,而这里parentCount取的始终是第一次渲染版本的值 console.log(parentCount); // 这里的打印值会实时更新,因为变量直接定义在组件外部,不受组件重新渲染影响 console.log(a); return parentCount + 1; }, []) ;
return ( <div style={{ background: 'lightseagreen' }}> <Child parentCount={parentCount} computedFn={computedFn} /> <button type="button" onClick={() => { setParentCount(parentCount + 1); a += 1; }}>父组件 +1</button> <button type="button" onClick={() => { setOtherCount(otherCount + 1); }}>父组件 otherCount+1</button> </div> );}
export default Parent;
因为useCallback目的是减少子组件重渲染,所以需要搭配子组件的shouldComponentUpdate或React.memo一起使用优化意义。
以上是依赖项更改不希望的情况,当依赖项更改的时候,使用回调的记忆效果就不好,可以使用引用依赖项解决。
function Form() {  const [text, updateText] = useState('');  const textRef = useRef();
useEffect(() => { textRef.current = text; // 把它写入 ref });
const handleSubmit = useCallback(() => { // ref 对象在组件的整个生命周期内保持不变 // 从 ref 读取它,current的变更不会引起组件的重新渲染,而函数内部又能拿到正确的值 const currentText = textRef.current; alert(currentText); }, [textRef]);
return ( <> <input value={text} onChange={e => updateText(e.target.value)} /> <ExpensiveTree onSubmit={handleSubmit} /> </> );}

使用引用

看看官方介绍

const refContainer = useRef(initialValue);

useRef 返回一个附属的 ref 对象,其 .current 被初始化为粒子的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变

可以理解为:使用引用创建的对象拥有当前的属性,这个像个盒子,啥可能存,包括DOM节点;返回的引用对象在组件的整个生命周期内保持不变,即存在当前的值组件模糊渲染影响,始终保持着一开始的引用;同时属性的修改也不会触发组件的渲染;这个属性的最终效果是useRef的参数
看看官方例子
function TextInputWithFocusButton() {  const inputEl = useRef(null);  const onButtonClick = () => {    // `current` 指向已挂载到 DOM 上的文本输入元素    inputEl.current.focus();  };  return (    <>      <input ref={inputEl} type="text" />      <button onClick={onButtonClick}>Focus the input</button>    </>  );}
当把useRef创建的对象传给DOM元素的ref属性时,将动态DOM元素的引用存入当前属性,这样就可以通过ref对象直接操作DOM元素了。

学习更多技能

请点击下方公众号

浏览 41
点赞
评论
1收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
1收藏
分享

手机扫一扫分享

分享
举报