'假的'必学必会之React学习笔记之三

283 阅读5分钟

前面我已经更新了很多基础的学习笔记,也提到过组件及其属性的不可变性。既然是不可变的那不是和我们日常开发中对元素的操作是相违背的么。事实上,不让改变是React的限制,但与此同时官方肯定也会给出合适的方法来修改这个组件和状态。写到这那咱们就来好好记录一下官方提供的setState()。据说这个是面试中必问的点。

  • 首先,我们先上一段代码来看看这个setState是怎么运作的。

分析:属性父组件传过来的,自己是不能控制,也不能改变状态;状态是组件自己内部产生的维护的,由自己维护,外界无法访问。唯一(不是太严谨)改变状态的方式就是setState(除了改变状态,还会保留老的已有属性和新增属性,但是要删除某属性那么需要手动置为null或者undefined),可能有些人会在此处怼我说不是有replaceState么一样可以更新状态,但是不好意思这个方法早在15版就已经被废弃掉了,还有一个方法就是传说中的强制更新forceUpdate基本没人用。所以现在对于我们来说学习真的是一个延续不断地过程(多情自古空余恨,此恨绵绵无绝期!)。

import React from 'react';
import ReactDOM from 'react-dom';

/**
*这个也是很关键的点。
*在组件类的实例中this是不是类的实例?
*一般来说类的方法里的this是undefined
*那如何让普通方法的this指定组件实例
* 1. 箭头函数
* 2. 匿名函数 
* 3.  bind绑定 this.add.bind(this)
*      3.1 在render里绑定
*      3.2 是在构造函数里绑
*/

class Counter extends React.Component {
    <!--在构造函数以外可以定义和初始化状态,编译后也是在实例上的-->
    <!--state = {number: 0}-->
    constructor(props) {
     super(props)
     <!--这个是将该函数绑定到实例上而且绑定了this指针-->
     <!--this.add = this.add.bind(this)-->
    }
    this.state = {
        number: 0,
        key: 'key'
    };
    <!--这个对应下面函数调用的第二种,用箭头函数调用将this指向外部的render函数他的this是指向实例的-->
    add(num){
    <!--核心的setState方法-->
        this.setState({
            number:this.state.number +num
        })
    }
    <!--将方法名挂到累得实例上去,箭头函数内部无this,此时就会指向外部的实例-->
    add = (num) => {
    <!--核心的setState方法-->
        this.setState({
            number:this.state.number +num
        })
    }
    render() {
        return (
        <div>
          <p>{this.state.name}</p>
          <p>{this.state.number}</p>
          <button onClick={this.add(3).bind(this)}>加一</button>
          <button onClick={() => this.add(3)}>加一</button>
          <button onClick={ this.add(3)}>加一</button>
        </div>
        )
    }
}
ReactDOM.render(<Counter/>, 'root')

写到这里大家发现这个例子好像也就完事了。我想要的结果也都有了。但是,如果只是这样的学习我觉得没啥意义。这个api毕竟是面试官最关注的问题之一那么他必然有他的难点。下面我们来细聊一下。

  • 针对setState的详细的解剖 >setState

    • 异步
    • 合并
    • 当你在进行事件处理的时候,会进行批量更新状态。
    • 先缓存所有的更新,然后等事件结束之后再进行统一的更新
    • 在settimeout里面调用setState的话,会立刻更新
    • 现在不推荐大家直接写值了,而是在setState里面放一个函数.参数是上一个状态,返回值是下一个状态
    • 对于上面的组件我们只是用了一种简单情景的更改,下面列举几个比较复杂的场景代码:
    this.setState((previousState) => ({ number: previousState.number + 1 }), () => {
        console.log(1, this.state); // 6
    });
    this.setState((previousState) => ({ number: previousState.number + 2 }), () => {
        console.log(2, this.state);
        // 6
    });
    this.setState((previousState) => ({ number: previousState.number + 3 }), () => {
        console.log(3, this.state);
        // 6
    });
    此时界面实现的是6,因为每次调用之后并不是直接去改,而是会缓存状态。并且更新掉了老的转态。异步执行,等最后执行打印状态的时候显示的就是最后的结果。
    
    this.setState({ number: this.state.number + 1 });
    console.log(this.state.number); // 0
    
    this.setState({ number: this.state.number + 2 });
    console.log(this.state.number);
    // 0
    
    this.setState({ number: this.state.number + 3 });
    console.log(this.state.number);
    // 0
    此时界面显示的是3;也就是每次执行setState的时候都把this.state.number的值变成了0;并没有累加起来。所以此时她是异步执行的。
    
    setTimeout(() => {
        this.setState({ number: this.state.number + 1 });
        console.log(this.state.number); // 1
        this.setState({ number: this.state.number + 3 });
        console.log(this.state.number); // 4
        this.setState({ number: this.state.number + 5 });
        console.log(this.state.number); // 9
    });
    此时界面显示的是9;也就是每次执行setState的时候都把this.state.number的值累加起来。并且此时的setState是`同步执行的`

    看到这里大家肯定很奇怪为什么三种情况的结果各不相同。那么现在我们就来代码描述一下,为什么会产生这样的问题。

  • 简易的实现以下setState

    let state = {number: 0};
    let updateQueue = [];
    function setState(newState) {
      updateQueue.push(newState);
    }
    setState({ number: state.number + 1 });
    setState({ number: state.number + 1 });
    setState({ number: state.number + 1 });
    
    updateQueue.forEach(newState =>state = newState);
    
  • 接下来我们要上难度了

    class Component {
        constructor() {
            this.state = {
                number: 0,
                key: 'key'
            };
            <!--批量更新-->
            this.batchUpdate = false;
            this.updateQueue = [];
            this.callbackQueue = [];
            this.preAdd = this.add;
            this.add = () => {
         <!--在这里我会开启批量更新模式-->
                this.batchUpdate = true;
                perAdd.apply(this, argumnets);
                this.flushUpdate()
            }
        }
        setState(partialState, callback) {
            if(this.batchUpdate) {
                this.updateQueue.push(partialState);
                if(callback) {
                    this.callbackQueue.push(callback);
                }
            } else {
                this.state = typeof partialState === 'function' ? partialState(this.state) : partialState
            }
        }
        forceUpadte() {
        <!--直接render并且更新-->
        <!--当组件的属性和状态没有改变是,我们也想更新可以使用-->
        }
        flushUpdate() {
            let state = this.state;
            for (let i=0; i < this.updateQueue.length; i++) {
                let partailState = 
                typeof this.updateQueue[i] === 'function' ? this.updateQueue[i](this.state) : this.updateQueue[i];
                <!--保留原来的老属性并用新的属性更新他们-->
                state = {
                    ...state,
                    ...partialState
                }
                <!--同步状态-->
                this.state = state;
            }
            this.callbackQueue.forEach(callback => callback && callback()
            this.batchUpdate = false;
        }
        add() {
            setTimeout(() => {
                this.setState({ number: this.state.number + 3 });
                console.log(this.state);
                this.setState({ number: this.state.number + 5 });
                console.log(this.state);
            });
            
             // this.setState({          number:       this.state.number + 1 });
            /*                     this.setState((previousState) => ({ number: previousState.number + 1 }), () => {
            console.log(1, this.state);
             });
            this.setState((previousState) => ({ number: previousState.number + 2 }), () => {
            console.log(2, this.state);
            });
            this.setState((previousState) => ({ number: previousState.number + 3 }), () => {
            console.log(3, this.state);
             }); */
            //this.setState({ number: this.state.number + 3 });
        }
    }
    
    let com = new Component();
    com.add();
    console.log(com.state)