NgRx MockStore setState使用疑问:为何需先调用setState({})?
首先,咱们先拆解下你遇到的问题:在使用NgRx的MockStore做单元测试时,直接调用setState更新修改后的状态没生效,必须先调用一次store.setState({})才行。这种写法确实不规范,问题主要出在你操作状态的方式和对MockStore的使用上,下面具体说明:
你代码里的几个核心问题
直接操作
MockStore的内部私有属性MockStore已经提供了官方推荐的getState()方法来获取当前状态,你直接访问store.source的BehaviorSubject属于绕过了MockStore的封装逻辑,很容易导致内部状态不同步,这是绝对不推荐的做法。状态不可变性处理不彻底
你虽然做了state = {...state}的浅拷贝,但如果state.library是嵌套对象,浅拷贝只会复制它的引用,此时修改state.library.bookIdSelected并没有创建全新的library对象。MockStore内部依赖状态对象的引用变化来触发更新,这种半吊子的拷贝可能无法被识别为“状态变更”。多余的
store.setState({})属于歪打正着的 workaround
这一步其实是通过先传入全新的空对象(打破原有引用),再传入修改后的状态(又是新引用),强制触发了MockStore的更新逻辑,但完全是多余的操作,正确写法只需要一次setState调用。
正确的实现方式
function setSelectedBook(bookId: string) { // 用官方API获取当前状态 const currentState = store.getState(); // 逐层创建不可变的新状态,确保嵌套层级也是新引用 const newState = { ...currentState, library: { ...currentState.library, bookIdSelected: bookId } }; // 一次setState即可完成状态更新 store.setState(newState); }
为什么原来的写法需要两次setState?
本质原因是你第一次调用store.setState(state)时,传入的state对象和MockStore当前内部状态的引用没有发生有效变化(比如嵌套对象还是原引用)。MockStore会认为状态没有变更,所以不会触发订阅者更新。而先调用store.setState({})传入了全新的空对象,打破了原有引用,之后再传入修改后的state(新引用),才触发了预期的更新。
额外建议
- 永远使用
store.getState()获取当前状态,不要依赖MockStore的内部实现细节(比如source属性)。 - 严格遵守NgRx的不可变状态原则,修改嵌套状态时要逐层拷贝,确保每个修改的层级都是新对象。
- 可以尝试使用immer库来简化不可变状态的创建,减少手动拷贝的出错概率。
内容的提问来源于stack exchange,提问作者Mike de Klerk

