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 閉包中可見。