人工智能

10个案例让你彻底理解React hooks的渲染逻辑

时间:2010-12-5 17:23:32  作者:人工智能   来源:域名  查看:  评论:0
内容摘要:正式开始,今天要写什么呢,原本我对react原理非常清楚,自己写过简单的react,带diff算法和异步更新队列的,但是对hooks源码一知半解,于是就要深究他的性能相关问题了 - 重复渲染的逻辑由于

正式开始,个案今天要写什么呢,例让理解原本我对react原理非常清楚,彻底自己写过简单的染逻react,带diff算法和异步更新队列的个案,但是例让理解对hooks源码一知半解,于是彻底就要深究他的性能相关问题了   - 重复渲染的逻辑

由于项目环境比较复杂,如果是染逻纯class组件,那么就是个案component、pureComponent、例让理解shouldComponentUpdate之类的彻底控制一下是否重新渲染,但是染逻hooks似乎更多场景,接下来一一攻破。个案

 场景一 ,例让理解父组件使用hooks,彻底子组件使用class Component

 父组件 

export default function Test() {       const [state, setState] = useState({  a: 1, b: 1, c: 1 });      const [value, setValue] = useState(11);      return (          <div>              <div>                  state{ state.a},{ state.b}              </div>              <Button                  type="default"                  onClick={ () => {                       //@ts-ignore                      setState({  a: 2, b: 1 });                      //@ts-ignore                      setState({  a: 2, b: 2 });                      console.log(state, state);                  }}              >                  测试              </Button>              <hr />              <div>value{ value}</div>              <Button                  type="default"                  onClick={ () => {                       setValue(value + 1);                  }}              >                  测试              </Button>              <Demo value={ state} />          </div>      );  } 

子组件 

export default class App extends React.Component<Props> {       render() {           const {  props } = this;          console.log(demo render);          return (              <div>                  { props.value.a},{ props.value.b}              </div>          );      }  } 

结果每次点击图中的测试按钮,子组件Demo都会重新render: 

总结:父组件(hook)每次更新,都会导出一个新的state和value对象,子组件肯定会更新(如果不做特殊处理)

 场景二,父组件使用hooks,子组件使用class PureComponent 

父组件代码跟上面一样,子组件使用PureComponent: 

export default function Test() {       const [state, setState] = useState({  a: 1, b: 1, c: 1 });      const [value, setValue] = useState(11);      return (          <div>              <div>                  state{ state.a},{ state.b}              </div>              <Button                  type="default"                  onClick={ () => {                       //@ts-ignore                      setState({  a: 2, b: 1 });                      //@ts-ignore                      setState({  a: 2, b: 2 });                      console.log(state, state);                  }}              >                  测试              </Button>              <hr />              <div>value{ value}</div>              <Button                  type="default"                  onClick={ () => {                       setValue(value + 1);                  }}              >                  测试              </Button>              <Demo value={ state} />          </div>      );  } 

子组件使用PureComponent: 

export default class App extends React.PureComponent<Props> {       render() {           const {  props } = this;          console.log(demo render);          return (              <div>                  { props.value.a},{ props.value.b}              </div>          );      }  } 

结果子组件依旧会每次都重新render:

总结:结论同上,确实是依赖的云服务器props改变了,因为父组件是hook模式,每次更新都是直接导出新的value和state.

 场景三,搞懂hook的setState跟class组件setState有什么不一样

理论:class的setState,如果你传入的是对象,那么就会被异步合并,如果传入的是函数,那么就会立马执行替换,而hook的setState是直接替换,那么setState在hook中是异步还是同步呢?

实践:

组件A: 

export default function Test() {       const [state, setState] = useState({  a: 1, b: 1, c: 1 });      const [value, setValue] = useState(11);      return (          <div>              <div>                  state{ state.a},{ state.b},{ state.c}              </div>              <Button                  type="default"                  onClick={ () => {                       //@ts-ignore                      setState({  a: 2 });                      //@ts-ignore                      setState({  b: 2 });                      console.log(state, state);                  }}              >                  测试              </Button>              <hr />              <div>value{ value}</div>              <Button                  type="default"                  onClick={ () => {                       setValue(value + 1);                  }}              >                  测试              </Button>              <Demo value={ state} />          </div>      );  } 

我将setState里两次分别设置了state的值为{ a:2},{ b:2},那么是合并,那么我最终得到state应该是{ a:2,b:2,c:1},如果是替换,那么最后得到的state是{ b:2}

结果:

点击测试按钮后,state变成了{ b:2},整个value被替换成了{ b:2}

结论:hook的源码库setState是直接替换,而不是合并

 场景四 , 父组件使用class,子组件使用hook

    父组件: 

export default class App extends React.PureComponent {       state = {           count: 1,      };      onClick = () => {           const {  count } = this.state;          this.setState({               count: count + 1,          });      };      render() {           const {  count } = this.state;          console.log(father render);          return (              <div>                  <Demo count={ count} />                  <Button onClick={ this.onClick}>测试</Button>              </div>          );      }  } 

子组件: 

interface Props {       count: number;  }  export default function App(props: Props) {       console.log(props, props);      return <div>{ props.count}</div>;  } 

逻辑:父组件(class组件)调用setState,刷新自身,然后传递给hooks子组件,然后自组件重新调用,更新

 场景五

但是我此时需要想实现一个class 组件的 PureComponent一样的效果,需要用到React.memo

修改父组件代码为: 

export default class App extends React.PureComponent {       state = {           count: 1,          value: 1,      };      onClick = () => {           const {  value } = this.state;          this.setState({               count: value + 1,          });      };      render() {           const {  count, value } = this.state;          console.log(father render);          return (              <div>                  <Demo count={ count} />                  { value}                  <Button onClick={ this.onClick}>测试</Button>              </div>          );      }  } 

子组件加入memo,代码修改为: 

import React, {  useState, memo } from react;  interface Props {       count: number;  }  function App(props: Props) {       console.log(props, props);      return <div>{ props.count}</div>;  }  export default memo(App); 

此时逻辑:class组件改变了自身的state,自己刷新自己,由上而下,传递了一个没有变化的props给hooks组件,hooks组件使用了memo包裹自己。

结果:

我们使用了memo实现了PureComponent的效果,浅比较了一次

 场景六,hook,setState每次都是相同的值  export default class App extends React.PureComponent {       state = {           count: 1,          value: 1,      };      onClick = () => {           const {  value } = this.state;          this.setState({               value:   1,          });      };      render() {           const {  count, value } = this.state;          console.log(father render);          return (              <div>                  <Demo count={ count} />                  { value}                  <Button onClick={ this.onClick}>测试</Button>              </div>          );      }  } 

结果:由于每次设置的值都是一样的(都是1),hooks不会更新,同class

 场景七,父组件和子组件都使用hook

父组件传入count给子组件 

export default function Father() {       const [count, setCount] = useState(1);      const [value, setValue] = useState(1);      console.log(father render)      return (          <div>              <Demo count={ count} />              <div>value{ value}</div>              <Button                  onClick={ () => {                       setValue(value + 1);                  }}              >                  测试              </Button>          </div>      );  } 

子组件使用count 

export default function App(props: Props) {       console.log(props, props);      return <div>{ props.count}</div>;  } 

结果:每次点击测试,都会导致子组件重新render

子组件加入memo 

function App(props: Props) {       console.log(props, props);      return <div>{ props.count}</div>;  }  export default memo(App); 

结果:

子组件并没有触发更新

这里跟第一个案例class的PureComponent不一样,第一个案例class的PureComponent子组件此时会重新render,是因为父组件hooks确实每次更新都会导出新的网站模板value和state。这里是调用了一次,设置的都是相同的state.所以此时不更新

 场景八,父组件hook,子组件hook,使用useCallback缓存函数

父组件: 

export default function App() {     const [count1, setCount1] = useState(0);    const [count2, setCount2] = useState(0);   const handleClickButton1 = () => {       setCount1(count1 + 1);    };    const handleClickButton2 = useCallback(() => {       setCount2(count2 + 1);    }, [count2]);     return (      <div>        <div>          <Button onClickButton={ handleClickButton1}>Button1</Button>        </div>        <div>          <Button onClickButton={ handleClickButton2}>Button2</Button>        </div>      </div>    );  } 

子组件: 

import React from react;  const Button = (props: any) => {      const {  onClickButton, children } = props;      return (          <>              <button onClick={ onClickButton}>{ children}</button>              <span>{ Math.random()}</span>          </>      );  };  export default React.memo(Button); 

结果:虽然我们使用了memo.但是点击demo1,只有demo1后面的数字改变了,demo2没有改变,点击demo2,两个数字都改变了。

那么我们不使用useCallback看看

父组件修改代码,去掉useCallback 

export default function App() {       const [count1, setCount1] = useState(0);      const [count2, setCount2] = useState(0);      const handleClickButton1 = () => {           setCount1(count1 + 1);      };      const handleClickButton2 = () => {           setCount2(count2+ 1);      };      return (          <div>              <div>                  <Demo onClickButton={ handleClickButton1}>Demo1</Demo>              </div>              <div>                  <Demo onClickButton={ handleClickButton2}>Demo</Demo>              </div>          </div>      );  } 

子组件代码不变,结果此时每次都会两个数字都会跟着变。

官方对useCallback的解释:

就是返回一个函数,只有在依赖项发生变化的时候才会更新(返回一个新的函数)

结论:

我们声明的 handleClickButton1 是直接定义了一个方法,这也就导致只要是父组件重新渲染(状态或者props更新)就会导致这里声明出一个新的方法,新的方法和旧的方法尽管长的一样,但是依旧是两个不同的对象,React.memo 对比后发现对象 props 改变,就重新渲染了。 

const a =()=>{ }  const b =()=>{ }  a===b //false 

这个道理大家都懂,不解释了

 场景九,去掉依赖数组中的count2字段  import React, {  useState, useCallback } from react;  import Demo from ./Demo;  export default function App() {     const [count2, setCount2] = useState(0);    const handleClickButton2 = useCallback(() => {       setCount2(count2 + 1);    }, []);    return (      <Demo         count={ count2}        onClickButton={ handleClickButton2}      >测试</Demo>    );  } 

这样count2的值永远都是0,那么这个组件就不会重导出setCount2这个方法,handleClickButton2这个函数永远不会变化,Button只会更新一次,就是Demo组件接受到的props从0到1到的时候.继续点击,count2也是0,但是props有一次从0-1的过程导致Demo子组件被更新,不过count2始终是0,这非常关键

 场景十,使用useMemo,缓存对象,达到useCallback的效果

使用前 

export default function App() {       const [count, setCount] = useState(0);      const [value, setValue] = useState(0);      const userInfo = {           age: count,          name: Jace,      };      return (          <div>              <div>                  <Demo userInfo={ userInfo} />              </div>              <div>                  { value}                  <Button                      onClick={ () => {                           setValue(value + 1);                      }}                  ></Button>              </div>          </div>      );  } 

子组件使用了memo,没有依赖value,只是依赖了count.

但是结果每次父组件修改了value的值后,虽然子组件没有依赖value,而且使用了memo包裹,还是每次都重新渲染了

 

import React from react;  const Button = (props: any) => {       const {  userInfo } = props;      console.log(sub render);      return (          <>              <span>{ userInfo.count}</span>          </>      );  };  export default React.memo(Button); 

使用后useMemo 

const [count, setCount] = useState(0);  const obj = useMemo(() => {     return {       name: "Peter",      age: count    };  }, [count]);  return <Demo obj={ obj}> 

很明显,第一种方式,如果每次hook组件更新,那么hook就会导出一个新的count,const 就会声明一个新的obj对象,即使用了memo包裹,也会被认为是一个新的对象。

看看第二种的结果:

父组件更新,没有再影响到子组件了。

写在最后:

为什么花了将近4000字来讲React hooks的渲染逻辑,React的核心思想,就是拆分到极致的组件化。拆得越细致,性能越好,避免不必要的更新,就是性能优化的基础,希望此文能真正帮助到你了解hook的渲染逻辑 

copyright © 2025 powered by 益强资讯全景  滇ICP备2023006006号-31sitemap