Странное поведение javascript toString()
Я пытаюсь использовать 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;
1 ответ
В toString
метод, который вы помещаете в State, привязан к исходному экземпляру состояния:
class State {
constructor(xs) { this.xs = xs }
toString = () => `[${this.xs}]` // Class field arrow function
}
Поле класса означает, что независимо от контекста вызова toString
вызывается с помощью, он вернет this.xs
исходного состояния. Даже если редуктор обновляет состояние, конструктор состояния больше не запускается.
На более поздних звонках App
, создается начальное состояние, а затем выполняются некоторые действия по его обновлению, в результате чего state
переменная является обновленным объектом, но у нее все еще естьtoString
метод привязан к начальному состоянию.
Вот пример поведения ванильного JS:
const obj = {
val: 'val',
toString: () => obj.val
};
const copiedObj = { ...obj, val: 'newVal' };
console.log(copiedObj.toString());
Если вы назначили function
вместо стрелочной функции, тогда toString
будет вызываться с вызывающим контекстом обновленного состояния, потому что он не привязан к начальному состоянию, поэтому он будет вызван с вызывающим контекстом обновленного состояния и правильно получитxs
:
toString = function () {
return `[${this.xs}]`;
}
Кстати, вы не можете использовать обычный метод, например
toString() {
return `[${this.xs}]`;
}
потому что в вашем редукторе:
const reducer = (state, action) => ({
...state,
xs: [...state.xs, action.x]
});
Синтаксис распространения принимает только перечислимые собственные свойства. С синтаксисом метода (например,toString() {
) свойство помещается в прототип состояния, а не в фактический экземпляр, поэтому его не будет в конечномstate
, а встроенный Object.prototype.toString
вместо этого будет вызываться.