React组件的缓存

3,988 阅读6分钟

React缓存组件的方式

前言,正常来讲的话当我们点击组件的时候,该组件以及该组件的子组件都会重新渲染,但是如何避免子组件重新渲染呢,我们经常用memo来解决,但是这次我不仅仅告诉你们memo缓存而且props.children也可以缓存组件。让我们一起来看看吧

示例一 React.memo

const Index = ()=> {
  console.log('子组件刷新了');
  return (
    <div>
      这是子组件
    </div>
  )
}
//这里我们用react.memo对组件进行包裹,包裹一次之后react在render的过程中不会给该fiber打上更新的tag
//从而跳过更新,这个原理其实就是react.memo的第二个参数上,如果react.memo第二个参数不传递,react回默
//认给我们补充上第二个参数的逻辑,其中逻辑就是浅比较Index组件的props参数,如果相等的话默认第二个参数返
//回true,组件就会缓存了,如果不相等的话就会返回false组件就会重新打上更新的tag然后重新渲染。
const MemoIndex = React.memo(Index);

const App = ()=>{
  const [state, setState] = useState(0);
  return (
    <div className="App">
      <button onClick={()=>setState(state+1)}>点我看看子组件刷新了吗</button>
      <MemoIndex/>
    </div>
  );
}

下面这个示例再巩固一下刚才我们说的React.memo缓存组件的原理,并且手写React.memo的第二个参数。

const Index = ()=> {
  console.log('子组件刷新了');
  return (
    <div>
      这是子组件
    </div>
  )
}
//其实这Index组件不会更新的,react检测到我们不传递第二个参数的话,会把之前的props拆出来,和现在的
//props做比较 发现pre.next === cur.next 然后回返回true组件就会缓存了
const MemoIndex = React.memo(Index);
//这行代码就相当这样的代码
const MemoIndex = React.memo(Index, (pre, cur)=> {
    //这样写的就比较简单了,因为这是是针对于当前的demo来说的。react比较的代码逻辑比较复杂,因为react
    //需要考虑到多种情况,props中参数可能多一个少一个的情况,所以react默认提供的代码比较复杂
    if(pre.name === cur.name) {
        return true;
    }
    return false;
});

const App = ()=>{
  const [state, setState] = useState(0);
  return (
    <div className="App">
      <button onClick={()=>setState(state+1)}>点我看看子组件刷新了吗</button>
      <MemoIndex name={0}/>
    </div>
  );
}

遇到一些复杂的问题什么时候渲染什么时候不渲染我们应该按照自己的逻辑去写比较函数,而不是直接交给react帮我们做了

注意引用地址的比较📢

下面一个示例

const Index = ()=> {
  console.log('子组件刷新了');
  return (
    <div>
      这是子组件
    </div>
  )
}
const MemoIndex = React.memo(Index);

const App = ()=>{
  const [state, setState] = useState(0);
  const func = ()=> {

  };
  return (
    <div className="App">
        //这时候子组件会不会刷新呢,有的同学可能说不会因为浅比较发现pre.func === cur.func 返回
        //true所以不会刷新,但是其实是会刷新的,因为APP组件中触发了setState之后App组件重新渲染,也
        //就是相当于执行了App()这个方法,所以里面func的指向地址发生了变化,所以pre.func !== 
        //cur.func  子组件会重新渲染,
      <button onClick={()=>setState(state+1)}>点我看看子组件刷新了吗</button>
      <MemoIndex func={func}/>
    </div>
  );
}

这样子情况我们怎么处理呢?我们看一下解决方案。

const Index = ()=> {
  console.log('子组件刷新了');
  return (
    <div>
      这是子组件
    </div>
  )
}

const MemoIndex = React.memo(Index);

