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
闭包中可见。