Предотвращение повторного рендеринга для родственных компонентов React с массивом состояний, хранящихся в родительском

#javascript #reactjs #react-hooks

#javascript #reactjs #реагирующие хуки

Вопрос:

Я рендерю серию кадров из видео, каждый в своем собственном компоненте. Кадры хранятся как состояние в родительском компоненте, называемом Timeline.

Поскольку мне нужно манипулировать элементами массива фреймов из дочерних компонентов, я передаю функции обратного вызова, которые имеют индекс i с закрытием. Чтобы избежать изменений в отдельных фреймах, влияющих на все фреймы, я использовал memo компонент более высокого порядка вокруг каждого Frame .

Тем не менее, я обнаружил, что каждый раз, когда я выбираю фрейм, все фреймы повторно отображаются. Использование whyDidyouUpdate крючка (https://usehooks.com/useWhyDidYouUpdate /), я точно определил это по моему обратному вызову, изменяющемуся при каждом рендеринге.

Я попытался сделать onClick={useCallback(() => selectFrame(i), [])} , но вместо этого фрейм просто не меняет цвета, указывая мне, что это каким-то образом помешало правильному функционированию обратного вызова.

 let Frame = memo(({frame, ...}) => (
    <img style={{border: frame.selected ? "green" : "red";}} />
);

let Timeline = () => {

    let [frames, setFrames] = useState([
        { id: "00000", selected: True, ...more image information },
        { id: "00001", selected: True, ...more image information },
        { id: "00002", selected: True, ...more image information },
    ]);

    let selectFrame = (i) => {
        let newFrames = [...frames];
        newFrames[i].mask = base64;
        setFrames(newFrames);
    }

    return (
        <div>
        {frames.map((f, i) => (
            <Frame
                frame={f}
                key={f.id}
                onClick={() => selectFrame(i)}
            />
        )}
        </div>
    ));
}
  

Есть какие-нибудь предложения по правильному способу решения этой проблемы? Спасибо

РЕДАКТИРОВАТЬ: решение Дрю, приведенное ниже, указало мне правильное направление. Правильный синтаксис таков:

 const selectFrame = (i) => useCallback(() =>
    setFrames(frames.map((f, j) => (
        (i === j) ? {...f, selected: !f.selected} : f
    ))), []);
  

Для каждой функции вам нужно иметь отдельный запоминаемый обратный вызов.

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

1. Я склоняюсь к тому, что это просто правильное поведение React. Вы передаете обратный вызов, и когда он запускается, это вызывает повторный рендеринг в компонентах, в которые он был передан. Если вы хотите полностью избежать этого, вам может просто потребоваться отменить состояние / поведение, которыми вы манипулируете, с обратным вызовом к наиболее распространенному предку / родительскому

2. Потенциально, если это общее поведение с сохранением состояния (не контекстное), возможно, оно может стать пользовательским подключением…

Ответ №1:

memo HOC просто выполняет неглубокое сравнение реквизитов, каждый раз, когда они frames сопоставляются, создается новый onClick обратный вызов. Преобразуйте в обратный вызов curried, который включает индекс и возвращает, и использует обновление функционального состояния, и запоминайте обратный вызов с помощью useCallback перехвата.

 const Timeline = () => {

    const [frames, setFrames] = useState([
        { id: "00000", selected: True, ...more image information },
        { id: "00001", selected: True, ...more image information },
        { id: "00002", selected: True, ...more image information },
    ]);

    const selectFrame = useCallback(index => () => {
        setFrames(frames => frames.map((frame, i) => i === index ? {
          ...frame,
          mask: base64;
        } : frame));
    }, []);

    return (
        <div>
        {frames.map((f, i) => (
            <Frame
                frame={f}
                key={f.id}
                onClick={selectFrame(i)}
            />
        )}
        </div>
    ));
}
  

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

1. Это сработало! Не могли бы вы подробнее рассказать о том, почему обновление состояния должно быть функциональным?

2. @CristobalSciutto Я полагаю, что это не обязательно должно быть обновление функционального состояния, но это, безусловно, шаблон, который приводит к меньшему количеству ошибок в коде. Если есть какой -либо шанс, что два или более обновления состояния могут быть поставлены в очередь за один цикл рендеринга (т. Е. onClick быстрый щелчок по двум кадрам), то при обычных обновлениях все поставленные в очередь обновления используют одно и то же текущее состояние по сравнению с состоянием из предыдущего вычисления обновления. Я должен был также указать, что newFrames[i].mask = base64; считается мутацией состояния, следовательно, создавая новый объект frame { ...frame, mask: base64 } .