• 彻底搞清楚setState


    setState最常见的问题是,是异步的还是同步的?

    setState在React.Component中的函数,是同步函数。但是我们调用的时候,不同的传参和不同的调用位置都会导致不同的结果。

    从页面看有时候是同步的,有时候是异步的。

    PS: 如果在componentDidMount触发,和用按钮事件触发是一样的。

    1)调用函数中直接调用,调用完之后立即打印,结果不变,表现是异步

    this.state = {number: 0}
    add = () => {
       this.setState({
           number: this.state.number + 1}, () => console.log(this.state.number) // 1
       )
      // console.log(this.state.number); -- 0
    }  // 页面显示1

    后台逻辑是,事件触发-->开启批量更新模式-->执行事件方法add-->状态存入队列[{number: 1}]-->方法结束(this.state.number=0)-->关闭批量更新模式

    -->更新状态(number-1)-->更新组件(render)-->componentDidUpdate-->setState.callback(number-1)

    2)批量调用setState会触发批量更新

    如第一个参数是对象,状态会出现覆盖;第一个参数是函数,不会被覆盖,会累计。

        state = {number: 0, name: 'lyra'}
        add2 = () => {
            this.setState((state) => ({number: state.number + 1}));
            this.setState((state) => ({name: state.name + '-good'}));
            this.setState((state) => ({number: state.number + 1}));
            this.setState((state) => ({name: state.name + '-clever'}));
        }// 执行完结果显示{number:2, name: lyra-good-clever}
        add3 = () => {
            this.setState({number: this.state.number + 1});
            this.setState({name: this.state.name + '-good'});
            this.setState({number: this.state.number + 1});
            this.setState({name: this.state.name + '-clever'}); 
        } // 执行完结果显示{number: 1, name: lyra-clever}

    后台逻辑同上,会依次存入状态队列。但是更新状态的方式是:

    this.queue.pendingStates.forEach(particalState => Object.assign(this.state, particalState));

    如果传入对象,存储状态的时候直接存储。

            if (typeof particalState === 'object') {
                this.pendingStates.push(particalState);
            }

    如果传入的是方法,会先进行处理,每个状态,都是以前一个状态为基础

            if (typeof particalState === 'function') {// 传入的是(state) => {}
                this.setStateFuncs.push(particalState);
                this.setStateFuncs.forEach(callback => {
                    const newParticalState = callback(this.component.state);
                    this.pendingStates.push(newParticalState);
                    Object.assign(this.component.state, newParticalState);
                });
                this.setStateFuncs.length = 0;// 注意清空!!
            }

    3)在异步任务回调函数中表现同步,且不批量更新

    class Test extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                number: 0,
                name: 'lyra',
            }
        }
        componentDidMount() {
            this.add();
        }
        add = () => {
            this.setState({number: this.state.number + 1}, () => console.log('callback-3=',this.state));
            this.setState({name: this.state.name + '-good'}, () => console.log('callback-4=',this.state)); 
            this.setState({number: this.state.number + 1}, () => console.log('callback-5=',this.state));
            this.setState({name: this.state.name + '-clever'}, () => console.log('callback-6=',this.state));
            console.log('==1==',this.state); 
            setTimeout(() => {
                console.log("==7===",this.state); 
                this.setState({ // 非批量处理,立即执行,触发render
                    number: this.state.number + 1,
                    name: this.state.name + '-bravo'
                }, () => console.log('callback-8=',this.state))
                console.log('==9==',this.state);
                this.setState({
                    number: this.state.number + 1,
                    name: this.state.name + '-congratulations'
                }, () => console.log('callback-10=',this.state))
                console.log('==11==',this.state); 
            }) 
            console.log("==2==",this.state);
        }
        render() {
            console.log('render')
            return (
                <h1>Hello, world!</h1>
            )
        }
    }

    运行结果如下

        ==1=={number: 0, name: 'lyra'}
        ==2=={number: 0, name: 'lyra'}
        render
        callback-3={number:1, name: 'lyra-clever}
        callback-4={number:1, name: 'lyra-clever}
        callback-5={number:1, name: 'lyra-clever}
        callback-6={number:1, name: 'lyra-clever}
        ==7==={number: 1, name: 'lyra-clever'}
        render
        callback-8={number: 2, name: 'lyra-clever-bravo'}
        ==9=={number: 2, name: 'lyra-clever-bravo'}
        render
        callback-10={number: 3, name: 'lyra-clever-bravo-congratulations'}
        ==11=={number: 3, name: 'lyra-clever-bravo-congratulations'}

    后台逻辑: 事件触发-->开启批量更新模式-->执行事件方法主线程(不包含setTimeout宏任务)-->前四个状态存入队列{number: 1}-->主线程结束-->

    关闭批量更新模式-->批量更新状态(this.state.number = 1)并渲染(DidUpdate)-->进入SetTImout-->setState-->非批量更新状态立即更新状态并渲染(DidUpdate)-->callback->

    -->下一个setState-->......

    PS:原生JS模拟setState实现

    html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <div id="root"></div>
        <script src="./component.js"></script>
        <script src='./counter.js'></script>
        <script src="./stateFunc.js"></script>
        <script>
            let container = document.getElementById('root');
            counterIns = new Counter({name: 'Lyra'}); //实例传属性
            counterIns1 = new SetFuncCounter({name: 'Lyra1'}); //实例传属性
            counterIns.mount(root); // ReactDOM.render(,window.root)
            counterIns1.mount(root);
        </script>
    </body>
    </html>
    View Code

    component.js

    /*
     * @Author: LyraLee
     * @Date: 2019-11-18 23:49:56
     * @LastEditTime: 2019-11-18 23:57:49
     * @Description: 
     */
    // 用于批量更新的全局状态和方法
    const batchingStragety = { 
        isBatchingUpdates: false, // 默认false,表面默认直接更新
        dirtyComponents: [], // 状态和页面展示不同的组件叫脏组件;应该去重一下
        batchUpdates() {
            this.dirtyComponents.forEach(dirtyComponent => {
                dirtyComponent.updateComponent();
            });
            this.dirtyComponents.length = 0;
        }
    }
    
    // 队列用于存储更新的状态,数据先入先出,有先后顺序
    class QueueUpdate{ 
        constructor(component) {
            this.pendingStates = []; // 队列存值
            this.setStateFuncs = []; // 传参是函数,存入该队列
            this.component = component;
        }
         // 插入队列
        addState(particalState) {
            // particalState有两种形式,function或者object;
            if (typeof particalState === 'object') {
                this.pendingStates.push(particalState);
            }
            if (typeof particalState === 'function') {// 传入的是(state) => {}
                this.setStateFuncs.push(particalState);
                this.setStateFuncs.forEach(callback => {
                    const newParticalState = callback(this.component.state);
                    this.pendingStates.push(newParticalState);
                    Object.assign(this.component.state, newParticalState);
                });
                this.setStateFuncs.length = 0;// 注意清空!!
            }
            /*
                然后判断isBatchingUpdating 是否是true,
                如果是true,推入dirtyComponents,    
            */
           // 如果是false, 遍历所有的dirtyComponents,调用各自的updateComponent()
           if (!batchingStragety.isBatchingUpdates) { 
                this.component.updateComponent();
                return;
            }
            batchingStragety.dirtyComponents.push(this.component);
        }
    }
    class Component {
        constructor(props) {
            this.props = props;
            this.queue = new QueueUpdate(this); // 实例化一个队列,用于存储newState的值。
        } 
        setState(particalState, callback) { // 传入需要更新的状态
            /** this.setState(new State)->将newState存入队列     */
            this.queue.addState(particalState);
            // callbackcomponentDidMount前存入队列,
            // 在componentDidMount后依次执行
        }
        updateComponent() { // 更新组件,在该方法中完成componentDidMount
            this.queue.pendingStates.forEach(particalState => {
                Object.assign(this.state, particalState)
            });
            this.queue.pendingStates.length = 0;
            let oldElement = this.topElement;
            let newElement = this.renderElement();
            oldElement.parentNode.replaceChild(newElement, oldElement);
        }
        renderElement() { // 模拟ReactDOM.render(element, container)
            let tempParent = document.createElement('div');
            tempParent.innerHTML = this.render(); // 将模版字符串转为真实dom
            this.topElement = tempParent.children[0]; // 使用属性,可以在this作用域暂存dom元素
            this.topElement.component = this;
            return this.topElement;
        }
        mount(container) {
            container.appendChild(this.renderElement())
        }
    } 
    
    // React中事件委托给全局document
    window.trigger = function(event, method) {
        const { component } = event.target;
        // React中每次触发事件时开启批量更新模式,都默认将isBatchingUpdates置为true,事件调用完成置为false。
        // 事件触发的核心逻辑代码是方法调用,isBatchingUpdates的状态变化可以看作一个事务,包裹方法
        // 所有组件用这个方法,各个组件方法名可能相同,所以需要在实例上调用方法
        const anyMethod = component[method].bind(component);
        transaction.perform(anyMethod);
    }
    // 事务,将任何方法(anyMethod)用wrapper包裹,然后通过Transcation的perform()方法执行anyMethod
    class Transcation{ 
        // 先执行所有wrapper的initialize方法,再执行anyMethod,最后执行所有wrapper的close方法。
        constructor(wrappers) {
            this.wrappers = wrappers;
        }
        perform(anyMethod) {
            this.wrappers.forEach(wrapper => {wrapper.initialize()});
            anyMethod();
            this.wrappers.forEach(wrapper => {wrapper.close()});
        }
    }
    const transaction = new Transcation([{ // 传入wrappers,是个数组!!
        initialize() {// 开启批量更新
           batchingStragety.isBatchingUpdates = true;
        },
        close() {
            batchingStragety.isBatchingUpdates = false;
            // 执行完后要把状态置为false, 调用更新函数
            batchingStragety.batchUpdates();
        }
    }]);
    View Code

    counter.js

    class Counter extends Component{
        constructor(props) {
            super(props);
            this.state = {
                number: 0,
                name: 'lyra'
            }
        }
        add() {
            this.setState({number: this.state.number + 1});
            this.setState({name: this.state.name + '-good'});
            this.setState({number: this.state.number + 1});
            this.setState({name: this.state.name + '-clever'});
            console.log('1==',this.state); // {number: 0, name: 'lyra'}
            setTimeout(() => {
                console.log("==3===",this.state); // {number: 1, name: 'lyra-clever'}
                this.setState({ // 非批量处理,立即执行
                    number: this.state.number + 1,
                    name: this.state.name + '-bravo'
                }) // 同步
                console.log('==4==',this.state); // {number: 2, name: 'lyra-clever-bravo'}
                this.setState({
                    number: this.state.number + 1,
                    name: this.state.name + '-congratulations'
                })
                console.log('==5==',this.state); // {number: 3, name: 'lyra-clever-bravo-congratulations'}
            })
            console.log("==2==",this.state); // {number: 0, name: 'lyra'}
        }
        render() {// 模拟JSX中虚拟DOM js中不能写html,只能写字符串模版,模版字符串只有一个顶级标签
            return `<button id="counter" onclick="trigger(event, 'add')">
                ${this.state.name}
                --------------<br/>
                ${this.state.number}
            </button>`   
        }
    }
    View Code
  • 相关阅读:
    [转载]--python3.6 错误: ModuleNotFoundError:No module named "Crypto"
    [笔记]--RedHat6.5使用CentOS的yum源
    [笔记]--Linux公社,想要的都在里面
    [笔记]--vsftpd配置教程
    Vue 中 axios 配置使用
    Element-ui自定义主题换肤
    vue-cli项目打包需要修改的路径问题
    cookie和session 以及 localStorage和sessionStorage之间的区别和应用场景
    正则表达式
    详解-vue项目中的文件和目录
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11566474.html
Copyright © 2020-2023  润新知