我正在尝试使用toString将类临时输出到DOM。
我遇到一些行为,我不明白被覆盖的toString()将始终在何处输出初始状态。但是,如果使用了外部函数(即stateToString)甚至是JSON.stringify,则更新的状态将按照我的期望输出。

下面是我尝试最小化此行为的尝试。
重申一下,我的预期行为是让他们所有人最初输出:["initial"],他们这样做。但是,单击按钮时toString()输出不会更新,而其他两个会更新。

这似乎特别奇怪,因为stateToStringState.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

09-17 17:26
查看更多