在单独的文件中具有 React 上下文,无法让组件不重新渲染 [英] Having React Context in Separate File, Can't Get Component to Not re-render

查看:46
本文介绍了在单独的文件中具有 React 上下文,无法让组件不重新渲染的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个简单的 React Context 示例,它使用 useMemo 来记忆一个函数,并在单击任何子组件时重新渲染所有子组件.我已经尝试了几种替代方案(已注释掉),但都没有奏效.请参阅 stackblitz 及下方的代码.

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

Index.js

从反应"导入反应;import { render } from react-dom";从./Hello"导入你好;import { GlobalProvider } from "./GlobalState";功能应用(){返回 (<全球供应商><你好/></GlobalProvider>);}渲染(,document.getElementById(root"));

GlobalState.js

import React, {createContext、useState、useCallback、useMemo} 来自反应";export const GlobalContext = createContext({});export const GlobalProvider = ({ children }) =>{const [speakerList, setSpeakerList] = useState([{ name: Crockford", id: 101, 最喜欢的: true },{ name: Gupta", id: 102, 最喜欢的: false },{ name: Ailes", id: 103, 最喜欢的: true },]);const clickFunction = useCallback((speakerIdClicked) => {setSpeakerList((currentState) => {返回 currentState.map((rec) => {如果(rec.id === SpeakerIdClicked){return { ...rec, 最喜欢的:!rec.favorite };}返回记录;});});},[]);//const provider = useMemo(() => {//return { clickFunction: clickFunction, SpeakerList: SpeakerList };//}, []);//const provider = { clickFunction: clickFunction, SpeakerList: SpeakerList };常量提供者 = {clickFunction: useMemo(() => clickFunction,[]),演讲者列表:演讲者列表,};返回 (<GlobalContext.Provider value={provider}>{children}</GlobalContext.Provider>);};

Hello.js

import React, {useContext} from "react";从./Speaker"导入扬声器;从 './GlobalState' 导入 { GlobalContext };导出默认值 () =>{const { SpeakerList } = useContext(GlobalContext);返回 (<div>{speakerList.map((rec) => {return <Speaker Speaker={rec} key={rec.id}></Speaker>;})}

);};

Speaker.js

import React, { useContext } from react";import { GlobalContext } from "./GlobalState";导出默认 React.memo(({ Speaker }) => {console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);const { clickFunction } = useContext(GlobalContext);返回 (<><按钮onClick={() =>{clickFunction(speaker.id);}}>{speaker.name} {speaker.id}{""}{speaker.favorite === true ?真实":假"}</>);});

解决方案

代码中的几个问题:

  • 您已经使用 useCallback 记忆了 clickFunction,无需使用 useMemo 钩子.

  • 您正在使用 Speaker 组件中的 Context.这就是导致 Speaker 组件的所有实例重新渲染的原因.

解决方案:

因为您不想将 clickFunction 作为道具从 Hello 组件传递给 Speaker 组件并希望访问 clickFunction 直接在 Speaker 组件中,你可以为 clickFunction 创建一个单独的 Context.

这会起作用,因为在单独的 Context 中提取 clickFunction 将允许 Speaker 组件不使用 GlobalContext.当任何按钮被点击时,GlobalContext 将被更新,导致所有使用 GlobalContext 的组件重新渲染.由于 Speaker 组件正在使用一个未更新的单独上下文,因此当单击任何按钮时,它将阻止 Speaker 组件的所有实例重新呈现.

演示

