关于javascript:深入了解-React-state-hook

先看个问题,上面组件中如果点击3次组件Counter的“setCounter”按钮,控制台输入是什么?

function Display({ counter }) {
    console.log('Display.render', counter);
    return <p>{counter}</p>
}

function Counter() {
    const [counter, setCounter] = useState(1);
    console.log('Counter.render', counter);
    return (
           <>
               <Display counter={counter}/>
               <button onClick={() => setCounter(2)}>setCounter</button>
           </>
   )
}

正确的答案:

  1. 第一次点击“setCounter”按钮,state变成2,触发一次re-render。输入:

    Counter.render 2
    Display.render 2
  2. 第二次点击“setCounter”按钮,尽管state没有变,然而又触发了一次组件Counter re-render,然而没有触发组件Display re-render,输入:

    Counter.render 2
    Display.render 2
    Counter.render 2
  3. 第三次点击“setCounter”按钮,state没有变,也没有触发re-render。

如果答对了,能够间接先点赞并返回上一页了。如果没有答对,那得花点工夫读读剩下的内容了

一、更新队列

其实每个state hook都关联一个更新队列。每次调用setState函数时,React并不会立刻执行更新函数,而是把更新函数插入更新队列里,并通知React须要安顿一次re-render。

function Counter() {
    const [counter, setCounter] = useState(0);
    console.log('Counter.render', counter);
    return (
           <>
               <Display counter={counter}/>
               <button onClick={() => setCounter(counter + 1)}>Add</button>
               <button onClick={() => {
                   console.log('Click event begin');
                   
                   setCounter(() => {
                       console.log('update 1');
                       return 1;
                   });

                   setCounter(() => {
                        console.log('update 2');
                        return 2;
                   });

                   console.log('Click event end');
               }}>setCounter</button>
           </>
   )
}

先点击下”Add”按钮,再点击“setCounter”按钮看下输入:

Click event begin
Click event end
update 1
update 2
Counter.render 2
Display.render 2

执行事件处理函数过程中并没有执行更新函数。次要还是为了性能优化吧,因为可能存在多处setState函数调用。

1.2 多个工作队列

每个state hook对应一个工作队列,一个组件里可能会波及多个工作队列。

  1. 每个工作队列是相互独立的;
  2. 每个工作队列的更新函数执行程序取决于工作队列创立先后,即调用useState的先后顺序。
function Counter() {
    console.log('Counter.render begin');
    const [counter, setCounter] = useState(1);
    const [counter2, setCounter2] = useState(1);
    return (
           <>
               <p>counter1: {counter}</p>
               <p>counter2: {counter2}</p>
               <button onClick={() => {
                    setCounter(() => {
                       console.log('setCounter update1');
                       return 2;
                   })
                    setCounter2(() => {
                        console.log('setCounter2 update1');
                        return 2;
                    })
                    setCounter(() => {
                        console.log('setCounter update2');
                        return 2;
                    })
                    setCounter2(() => {
                        console.log('setCounter2 update2');
                        return 2;
                    })
               }}>setCounter2</button>
           </>
   )
}

setCounter对应的工作队列的更新函数永远要先于setCounter2对应的工作队列的更新函数执行。

二、懒计算

只有须要state时React才会去计算最新的state值,即得等到再次执行useState时才会执行更新队列里的更新函数。并且同一个更新队列里多个更新函数是顺次执行的,前一个更新函数的输入,作为下一个更新函数的输出。

function Display({ counter }) {
    console.log('Display.render', counter);
    return <p>{counter}</p>
}

function Counter() {
    console.log('Counter.render begin');
    const [counter, setCounter] = useState(0);
    console.log('Counter.render', counter);
    return (
           <>
               <Display counter={counter}/>
               <button onClick={() => setCounter(counter + 1)}>Add</button>
               <button onClick={() => {
                   console.log('Click event begin');
                   
                   setCounter(prev => {
                       console.log(`update 1, prev=${prev}`);
                       return 10;
                   });

                   setCounter(prev => {
                        console.log(`update 2, prev=${prev}`);
                        return 20;
                   });

                   console.log('Click event end');
               }}>setCounter</button>
           </>
   )
}

先点击下”Add”按钮,再点击“setCounter”按钮看下输入:

Click event begin
Click event end
Counter.render begin
update 1, prev=1
update 2, prev=10
Counter.render 20
Display.render 20

会发现此时先执行的渲染函数,再执行更新函数。第二个更新函数的实参就是第一个更新函数的返回值。

三、批处理

