У меня есть тривиальный пример, который я построил, который показывает, что функция, переданная во второй параметр React.memo, похоже, не получает предыдущее значение свойства, как я ожидал. Этот пример представляет собой простой список динамиков (объектов), которые отображаются в виде кнопок. Событие onClick на кнопке передается родительскому компоненту, состояние изменяется в родительском компоненте, и новый рендер должен отображать только обновленный компонент.

Однако, независимо от предыдущего состояния, переданное в предыдущем всегда совпадает с новым состоянием.

Пример находится по этому URL-адресу: https://stackblitz.com/edit/react-upe9vu.

В компоненте Hello.js текущий импорт предназначен для ./Speaker, однако, если вы измените его на ./SpeakerWithMemo, как показано в строке выше, нажатие кнопки не приведет к обновлению статуса "избранное".

Я ожидал, что в SpeakerWithMemo.js console.log в строке 12 будет показывать prevProps.speaker.favorite, отличный от nextProps.speaker.favorite.

Соответствующий код приведен ниже:

< Сильный > Hello.js

import React, { useState } from "react";

// NO UPDATE HAPPENS ON BUTTON CLICK.
//import Speaker from "./SpeakerWithMemo";

// UPDATE HAPPENS AS EXPECTED ON BUTTON CLICK
import Speaker from "./Speaker";

export default () => {
  const speakersArray = [
    { name: "Crockford", id: 101, favorite: true },
    { name: "Gupta", id: 102, favorite: false },
    { name: "Ailes", id: 103, favorite: true },
  ];

  const [speakers, setSpeakers] = useState(speakersArray);

  const clickFunction = (speakerIdClicked) => {
    var speakersArrayUpdated = speakers.map((rec) => {
      if (rec.id === speakerIdClicked) {
        rec.favorite = !rec.favorite;
      }
      return rec;
    });
    setSpeakers(speakersArrayUpdated);
  };

  return (
    <div>
      {speakers.map((rec) => {
        return (
          <Speaker
            speaker={rec}
            key={rec.id}
            clickFunction={() => {
              clickFunction(rec.id);
            }}
          ></Speaker>
        );
      })}
    </div>
  );
};

SpeakerWithMemo.js

import React from "react";

export default React.memo(
  ({ speaker, clickFunction }) => {
    console.log(`Rendering Speaker ${speaker.name} ${speaker.favorite}`);
    return (
      <button onClick={clickFunction}>
        With Memo {speaker.name} {speaker.id}{" "}
        {speaker.favorite === true ? "true" : "false"}
      </button>
    );
  },
  (prevProps, nextProps) => {
    console.log(
      `memo: ${prevProps.speaker.favorite === nextProps.speaker.favorite} ${
        prevProps.speaker.name
      } fav: prev: ${prevProps.speaker.favorite} next: ${
        nextProps.speaker.favorite
      } `
    );
    return prevProps.speaker.favorite === nextProps.speaker.favorite;
  }
);
1
Pete 28 Июл 2020 в 00:29

1 ответ

Лучший ответ

Памятка работает не так, как ожидалось, потому что у вас мутация состояния. Не возвращая ссылку на новый объект, реакция отключается при повторной отрисовке, то есть ссылка на объект props.speaker никогда не изменяется, поэтому response предполагает, что более глубокая проверка неуместна.

const clickFunction = (speakerIdClicked) => {
  var speakersArrayUpdated = speakers.map((rec) => {
    if (rec.id === speakerIdClicked) {
      rec.favorite = !rec.favorite; // <-- Mutates the object you try to memoize
    }
    return rec;
  });
  setSpeakers(speakersArrayUpdated);
};

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

const clickFunction = speakerIdClicked => {
  setSpeakers(speakers =>
    speakers.map(rec =>
      rec.id === speakerIdClicked
        ? {
            ...rec, // <-- shallow copy existing value
            favorite: !rec.favorite // <-- update field/property
          }
        : rec
    )
  );
};

https://react-eyvskj.stackblitz.io

1
Drew Reese 27 Июл 2020 в 23:06