const GlobalContext = React.createContext({});const GlobalProvider = ({ children }) =>{const [speakerList, setSpeakerList] = React.useState([{ name: "Crockford", id: 101, 最喜欢的: true },{ name: "Gupta", id: 102, 最喜欢的: false },{ 名称:Ailes",id:103,最喜欢的:true }]);返回 (<GlobalContext.Provider value={{speakerList, setSpeakerList }}>{孩子们}</GlobalContext.Provider>);};const ClickFuncContext = React.createContext();const ClickFuncProvider = ({ children }) =>{const { SpeakerList, setSpeakerList } = React.useContext(GlobalContext);const clickFunction = React.useCallback(speakerIdClicked => {setSpeakerList(currentState => {返回 currentState.map(rec => {如果(rec.id === SpeakerIdClicked){return { ...rec, 最喜欢的:!rec.favorite };}返回记录;});});}, []);返回 (<ClickFuncContext.Provider value={clickFunction}>{孩子们}</ClickFuncContext.Provider>);};const Speaker = React.memo(({ Speaker }) => {console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);const clickFunction = React.useContext(ClickFuncContext)返回 (<div><按钮onClick={() =>{clickFunction(speaker.id);}}>{speaker.name} {speaker.id}{" "}{speaker.favorite === true ?真假"}

);});功能扬声器列表(){const { SpeakerList } = React.useContext(GlobalContext);返回 (<div>{speakerList.map(rec => {返回 (<Speaker Speaker={rec} key={rec.id}/>);})}

);};功能应用(){返回 (<全球供应商><ClickFuncProvider><发言人名单/></ClickFuncProvider></GlobalProvider>);}ReactDOM.render(, document.getElementById("root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script><div id="root"></div>

您还可以在 StackBlitz

I've got a simple example of React Context that uses useMemo to memoize a function and all child components re-render when any are clicked. I've tried several alternatives (commented out) and none work. Please see code at stackblitz and below.

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

Index.js

import React from "react";
import { render } from "react-dom";
import Hello from "./Hello";

import { GlobalProvider } from "./GlobalState";

function App() {
  return (
    <GlobalProvider>
      <Hello />
    </GlobalProvider>
  );
}

render(<App />, document.getElementById("root"));

GlobalState.js

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

export const GlobalContext = createContext({});

export const GlobalProvider = ({ children }) => {
  const [speakerList, setSpeakerList] = useState([
    { name: "Crockford", id: 101, favorite: true },
    { name: "Gupta", id: 102, favorite: false },
    { name: "Ailes", id: 103, favorite: true },
  ]);

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

  // const provider = useMemo(() => {
  //   return { clickFunction: clickFunction, speakerList: speakerList };
  // }, []);

  //const provider = { clickFunction: clickFunction, speakerList: speakerList };

  const provider = {
    clickFunction: useMemo(() => clickFunction,[]),
    speakerList: speakerList,
  };

  return (
    <GlobalContext.Provider value={provider}>{children}</GlobalContext.Provider>
  );
};

Hello.js

import React, {useContext} from "react";

import Speaker from "./Speaker";

import { GlobalContext } from './GlobalState';

export default () => {
  const { speakerList } = useContext(GlobalContext);

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

Speaker.js

import React, { useContext } from "react";

import { GlobalContext } from "./GlobalState";

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>
    </>
  );
});

解决方案

Couple of problems in your code:

  • You already have memoized the clickFunction with useCallback, no need to use useMemo hook.

  • You are consuming the Context in Speaker component. That is what's causing the re-render of all the instances of Speaker component.

Solution:

Since you don't want to pass clickFunction as a prop from Hello component to Speaker component and want to access clickFunction directly in Speaker component, you can create a separate Context for clickFunction.

This will work because extracting clickFunction in a separate Context will allow Speaker component to not consume GlobalContext. When any button is clicked, GlobalContext will be updated, leading to the re-render of all the components consuming the GlobalContext. Since, Speaker component is consuming a separate context that is not updated, it will prevent all instances of Speaker component from re-rendering when any button is clicked.

Demo

const GlobalContext = React.createContext({});

const GlobalProvider = ({ children }) => {
  const [speakerList, setSpeakerList] = React.useState([
    { name: "Crockford", id: 101, favorite: true },
    { name: "Gupta", id: 102, favorite: false },
    { name: "Ailes", id: 103, favorite: true }
  ]);

  return (
    <GlobalContext.Provider value={{ speakerList, setSpeakerList }}>
      {children}
    </GlobalContext.Provider>
  );
};

const ClickFuncContext = React.createContext();

const ClickFuncProvider = ({ children }) => {
  const { speakerList, setSpeakerList } = React.useContext(GlobalContext);

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

  return (
    <ClickFuncContext.Provider value={clickFunction}>
      {children}
    </ClickFuncContext.Provider>
  );
};

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

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

function SpeakerList() {
  const { speakerList } = React.useContext(GlobalContext);

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

function App() {
  return (
    <GlobalProvider>
      <ClickFuncProvider>
        <SpeakerList />
      </ClickFuncProvider>
    </GlobalProvider>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

You can also see this demo on StackBlitz

这篇关于在单独的文件中具有 React 上下文,无法让组件不重新渲染的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