只有再次执行useState时React才会执行更新函数,也就是说只有再次执行渲染函数时才会晓得state是否发生变化。那React什么时候再次执行渲染函数呢?
个别咱们都是在事件处理函数里(用户交互,网络)调用setState,React是在一个批处理里执行回调函数。回调函数执行结束后如果触发了re-render申请,则React就触发一次re-render

  1. 一个批处理最多触发一次re-render, 并且一个批处理里能够蕴含多个工作队列;

    function Counter() {
        console.log('Counter.render begin');
        const [counter1, setCounter1] = useState(0);
        const [counter2, setCounter2] = useState(0);
    
        return (
               <>
                   <p>counter1={counter1}</p>
                   <p>counter2={counter2}</p>
                   <button onClick={() => {                   
                       setCounter1(10);
                       setCounter1(11);
    
                       setCounter2(20);
                       setCounter2(21);
                   }}>setCounter</button>
               </>
       )
    }

    点击”setCounter”按钮,看下输入:

    Counter.render begin
  2. 批处理只能解决回调函数里的同步代码,异步代码会作为新的批处理;

    function Display({ counter }) {
        console.log('Display.render', counter);
        return <p>{counter}</p>
    }
    
    function Counter() {
        console.log('Counter.render begin');
        const [counter, setCounter] = useState(0);
        return (
               <>
                   <Display counter={counter}/>
                   <button onClick={() => {                   
                       setCounter(prev => {
                           return 10;
                       });
    
                       setTimeout(() => {
                            setCounter(prev => {
                                return 20;
                            });
                       })
                   }}>setCounter</button>
               </>
       )
    }

    点击”setCounter”按钮,看下输入:

    Counter.render begin
    Display.render 10
    Counter.render begin
    Display.render 20

    触发两次批处理。

四、跳过渲染

咱们都晓得如果state的值没有发生变化,React是不会从新渲染组件的。然而从下面得悉React只有再次执行useState时才会计算state的值啊。
为了计算最新的state须要触发re-render,而state如果不变又不渲染组件,这如同是个先有蛋还是先有鸡的问题。React是采纳2个策略跳过从新渲染。

4.1 立刻计算

下面提到的都是懒计算,其实React还存在立刻计算。React立刻更新函数:

  • 如果state值不变,则不会触发re-render
  • 如果state值发生变化,则转到懒加载策略。

当上一次计算的state没有发生变化或者上次是初始state,则采纳立刻执行策略调用更新函数

  1. 以后state是初始state;

    function Counter() {
        console.log('Counter.render begin');
        const [counter, setCounter] = useState(1);
        return (
               <>
                   <p>counter={counter}</p>
                   <button onClick={() => {                   
                        console.log('Click event begin');
                        setCounter(() => {
                            console.log('update');
                            return counter;
                        })
                        console.log('Click event end');
                   }}>setCounter</button>
               </>
       )
    }

    点击“setCounter”按钮看下输入:

    Click event begin
    update
    Click event end

    这样阐明了React默认采纳立刻执行策略。

  2. 上一次计算state不变

    function Counter() {
        console.log('Counter.render begin');
        const [counter, setCounter] = useState(1);
        return (
               <>
                   <p>counter={counter}</p>     
    
                   <button onClick={() => {                   
                        console.log('Click event begin');
                        // 放弃state不变
                        setCounter(() => {
                            console.log('update');
                            return counter;
                        })
                        console.log('Click event end');
                   }}>setCounter</button>
                    <button onClick={() => {
                        setCounter(2)
                    }}>setCounter2</button>
               </>
       )
    }

    先点击两次或者更屡次”setCounter2″按钮(营造上次计算结果是state不变),再点击一次“setCounter”按钮看下输入。

4.2 懒计算

懒计算就是下面说到的那样。懒计算过程中如果发现最终计算的state没有发现变动,则React不抉择组件的子组件,即此时尽管执行了组件渲染函数,然而不会渲染组件的子组件

function Display({ counter }) {
    console.log('Display.render', counter);
    return <p>{counter}</p>
}

function Counter() {
    console.log('Counter.render begin');
    const [counter, setCounter] = useState(1);
    return (
           <>
               <Display counter={counter} />
               <button onClick={() => setCounter(2) }>setCounter2</button>
           </>
   )
}

点击两次“setCounter2”按钮,看下输入:

Counter.render begin
Display.render 2
Counter.render begin

第二次点击尽管触发了父组件re-render,然而子组件Display并没有re-render

懒计算导致的问题只是会多触发一次组件re-render,但这个别不是问题。React useState API文档 也提到了:

Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.

4.3 立刻计算主动转懒计算

在一个批处理中采纳立刻计算发现state发生变化,则立马转成懒计算模式,即前面的所有工作队列的所有更新函数都不执行了。

function Counter() {
    console.log('Counter.render begin');
    const [counter, setCounter] = useState(1);
    return (
           <>
               <p>counter={counter}</p>     

               <button onClick={() => {                   
                    console.log('Click event begin');
                    // 放弃state不变
                    setCounter(() => {
                        console.log('update 1');
                        return counter;
                    })

                    // state + 1
                    setCounter(() => {
                        console.log('update 2');
                        return counter + 1;
                    })

                    // state + 1
                    setCounter(() => {
                        console.log('update 3');
                        return counter + 1;
                    })
                    console.log('Click event end');
               }}>setCounter</button>
           </>
   )
}

点击“setCounter”按钮,看下输入:

Click event begin // 先调用事件处理函数
update 1 // 上个state是初始state,采纳立刻执行策略,所以立马执行更新函数1
update 2 // 更新函数1并没有更新state,持续采纳立刻执行策略,所以立马执行更新函数2,然而state产生了变动,转懒计算策略
Click event end
Counter.render begin
update 3

执行完更新函数2state产生了变动,React立马转成懒加载模式,前面的更新函数都不立刻执行了。

参考

整顿自gitHub笔记:

  1. 解密React state hook
  2. State Hook

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理