React Memo 和 React useMemo 和 useCallback的简单用法

  • Post author:
  • Post category:其他


我们首先来讲useMemo的用法


useMemo


首先,说起这个 我们肯定要知道 在class的声明组件中

shouldComponentUpdate

shouldComponentUpdate(nextProps, nextState)

使用shouldComponentUpdate()以让React知道当前状态或属性的改变是否不影响组件的输出,默认返回

ture

,返回

false

时不会重写render,而且该方法并不会在初始化渲染或当使用**forceUpdate()**时被调用,我们要做的只是这样:

shouldComponentUpdate(nextProps, nextState) {


return nextState.someData !== this.state.someData

}

当然上面的知识 class 中的api 控制方法,

相信写过React的都知道 智能组件 和 傻瓜组件的两个名词吧,

没错,傻瓜组件就是 我们class中的

React.PureComponent

React.PureComponent通过props和state的浅对比来实现 shouldComponentUpate()。所以我们只需要往组件中传入需要显示在页面上的值就可以了。(

React.memo

useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,

useMemo返回缓存的变量



useCallback返回缓存的函数


React.memo的用法

import React, { useState, } from 'react';
import Child1 from './Child1';
import Child2 from './Child2 ';

export default (props = {}) => {
    const [step, setStep] = useState(0);
    const [val, setValue] = useState('');
    
    const handleSetStep = () => {
        setStep(step + 1);
    }

	const  handleChange = (e) => {
		setValue(e.target.value);
	}


    return (
        <div>
            <button onClick={handleSetStep}>step is : {step} </button>
     		<input onChange={(e) => handleChange(e)} />
            <hr />
            <Child1 step={step} count={count}  /> 
        </div>
    );
}

这里,只要是我的input 输入发生改变,就会触发react的set state 导致re-render 方法发生渲染,就会导致

Child1

发生渲染,倒是不必要的性能浪费, 我只想要

step

改变才触发子组件,所以用法就是

import React, { useState } from 'react';
import Child1 from './Child1';
import Child2 from './Child2 ';

export default (props = {}) => {
    const [step, setStep] = useState(0);
    const [val, setValue] = useState('');
    
    const handleSetStep = () => {
        setStep(step + 1);
    }

	const  handleChange = (e) => {
		setValue(e.target.value);
	}
    // 改变之后
	const ChildMedo = React.memo(() => {
	 	return <Child1 step={step} count={count}  /> 
	});
    return (
        <div>
            <button onClick={handleSetStep}>step is : {step} </button>
     		<input onChange={(e) => handleChange(e)} />
            <hr />
           <ChildMedo />
        </div>
    );
}


注意事项

不要坑了子组件
我们做一个点击累加的按钮作为父组件,那么父组件每次点击后都会刷新:

function App() {
  const [count, forceUpdate] = useState(0);
  const schema = { b: 1 };

  return (
    <div>
      <Child schema={schema} />
      <div onClick={() => forceUpdate(count + 1)}>Count {count}</div>
    </div>
  );
}
另外我们将 schema = { b: 1 } 传递给子组件,这个就是埋的一个大坑。

子组件的代码如下:

const Child = memo(props => {
  useEffect(() => {
    console.log("schema", props.schema);
  }, [props.schema]);

  return <div>Child</div>;
});
只要父级 props.schema 变化就会打印日志。结果自然是,父组件每次刷新,子组件都会打印日志,也就是 子组件 [props.schema] 完全失效了,因为引用一直在变化。

其实 子组件关心的是值,而不是引用,所以一种解法是改写子组件的依赖:

const Child = memo(props => {
  useEffect(() => {
    console.log("schema", props.schema);
  }, [JSON.stringify(props.schema)]);

  return <div>Child</div>;
});

当然React.memo() 还有第二个参数,来判断是否进行页面的刷新

React官网链接

在这里插入图片描述


useMemo的用法

export default function useMemoDemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
    
    // 使用useMemo的话 当input的值改变了,func 还是拿到之前的缓存的值,这样子就节省了性能的再次计算的开销
    const func= useMemo(() => {
       let result = Math.random() * count;
        return result;
    }, [count]);
 
    return <div>
        <h4>{count}-{func}</h4>
        {val}
        <div>
            <button onClick={() => setCount(count + 1)}>+c1</button>
            <input value={val} onChange={event => setValue(event.target.value)}/>
        </div>
    </div>;
}


useCallback 的用法

