Проблема с повторным рендерингом при передаче функции React с React Context API - PullRequest
0 голосов
/ 01 августа 2020

У меня есть простой пример, в котором я передаю clickFunction в качестве значения в React Context, а затем получаю доступ к этому значению в дочернем компоненте. Этот дочерний компонент повторно отображает событие, хотя я использую React.memo и React.useCallback. У меня есть пример в stackblitz, у которого нет проблемы повторного рендеринга без с использованием контекста здесь:

https://stackblitz.com/edit/react-y5w2cp (с этим проблем нет)

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

https://stackblitz.com/edit/react-wpnmuk

Вот код проблемы:

Здравствуйте. js

import React, { useCallback, useState, createContext } from "react";

import Speaker from "./Speaker";

export const GlobalContext = createContext({});

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 = useCallback((speakerIdClicked) => {
    setSpeakers((currentState) =>
      currentState.map((rec) => {
        if (rec.id === speakerIdClicked) {
          return { ...rec, favorite: !rec.favorite };
        }
        return rec;
      })
    );
  }, []);

  return (
    <GlobalContext.Provider
      value={{
        clickFunction: memoizedValue,
      }}
    >
      {speakers.map((rec) => {
        return <Speaker speaker={rec} key={rec.id}></Speaker>;
      })}
    </GlobalContext.Provider>
  );
};

Динамик. js

import React, {useContext} from "react";

import { GlobalContext } from "./Hello";

export default React.memo(({ speaker }) => {
  console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);

  const { clickFunction } = useContext(GlobalContext);

  return (
    <button
      onClick={() => {
        clickFunction(speaker.id);
      }}
    >
      {speaker.name} {speaker.id} {speaker.favorite === true ? "true" : "false"}
    </button>
  );
});

КОД РАБОТЫ НИЖЕ ИЗ ОТВЕТОВ НИЖЕ

Динамик . js

import React, {useContext} from "react";

import { GlobalContext } from "./Hello";

export default React.memo(({ speaker }) => {
  console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);

  const { clickFunction } = useContext(GlobalContext);

  return (
    <button
      onClick={() => {
        clickFunction(speaker.id);
      }}
    >
      {speaker.name} {speaker.id} {speaker.favorite === true ? "true" : "false"}
    </button>
  );
});

Привет. js

import React, { useState, createContext, useMemo } from "react";

import Speaker from "./Speaker";

export const GlobalContext = createContext({});

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) => {
    setSpeakers((currentState) =>
      currentState.map((rec) => {
        if (rec.id === speakerIdClicked) {
          return { ...rec, favorite: !rec.favorite };
        }
        return rec;
      })
    );
  };
  const provider = useMemo(() => {
    return ({clickFunction: clickFunction});
  }, []);
  return (
    <GlobalContext.Provider value={provider}>
      {speakers.map((rec) => {
        return <Speaker speaker={rec} key={rec.id}></Speaker>;
      })}
    </GlobalContext.Provider>
  );
};

Ответы [ 2 ]

2 голосов
/ 01 августа 2020

при передаче value = {{clickFunction}} в качестве опоры для Provider, как это, когда компонент повторно визуализирует и воссоздает этот объект, чтобы выполнить дочернее обновление, поэтому для предотвращения этого вам необходимо запомнить значение с помощью useMemo.

здесь код:

import React, { useCallback, useState, createContext,useMemo } from "react";

import Speaker from "./Speaker";

export const GlobalContext = createContext({});

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 = useCallback((speakerIdClicked) => {
    setSpeakers((currentState) =>
      currentState.map((rec) => {
        if (rec.id === speakerIdClicked) {
          return { ...rec, favorite: !rec.favorite };
        }
        return rec;
      })
    );
  }, []);
const provider =useMemo(()=>({clickFunction}),[])
  return (
    <div>
      {speakers.map((rec) => {
        return (
          <GlobalContext.Provider value={provider}>
            <Speaker
              speaker={rec}
              key={rec.id}
            ></Speaker>
          </GlobalContext.Provider>
        );
      })}
    </div>
  );
};

обратите внимание, что вам больше не нужно использовать useCallback clickFunction

1 голос
/ 01 августа 2020

Это потому, что ваш value, который вы передаете своему провайдеру, меняется каждый раз. Таким образом, это вызывает повторный рендеринг, потому что ваш Speaker компонент считает, что value изменен.

Возможно, вы можете использовать что-то вроде этого:

const memoizedValue = useMemo(() => ({ clickFunction }), []);

и удалите useCallback из определения функции, поскольку useMemo будет обрабатывать эту часть за вас.

const clickFunction = speakerIdClicked =>
  setSpeakers(currentState =>
    currentState.map(rec => {
      if (rec.id === speakerIdClicked) {
        return { ...rec, favorite: !rec.favorite };
      }
      return rec;
    })
  );

и передайте это вашему провайдеру, например:

<GlobalContext.Provider value={memoizedValue}>
  <Speaker speaker={rec} key={rec.id} />
</GlobalContext.Provider>

После предоставления ответ, я понял, что вы как-то неправильно используете Context. Вы сопоставляете массив и создаете несколько поставщиков для каждых данных. Вероятно, вам следует изменить свой лог c.

Обновление:

В большинстве случаев вы хотите сохранить состояние в своем контексте. Так что вы можете получить его и по value. Ниже приводится рабочий пример. На этот раз будьте осторожны с функцией, мы используем useCallback, чтобы получить стабильную ссылку.

const GlobalContext = React.createContext({});

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

function App() {
  const [speakers, setSpeakers] = React.useState(speakersArray);

  const clickFunction = React.useCallback((speakerIdClicked) => {
    setSpeakers((currentState) =>
      currentState.map((rec) => {
        if (rec.id === speakerIdClicked) {
          return { ...rec, favorite: !rec.favorite };
        }
        return rec;
      })
    );
  }, []);

  const memoizedValue = React.useMemo(() => ({ speakers, clickFunction }), [
    speakers,
    clickFunction,
  ]);

  return (
    <GlobalContext.Provider value={memoizedValue}>
      <Speakers />
    </GlobalContext.Provider>
  );
}

function Speakers() {
  const { speakers, clickFunction } = React.useContext(GlobalContext);

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

const Speaker = React.memo(({ speaker, clickFunction }) => {
  console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);

  return (
    <button
      onClick={() => {
        clickFunction(speaker.id);
      }}
    >
      {speaker.name} {speaker.id} {speaker.favorite === true ? "true" : "false"}
    </button>
  );
});

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root" />
...