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