function Counter() {
  const [count, setCount] = useState(0);

  const getCount= useCallback(() => {
    return "count=" + count;
  }, [count]);

  useEffect(() => {
    getCount();
  }, [getCount]);

  return <h1>{count}</h1>;
}

可以看到,useCallback 也有第二个参数 – 依赖项,我们将 getCount函数的依赖项通过 useCallback 打包到新的 getCount函数中,那么 useEffect 就只需要依赖 getCount这个函数,就实现了对 count 的间接依赖。

我们来对比一下 React的

useCallback

和 class中的

componentDidUpdate

class Parent extends Component {
  state = {
    count: 0,
    step: 0
  };
  
  fetchData = () => {
    const url =
      "https://count=" + this.state.count + "&step=" + this.state.step;
  };
  render() {
    return <Child fetchData={this.fetchData} count={count} step={step} />;
  }
}

class Child extends Component {
  state = {
    data: null
  };
  componentDidMount() {
    this.props.fetchData();
  }
  componentDidUpdate(prevProps) {
    if (
      this.props.count !== prevProps.count ||
      this.props.step !== prevProps.step 
    ) {
      this.props.fetchData();
    }
  }
  render() {
    // ...
  }
}

上面的代码经常用 Class Component 的人应该很熟悉,因此在 componentDidUpdate 时,判断这两个参数

count


step

发生了变化就触发重新取数。

如果父级函数 fetchData 不是我写的,在不读源码的情况下,我怎么知道它依赖了 props.count 与 props.step 呢?更严重的是,如果某一天 fetchData 多依赖了 params 这个参数,下游函数将需要全部在 componentDidUpdate 覆盖到这个逻辑,否则 params 变化时将不会重新取数。可以想象,这种方式维护成本巨大,甚至可以说几乎无法维护。

换成 Function Component 的思维吧!试着用上刚才提到的

useCallback

解决问题:

function Parent() {
  const [ count, setCount ] = useState(0);
  const [ step, setStep ] = useState(0);

  const fetchData = useCallback(() => {
    const url =
      "https://count=" + this.state.count + "&step=" + this.state.step;
  }, [count, step])

  return (
    <Child fetchData={fetchData} />
  )
}

function Child(props) {
  useEffect(() => {
    props.fetchData()
  }, [props.fetchData])

  return (
    // ...
  )
}

可以看出来,当 fetchData 的依赖变化后,按下保存键,

eslint-plugin-react-hooks

会自动补上更新后的依赖,子组件只需要关心传递过来的

fetchData

就可以了,至于这个函数依赖了什么,已经封装在 useCallback 后打包透传下来了。

还有 useCallback 如果想一直获取最新的值,下面的这个方法,将会导致 callback 一直变化,就会很影响性能

import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        return "http://val="+val;
    }, [val]);
    return <div>
        <Child callback={callback}/>
        <div>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}
 
function Child({ callback }) {
    const [val, setVal] = useState(() => callback());
    useEffect(() => {
        setVal(callback());
    }, [callback]);
    return <div>
        {val}
    </div>
}

我们可以用过useRef来解决

import React, { useState, useCallback, useEffect,useRef } from 'react';
function Parent() {
    const [val, setVal] = useState('');
    const textRef = useRef();

   
  useLayoutEffect(() => {
    //这个是等待 dom发生改变后 把值赋值给 **textRef**
    textRef.current = val;    
  });

  // 之所以传入 textRef 这个的作用是 textRef 是不会变化的,但是子组件仍然可以拿到最新的值
  // 节约了性能的开销
  const callback = useCallback(() => {
    const currentText = textRef.current; 
    console.log(currentText);
  }, [textRef]);
  
    return <div>
        <Child callback={callback}/>
        <div>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}
 
function Child({ callback }) {
    const [val, setVal] = useState(() => callback());
    useEffect(() => {
        setVal(callback());
    }, [callback]);
    return <div>
        {val}
    </div>
}


useCallback

的依赖是只比较值的, 如果是对象, 就是只比较引用

而textRef是一直存在不会销毁的跨生命周期对象, 引用一直不变, 相当于, useCallback的依赖为[]

为什么只比较引用呢, 因为useCallback/useEffect的依赖里基本都该是useState的返回值, 而每次调用setState都会赋给state一个全新的值, 如果是对象, 引用就变了, 只需要比较值(对象的话是地址)就知道依赖有没有变化

如果有任何问题 欢迎评论指出,谢谢



版权声明:本文为hl971115原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。