场景一: 更新 state 的一个对象(或数组)属性的某个子属性或值。
使用 Hook Function Component
function App() {
const [arr, updateArr] = useState([]);
const addList = () => {
arr.push('Hello React');
updateArr(arr);
};
return (
<div>
{
arr.map((item, index) => (
<p key={index}>{index} {item}</p>
))
}
<button onClick={addList}>添加List</button>
</div>
);
}
使用 Class Component
class App extends Component {
constructor(props) {
super(props);
this.state = {
arr: []
}
}
addList = () => {
let arr = this.state.arr;
arr.push('Hello React');
this.setState({
arr: arr
}, () => {
console.log(this.state.arr);
});
};
deleteList = () => {
const { arr } = this.state;
arr.splice(0, 1);
this.setState({
arr: arr
}, () => {
console.log(this.state.arr);
});
};
render() {
const { arr } = this.state;
return (
<div>
{
arr.map((item, index) => (
<p key={index}>{index} {item}</p>
))
}
<button onClick={this.addList}>添加List</button>
<button onClick={this.deleteList}>删除List</button>
</div>
);
}
}
结果:使用 Hook Function Component push 数组后数组长度并没有改变,使用Class Component正常。
原因:在 Hook 中直接修改 state 的一个对象(或数组)属性的某个子属性或值,然后直接进行 set,不会触发重新渲染。
- 对 Class Component来说,state 是 Immutable 的,setState 后一定会生成一个全新的 state 引用。它是通过 this.state 方式读取 state,所以每次代码执行都会拿到最新的 state 引用。
- 对 Hook Function Component 而言,useState 产生的数据也是 Immutable 的,通过数组第二个参数 Set 一个新值后,原来的值会形成一个新的引用在下次渲染时。
解决方案
改变引用地址
function App() {
const [arr, updateArr] = useState([]);
const addList = () => {
arr.push('Hello React');
// 通过扩展运算符实现深拷贝
updateArr([...arr]);
};
return (
<div>
{
arr.map((item, index) => (
<p key={index}>{index} {item}</p>
))
}
<button onClick={addList}>添加List</button>
</div>
);
}
场景二: 在setTimeout中更改state。
使用 Hook Function Component
function App() {
const [count, updateCount] = useState(0);
useEffect(() => {
let timer = setTimeout(() => {
updateCount(1);
getCount();
}, 1000);
return () => {
clearTimeout(timer);
}
}, []);
const getCount = () => {
console.log(count); // result: 0
};
return (
<div>{count}</div>
);
}
使用 Class Component
let timer = null;
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
componentDidMount() {
timer = setTimeout(() => {
this.setState({
count: 1
})
this.getCount();
}, 1000);
}
getCount = () => {
console.log(this.state.count); // result: 1
}
componentWillUnmount() {
clearTimeout(timer);
}
render() {
const { count } = this.state;
console.log(count); // result: 1
return (
<div>{count}</div>
);
}
}
结果:使用 Hook Function Component 更改count后,页面显示1,getCount方法中打印的count为0,使用Class Component更改count后页面显示1,getCount方法中打印的count为1。
原因:Hook Function Comoponent中由于对 state 的读取没有通过 this. 的方式,使得每次 setTimeout 都读取了当时渲染闭包环境的数据,虽然最新的值跟着最新的渲染变了,但旧的渲染里,状态依然是旧值。
解决方案
使用ref
function App() {
const [count, updateCount] = useState(0);
useEffect(() => {
let timer = setTimeout(() => {
updateCount(1);
getCount();
}, 1000);
return () => {
clearTimeout(timer);
}
}, []);
let ref = useRef();
ref.current = count;
const getCount = () => {
console.log(ref.current); // result: 1
};
return (
<div>{count}</div>
);
}
The End~
附上我认为比较值得研读的相关文章: