React——setState

一、setState(updater, [callback])
React 通过setState方法来更新组件的内部状态,当setState方法被调用时,React 会根据新的state来重新渲染组件(并不是每次setState都会触发render,React可能会合并操作,再一次性 render)。

React将setState设置为批次更新,从而避免了频繁地重新渲染组件,即setState是不能保证同步更新状态的。因此,如果我们在setState之后,直接使用this.state.key很可能得到错误的结果。那么当我们需要同步获得 state 更新后的值时,如何做呢?React提供了两种方式来实现该功能。

1、回调函数
setState方法可以传入第二个函数类型的参数作为回调函数,该回调函数将会在state完成更新之后被调用。

class StateTest extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }

    componentDidMount() {
        this.setState({
            count: this.state.count + 1
        }, () => {
            console.log(this.state.count); // 1
        });
        console.log(this.state.count); // 0
    }


    render () {
        return (
            <div></div>
        )
    }
}

2、状态计算函数
React 还提供了另一种实现state同步更新的策略。React 允许我们将函数作为setState()的第一个参数,该函数作为状态计算函数将被传入两个参数,可信赖的组件state和组件props。React会自动将我们的状态更新操作添加到队列中并等待前面的状态更新完毕,最后将最新的state传入状态计算函数。

class StateTest extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }

    componentDidMount() {
        this.setState({
            count: this.state.count + 1
        });

        this.setState({
            count: this.state.count + 1
        }, () => {
            console.log(this.state.count); // 1
        });
    }

    render () {
        return (
            <div></div>
        )
    }
}
class StateTest extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }

    componentDidMount() {
        this.setState((prevState, props) => ({
            count: prevState.count + 1
        }));

        this.setState((prevState, props) => ({
            count: prevState.count + 1
        }), () => {
            console.log(this.state.count); // 2
        });
    }


    render () {
        return (
            <div></div>
        )
    }
}

二、setState的缺点
1、setState循环调用风险
调用setState之后,shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate 等生命周期函数会依次被调用(如果shouldComponentUpdate没有返回 false的话),如果我们在render、componentWillUpdate或componentDidUpdate中调用了setState方法,那么可能会造成循环调用,最终导致浏览器内存占满后崩溃。

2、setState可能会引发不必要的渲染
可能造成不必要渲染的因素如下:
(1)新 state 和之前的一样。这种情况可以通过 shouldComponentUpdate 解决。
(2)state 中的某些属性和视图没有关系(譬如事件、timer ID等),这些属性改变不影响视图的显示。

3、setState并不总能有效地管理组件中的所有状态

因为组件中的某些属性是和视图没有关系的,当组件变得复杂的时候可能会出现各种各样的状态需要管理,这时候用setState管理所有状态是不可取的。state中本应该只保存与渲染有关的状态,而与渲染无关的状态尽量不放在state中管理,可以直接保存为组件实例的属性,这样在属性改变的时候,不会触发渲染,避免浪费。

三、setsate经典面试题

class StateTest1 extends React.Component {
    constructor() {
        super();
        this.state = {
            count: 0
        };
    }

    componentDidMount() {
        this.setState({
            count: this.state.count + 1
        });
        console.log(this.state.count);    // 0

        this.setState({
            count: this.state.count + 1
        });
        console.log(this.state.count);    // 0

        setTimeout(() => {
            this.setState({
                count: this.state.count + 1
            });
            console.log(this.state.count);  // 2

            this.setState({
                count: this.state.count + 1
            });
            console.log(this.state.count);  // 3
        }, 0);
    }

    render() {
        return null;
    }
};

这里写图片描述
setTimeout中的两次setState是异步的,所以没有立即执行。componentDidMount运行结束,transaction也会运行并结束,transaction结束时会触发closeAll函数,这个函数会将isBatchingUpdates重新给设置会初始状态false。当setTimeout中的setState执行时,因为没有前置的batchedUpdate调用,所以batchingStrategy的isBatchingUpdates标志位是false,也就导致了新的state马上生效,没有走到dirtyComponents分支。

总结
setState并不总是异步执行的。当没有前置batchedUpdate时,setState中的新状态是会立刻生效的,而不是放到dirtyComponents中等待更新。

参考资料:https://github.com/Marco2333/react-demo/tree/master/demo/demo07%20setState

猜你喜欢

转载自blog.csdn.net/michellezhai/article/details/80098211