Странное javascript toString () поведение - PullRequest
4 голосов
/ 09 января 2020

Я пытаюсь использовать 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 Ответ

4 голосов
/ 09 января 2020

Метод toString, который вы вводите в State, связан с исходным экземпляром состояния:

class State { 
  constructor(xs) { this.xs = xs } 
  toString = () => `[${this.xs}]` // Class field arrow function
}

Поле класса там означает, что независимо от того, какой контекст вызова вызывается toString вызывается с помощью, он вернет this.xs исходного состояния. Даже если редуктор обновляет состояние, конструктор состояния не запускается снова .

При последующих вызовах App создается начальное состояние, а затем выполняется несколько действий для обновления. это, в результате переменная state является обновленным объектом, , но у него все еще есть метод toString, связанный с начальным состоянием .

Вот пример поведения в vanilla JS:

const obj = {
  val: 'val',
  toString: () => obj.val
};

const copiedObj = { ...obj, val: 'newVal' };
console.log(copiedObj.toString());

Если вы назначили function вместо функции стрелки, то toString будет вызываться с контекстом вызова обновленного состояния *1029* потому что он не связан с начальным состоянием, поэтому он будет вызываться с контекстом вызова обновленного состояния и правильно извлекать xs:

toString = function () {
    return `[${this.xs}]`;
}

Как примечание, вы не можете используйте обычный метод как

toString() {
    return `[${this.xs}]`;
}

, потому что в вашем редукторе:

const reducer = (state, action) => ({
    ...state,
    xs: [...state.xs, action.x]
});

синтаксис распространения принимает только перечислимых собственных свойств. С синтаксисом метода (например, toString() {) свойство помещается в прототип State , а не в фактический экземпляр, поэтому он не будет существовать в конечном state, и вместо этого будет вызываться встроенный Object.prototype.toString.

...