Как установить состояние с помощью перехватов в функции on(‘change’) EventEmitter?

#javascript #node.js #reactjs #flux #eventemitter

#javascript #node.js #reactjs #поток #eventemitter

Вопрос:

С целью обучения я пытаюсь разобраться в основах шаблона потока, реализовав его с нуля. Я знаю, что могу решить проблему ниже, используя компонент класса, но почему это работает с компонентом класса setState({ ... }) , а не с функцией хука setValues ?

Как вы можете догадаться, проблема в том, что setValues функция, похоже, не запускает повторный рендеринг с новым состоянием. У меня такое чувство, что это связано с замыканиями, но я не очень хорошо их понимаю, поэтому я был бы признателен за любые рекомендации и помощь.

Магазин

 // store.js
import EventEmitter from "events";

class StoreClass extends EventEmitter {
    constructor() {
        super();
        this.values = []
    }

    addNow() {
        this.values.push(Date.now());
        this.emit('change')
    }

    getAll() {
        return this.values;
    }
}

const store = new StoreClass();

export default store;
 

Компонент

 // App.js
import store from './store';

function App() {
    const [values, setValues] = useState([]);

    const handleClick = () => {
        store.addNow();
    };

    useEffect(() => {
        const onChangeHandler = () => {
            console.log("Change called");
            setValues(() => store.getAll())
        };

        store.on('change', onChangeHandler);

        return () => store.removeAllListeners('change')
    }, [values]);

    return (
        <div>
            <button onClick={handleClick}>Click to add</button>
            <ul>
                {values.map(val => <li key={val}>{val}</li>)}
            </ul>
        </div>
    );
}
 

Комментарии:

1. что должно изменить значения? если setValues существуют только в useEffect, которые прослушивают изменения значения?

Ответ №1:

useeffcct прослушивает изменения значений, но setValues существуют только в useEffect, я не думаю, что вам вообще нужен useEffect

вы можете попробовать что-то вроде этого:

     // App.js
import store from './store';

function App() {
    const [values, setValues] = useState([]);


    const onChangeHandler = () => {
            console.log("Change called");
            const storeValues = store.getAll();
            setValues(storeValues);
        };

    const handleClick = () => {
        store.addNow();
        onChangeHandler();
        };


    return (
        <div>
            <button onClick={handleClick}>Click to add</button>
            <ul>
                {values.map(val => <li key={val}>{val}</li>)}
            </ul>
        </div>
    );
}
 

Комментарии:

1. Это сработало бы, однако оно не соответствует шаблону потока, когда «умный» компонент представления контроллера прослушивает события из хранилища.

Ответ №2:

Поработав еще несколько часов и снова прочитав документы react, я могу, наконец, ответить на свой вопрос. Короче говоря, проблема в том, что состояние в хранилище не обновлялось неизменно.

Redux, который является реализацией архитектуры Flux, очень четко объясняет это в своей документации:

Redux ожидает, что все обновления состояния выполняются неизменно

Следовательно, виновник в приведенном выше коде находится в addNow функции хранилища. Array.push() Метод изменяет существующий массив путем добавления элемента. Неизменным способом сделать это было бы либо использовать Array.concat() метод, либо синтаксис распространения ES6 (деструктурирование) [...this.values] , а затем присвоить новый массив this.values .

 addNow() {
  this.values.push(Date.now()); // Problem - Mutable Change
  this.values = this.values.concat(Date.now()) // Option 1 - Immutable Change
  this.values = [...this.values, Date.now()] // Option 2 - Immutable Change
  this.emit('change')
}
 

Чтобы увидеть эффект каждого из этих изменений в компоненте react, измените setValues функцию в useEffect перехвате на следующую:

 setValues((prev) => {
  const newValue = store.getAll()

  console.log("Previous:", prev)
  console.log("New value:", newValue)

  return newValue
})
 

В консоли станет ясно, что когда состояние в хранилище изменяется изменчиво, после первого нажатия кнопки предыдущее состояние и новое состояние всегда будут одинаковыми и будут ссылаться на один и тот же массив. Однако, когда состояние в хранилище изменяется неизменно, тогда предыдущее состояние и новое состояние не ссылаются на один и тот же массив. Кроме того, в консоли будет ясно, что предыдущий массив состояний имеет на одно значение меньше, чем новый массив состояний.

Почему изменение состояния вызывает повторный рендеринг при использовании компонента класса, но не при использовании хуков?

Согласно документации react для функции setState компонента класса.

setState() всегда будет приводить к повторному рендерингу, если shouldComponentUpdate() не возвращает false .

Это означает, что независимо от того, изменяется ли состояние в хранилище изменчиво или неизменно, при запуске события изменения и setState вызове функции react повторно отобразит компонент и отобразит «новое» состояние. Однако это не относится к useState крючку.

Согласно документам react для крючка useState:

Если вы обновите перехват состояния до того же значения, что и текущее состояние, React выйдет из строя без рендеринга дочерних элементов или срабатывания эффектов. (React использует Object.is алгоритм сравнения.)

Это означает, что когда изменение массива в хранилище выполняется путем изменения исходного объекта, react проверяет, совпадают ли новое состояние и предыдущее, прежде чем принять решение о повторном рендеринге. Поскольку ссылка как для предыдущего, так и для текущего состояния одинакова, react ничего не делает. Чтобы исправить это, изменения в хранилище должны выполняться неизменно.