一句话总结: StrictMode 下,不要在 useState、useRef 等初始值的构造函数/回调函数里修改 state
起因是,我需要写这样一个代码
- 有一个通过单例模式创建的对象;
- 这个单例对象初始化完成以后,需要刷新页面状态;
正常的前端,大概会把对象初始化的逻辑写在 useEffect 或者 useEffectLayout
可是,今天的我脑洞清奇,因为我想让早点初始化完成,所以我把对象的初始化写在了 useState 的初始值里,这样执行的时机会在 useEffect 或者 useEffectLayout 之前
js
import React, { useState } from 'react';
class Onething {
constructor(props) {
console.log('*******constructor');
props.init();
}
static instance;
static getInstance(props) {
console.log('*******getInstance');
if (!this.instance) {
this.instance = new Onething(props);
}
return this.instance;
}
}
const Example = () => {
const init = () => {
setText('完成');
}
const [text, setText] = useState('初始');
const [thing] = useState(Onething.getInstance({ init }));
console.log('******render', text);
return <div>{text}</div>;
}
我心里,这个代码的执行顺序是这样的:
- 将 text 初始化为 "初始";
- 执行 Onething 的构造函数,将 text 修改为 "完成";
会先初始化 text 为 "初始",再执行 Onething 的构造函数,构造函数里将 text 变成 "完成",所以最后界面会展示"完成"。
结果!这个代码!!没有照我的预期运行!!!单例对象初始化完成后,界面依然显示"初始"!!!!
打开控制台,组件函数在 StrictMode 下执行了两遍,
- 将 text 初始化为 "初始";
- 执行 Onething 的构造函数,将 text 修改为 "完成";
- StrictMode 下第二次执行组件函数,再次将 text 初始化为 "初始";
由于 Onething 是单例模式,第二次执行时没有执行构造函数、再修改 text 值,所以 text 值又变成了 "初始";
唉,唉,唉
我是专门来写 bug 的吧