如果不需要重新渲染,直接修改反应状态是否安全?
Is it safe to modify a react state directly, if you don't need a rerender?
看起来直接修改反应状态是一种不好的做法(Do Not Modify State Directly) and the reasons have already been discussed many times on SO (see ) and outside in the web (see a blog post)。
问题好像是直接修改状态时,对当前虚拟DOM中的状态进行修改。因此,当需要重新渲染时,新虚拟 DOM 中的状态将与前一个虚拟 DOM 中的状态相同,而实际 DOM 将不会更新。
但是如果你不想在修改状态时触发重新渲染怎么办?这种情况直接修改state应该是安全的吧?
下面描述了我面临的情况,这让我陷入了评估在某些情况下是否可以安全地直接修改状态的兔子洞。
我有一个 React useReducer
状态,它由一个带有许多键的对象组成。
const initialState = {
a: 0,
b: 1,
c: 2,
d: 3,
};
在状态减速器中,我并不总是 return 一个新状态。说如果我想修改 a
或 b
那么我也想触发重新渲染,但是如果我修改 c
或 d
我不想因为 DOM 无论如何都没有受到影响。
我认为 c
和 d
有点像 ref
:它们在单个渲染中是可变的,但它们的值在重新渲染之间保留。
当然,我可以将状态限制为仅 a
和 b
,并为每个 c
和 d
创建一个 ref,但它们都是相关的,我经常需要将它们一起传递给一个函数。所以在我的例子中,将 refs 保持在状态下效果更好。
下面是减速器的样子:
// When modifying `a` or `b` `action.modifyRef` is set to `false`.
// When modifying `c` or `d` `action.modifyRef` is set to `true`.
const reducer = (state, action) => {
if (action.modifyRef) {
state[action.key] = action.value;
return state;
}
const newState = {
...state,
[action.key]: action.value,
};
return newState;
};
如果一开始就不需要重新渲染,我是否可以相信我可以安全地修改状态而不触发重新渲染?
如果前一个问题的答案是 "yes",那么要修改 c
或 d
我可以直接修改状态而不是针对reducer 不行吗?
state.c = 5;
// the above statement is equivalent to the one below
dispatchState({ modifyRef: true, key: 'c', value: 5 });
我不认为你描述的 c
和 d
(不应该导致 re-rendering 并且不用于渲染的成员)是状态信息因为这个术语在 React 中使用。它们是实例信息。在函数组件中使用 non-state 实例信息的正常方法是使用 ref.
在务实的 bits-and-bytes 层面上,可以 将 non-state 信息保存在状态中并直接修改它而不是使用状态 setter (直接或间接)?是的,你可能可以,至少一开始是这样。我可以看到导致 app/page 的 不正确行为 的唯一场景涉及渲染,而你说过它们不用于此。
但如果你这样做:
- 这会让其他团队成员感到困惑(或者您自己,如果您必须在休息后返回代码)。语义很重要。如果你称它为状态,但它不是状态,那会让某人绊倒。这就像调用一个不是函数的函数:在某些时候,有人会尝试调用那个“函数”。
- 这将是一个维护风险,因为团队成员(或你自己在休息后)可能会做出无害的更改,以便
c
或 d
用于渲染(因为在所有,它们都处于状态,因此可以将它们用于渲染),也许通过将其中一个作为 prop 传递给子组件。然后,您会遇到应用程序更改时无法正确重新呈现的情况。
有点切线,但在对您提到的问题的评论中,您 “...它们都是相关的,我经常需要将它们一起传递给一个函数...”。 =42=]
使用 ref 保存 c
和 d
,设置可能如下所示:
const [{a, b}, dispatch] = useReducer(/*...*/);
const instanceRef = useRef(null);
const {c, d} = instanceRef.current = instanceRef.current ?? {c: /*...*/, d: /*...*/};
然后获取一个对象,以便将它们视为一个单元是:
const stuff = {a, b, c, d};
// ...use `stuff` where needed...
创建对象在现代 JavaScript 引擎中 非常 昂贵,因为这是他们积极优化的常见操作。 :-)
看起来直接修改反应状态是一种不好的做法(Do Not Modify State Directly) and the reasons have already been discussed many times on SO (see
问题好像是直接修改状态时,对当前虚拟DOM中的状态进行修改。因此,当需要重新渲染时,新虚拟 DOM 中的状态将与前一个虚拟 DOM 中的状态相同,而实际 DOM 将不会更新。
但是如果你不想在修改状态时触发重新渲染怎么办?这种情况直接修改state应该是安全的吧?
下面描述了我面临的情况,这让我陷入了评估在某些情况下是否可以安全地直接修改状态的兔子洞。
我有一个 React useReducer
状态,它由一个带有许多键的对象组成。
const initialState = {
a: 0,
b: 1,
c: 2,
d: 3,
};
在状态减速器中,我并不总是 return 一个新状态。说如果我想修改 a
或 b
那么我也想触发重新渲染,但是如果我修改 c
或 d
我不想因为 DOM 无论如何都没有受到影响。
我认为 c
和 d
有点像 ref
:它们在单个渲染中是可变的,但它们的值在重新渲染之间保留。
当然,我可以将状态限制为仅 a
和 b
,并为每个 c
和 d
创建一个 ref,但它们都是相关的,我经常需要将它们一起传递给一个函数。所以在我的例子中,将 refs 保持在状态下效果更好。
下面是减速器的样子:
// When modifying `a` or `b` `action.modifyRef` is set to `false`.
// When modifying `c` or `d` `action.modifyRef` is set to `true`.
const reducer = (state, action) => {
if (action.modifyRef) {
state[action.key] = action.value;
return state;
}
const newState = {
...state,
[action.key]: action.value,
};
return newState;
};
如果一开始就不需要重新渲染,我是否可以相信我可以安全地修改状态而不触发重新渲染?
如果前一个问题的答案是 "yes",那么要修改 c
或 d
我可以直接修改状态而不是针对reducer 不行吗?
state.c = 5;
// the above statement is equivalent to the one below
dispatchState({ modifyRef: true, key: 'c', value: 5 });
我不认为你描述的 c
和 d
(不应该导致 re-rendering 并且不用于渲染的成员)是状态信息因为这个术语在 React 中使用。它们是实例信息。在函数组件中使用 non-state 实例信息的正常方法是使用 ref.
在务实的 bits-and-bytes 层面上,可以 将 non-state 信息保存在状态中并直接修改它而不是使用状态 setter (直接或间接)?是的,你可能可以,至少一开始是这样。我可以看到导致 app/page 的 不正确行为 的唯一场景涉及渲染,而你说过它们不用于此。
但如果你这样做:
- 这会让其他团队成员感到困惑(或者您自己,如果您必须在休息后返回代码)。语义很重要。如果你称它为状态,但它不是状态,那会让某人绊倒。这就像调用一个不是函数的函数:在某些时候,有人会尝试调用那个“函数”。
- 这将是一个维护风险,因为团队成员(或你自己在休息后)可能会做出无害的更改,以便
c
或d
用于渲染(因为在所有,它们都处于状态,因此可以将它们用于渲染),也许通过将其中一个作为 prop 传递给子组件。然后,您会遇到应用程序更改时无法正确重新呈现的情况。
有点切线,但在对您提到的问题的评论中,您 “...它们都是相关的,我经常需要将它们一起传递给一个函数...”。 =42=]
使用 ref 保存 c
和 d
,设置可能如下所示:
const [{a, b}, dispatch] = useReducer(/*...*/);
const instanceRef = useRef(null);
const {c, d} = instanceRef.current = instanceRef.current ?? {c: /*...*/, d: /*...*/};
然后获取一个对象,以便将它们视为一个单元是:
const stuff = {a, b, c, d};
// ...use `stuff` where needed...
创建对象在现代 JavaScript 引擎中 非常 昂贵,因为这是他们积极优化的常见操作。 :-)