const App = ()=>{
  const [state, setState] = useState(0);
  //这里我们用了useCallback,useCallback主要是缓存我们当前的函数,如果我们第二个参数传递空数组的话
  //他的地址不会改变,如果我们第二个参数传递的是一个变量,这个变量发生变化他的地址就会发生变化。所以这
  //和useEffect的第二个参数是一样的,但是请注意不要滥用useCallback的第二个参数。如果第二个参数滥用
  //会拿到我们之前的值。我们看下一个示例就知道了
  const func = useCallback(()=> {

  }, [])
  return (
    <div className="App">
      <button onClick={()=>setState(state+1)}>点我看看子组件刷新了吗</button>
      <MemoIndex func={func}/>
    </div>
  );
}

我们介绍了结合useCallback与memo缓存组件但同时不要滥用useCallback哈。

不要滥用useCallback📢

const Index = (props)=> {
  console.log('子组件刷新了');
  return (
    <div>
        //点击这个按钮之前先点击App组件下面的按钮,让state变大,然后在点击这个按钮看看state是啥
        //我们发现state一直是一个0。这是为什么呢,因为很简单我们之前讲了useCallback第二个和
        //useEffect的第二个参数是一样的,因为我们传递的是空数组说以useCallback一直拿到的是最原始的
        //值,所以会造成这个问题,我们写代码的时候千万要注意第二个参数,只要useCallback需要什
        //值我们就在第二个参数传递什么值,这样才可以确保我们拿到的是最新的值。同样的里面如果不需要一些
        //参数的话我们也不要把这些参数加到第二个参数上面否则会出现func的地址多次改变。
      <button onClick={props.func}>点我看看state是啥</button>
      这是子组件
    </div>
  )
}
const MemoIndex = React.memo(Index);

const App = ()=>{
  const [state, setState] = useState(0);
  const func = useCallback(()=> {
    console.log(state);
  }, [])
  return (
    <div className="App">
      <button onClick={()=>setState(state+1)}>点我看看子组件刷新了吗</button>
      <MemoIndex func={func}/>
    </div>
  );
}

React.memeo缓存组件已经讲完了,下面就说一下用useMemo如何缓存组件,让我们一起来看看吧。

useMemo缓存组件

const Index = (props)=> {
  console.log('子组件刷新了');
  return (
    <div>
      这是子组件
    </div>
  )
}

const App = ()=>{
  const [state, setState] = useState(0);
  //使用useMemo也要和useCallback一样特别注意第二个参数,因为他有可能导致我们拿不到最新的数据解决
  //解决方案就和useCallback的一样,简单来说套用react官方的话就是请确保数组中包含了所有外部作用域
  //中会随时间变化并且在useMemo中使用的变量都要放到第二个参数中。
  const Component = useMemo(()=><Index/>, []);
  return (
    <div className="App">
      <button onClick={()=>setState(state+1)}>点我看看子组件刷新了吗</button>
      {
        Component
      }
    </div>
  );
}

以上就是用React.memo和useMemo缓存组件,接下来我们讲一个90%的人不知道react缓存组件的方式

props.children缓存组件

这个缓存组件是我在看react-router源码时候发现的,react-router源码的话我打算下次出个专辑讲讲react源码的一些原理。接下来让我一起看看props.children如何缓存组件的。


const Index = (props)=> {
  const [state, setState] = useState(0);
  return (
    <div>
       //当这个按钮点击之后我们发现Children组件并不重新刷新了,其实原理我理解的是react帮我们做了一层
       //处理当渲染前与渲染后两个组件的引用地址一样他就会放弃render,当然这是我的猜测,这个的话之后
       //我看到源码的时候会和大家讲一下在补充一下。
      <button onClick={()=>setState(state+1)}>点我我看看子组件刷新不刷新</button>
      {props.children}
    </div>
  )
};
const Children = ()=> {
  return (
    <div>
      {console.log('子组件刷新了')}
      这是children组件
    </div>
  )
}

const App = ()=>{
  return (
    <div className="App">
      <Index>
        <Children/>
      </Index>
    </div>
  );
}

这就是props.children缓存组件的方式

结尾,像类组件缓存的组件的话像生命周期方法shouldcomponentupdate和pureCommeent我就不讲了,因为类组件大家写的也少,都是祖传代码才用类组件,这里我就不讲类组件缓存了。

下期计划

react-router源码解析,让我们一起来看看吧,拜拜👋🏻再会。