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

我们首先来讲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一个全新的值, 如果是对象, 引用就变了, 只需要比较值(对象的话是地址)就知道依赖有没有变化

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

当你在使用React时,你可能会经常遇到需要优化组件性能的情况。React.memouseMemo都是React提供的两种优化性能的方式。 React.memo是一个高阶组件(Higher Order Component),它可以优化组件的性能,使得组件只有在它的props发生改变时才会重新渲染。这种优化是通过比较前一次和当前props的浅层比较来实现的。使用React.memo时,需要将组件作为React.memo函数的参数传递,例如: ``` import React from 'react'; const MyComponent = React.memo(props => { // 组件代码 }); export default MyComponent; ``` useMemo是一个React的hook函数,它可以缓存组件中的一些计算结果,以避免重复计算。useMemo接收两个参数:一个计算函数和一个依赖数组。当依赖数组中的任何一个值发生改变时,useMemo会重新计算并返回新的值。如果依赖数组中的任何一个值都没有发生改变,则会返回上一次缓存的值。使用useMemo时,需要将计算函数作为useMemo的第一个参数传递,依赖数组作为第二个参数传递,例如: ``` import React, { useMemo } from 'react'; const MyComponent = props => { const expensiveCalculation = useMemo(() => { // 计算代码 }, [props.dependency]); // 组件代码 }; export default MyComponent; ``` 需要注意的是,React.memouseMemo都只是对组件性能进行优化的工具,并不是适用于所有情况的万能解决方案。在使用它们时,需要谨慎考虑依赖项和是否真的需要进行性能优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值