setState 陷阱

在异步上下文中使用 setState 时应谨慎使用。例如,你可能会尝试在 get 请求的回调中调用 setState

class MyClass extends React.Component {
    constructor() {
        super();

        this.state = {
            user: {}
        };
    }

    componentDidMount() {
        this.fetchUser();
    }

    fetchUser() {
        $.get('/api/users/self')
            .then((user) => {
                this.setState({user: user});
            });
    }

    render() {
        return <h1>{this.state.user}</h1>;
    }
}

这可以调用问题 - 如果在卸下 Component 之后调用回调,那么 this.setState 将不是一个函数。在这种情况下,你应该小心确保你的 setState 的使用是可取消的。

在此示例中,你可能希望在组件卸载时取消 XHR 请求:

class MyClass extends React.Component {
    constructor() {
        super();

        this.state = {
            user: {},
            xhr: null
        };
    }

    componentWillUnmount() {
        let xhr = this.state.xhr;

        // Cancel the xhr request, so the callback is never called
        if (xhr && xhr.readyState != 4) {
            xhr.abort();
        }
    }

    componentDidMount() {
        this.fetchUser();
    }

    fetchUser() {
        let xhr = $.get('/api/users/self')
            .then((user) => {
                this.setState({user: user});
            });

        this.setState({xhr: xhr});
    }
}

异步方法保存为状态。在 componentWillUnmount 中,你可以执行所有清理 - 包括取消 XHR 请求。

你也可以做一些更复杂的事情。在这个例子中,我正在创建一个’stateSetter’函数,它接受这个对象作为参数,并在调用函数 cancel 时阻止 this.setState

function stateSetter(context) {
    var cancelled = false;
    return {
        cancel: function () {
            cancelled = true;
        },
        setState(newState) {
            if (!cancelled) {
                context.setState(newState);
            }
        }
    }
}

class Component extends React.Component {
    constructor(props) {
        super(props);
        this.setter = stateSetter(this);
        this.state = {
            user: 'loading'
        };
    }
    componentWillUnmount() {
        this.setter.cancel();
    }
    componentDidMount() {
        this.fetchUser();
    }
    fetchUser() {
        $.get('/api/users/self')
            .then((user) => {
                this.setter.setState({user: user});
            });
    }
    render() {
        return <h1>{this.state.user}</h1>
    }
}

这是因为 cancelled 变量在我们创建的 setState 闭包中可见。