我正在尝试使用toString将类临时输出到DOM。
我遇到一些行为,我不明白被覆盖的toString()
将始终在何处输出初始状态。但是,如果使用了外部函数(即stateToString)甚至是JSON.stringify
,则更新的状态将按照我的期望输出。
下面是我尝试最小化此行为的尝试。
重申一下,我的预期行为是让他们所有人最初输出:["initial"]
,他们这样做。但是,单击按钮时toString()
输出不会更新,而其他两个会更新。
这似乎特别奇怪,因为stateToString
和State.toString
似乎本质上是相同的函数,除了一个将状态作为接收器并将一个状态作为参数。
如果有人可以解释为什么会发生这种情况,我将不胜感激。
import React, { useReducer } from 'react';
class State {
constructor(xs) { this.xs = xs }
toString = () => `[${this.xs}]`
}
const stateToString = state => `[${state.xs}]`;
const reducer = (state, action) => ({
...state,
xs: [...state.xs, action.x]
});
const App = () => {
const [state, dispatch] = useReducer(reducer, new State(["initial"]));
return (
<div>
<button onClick={() => dispatch({ x: Math.random() })}>click</button><br />
toString: {state.toString()}<br />
print: {stateToString(state)}<br />
stringify: {JSON.stringify(state)}
</div>
);
};
export default App;
最佳答案
您放置在State上的toString
方法绑定(bind)到state的原始实例:
class State {
constructor(xs) { this.xs = xs }
toString = () => `[${this.xs}]` // Class field arrow function
}
那里的class字段意味着,无论用什么调用
toString
的调用上下文,它都将返回初始状态的this.xs
。即使reducer更新状态,该状态的构造函数也不会再次运行。在稍后调用
App
时,将创建初始状态,然后通过一些操作对其进行更新,从而使state
变量成为已更新的对象,但是它仍然具有绑定(bind)到初始状态的toString
方法。这是 Vanilla JS中的行为示例:
const obj = {
val: 'val',
toString: () => obj.val
};
const copiedObj = { ...obj, val: 'newVal' };
console.log(copiedObj.toString());
如果您分配了
function
而不是箭头函数,则toString
将与更新状态的调用上下文一起被调用,因为它没有绑定(bind)到初始状态,因此将与更新状态的调用上下文一起被调用,并且正确检索xs
:toString = function () {
return `[${this.xs}]`;
}
附带说明,您不能使用像
toString() {
return `[${this.xs}]`;
}
因为在您的 reducer 中:
const reducer = (state, action) => ({
...state,
xs: [...state.xs, action.x]
});
扩展语法仅具有可枚举的自身属性。使用方法语法(如
toString() {
),该属性将放置在State原型(prototype)上,而不是实际实例上,因此该属性将不存在于最终的state
中,而是将调用内置的Object.prototype.toString
。