React Context API 的设置器工作很慢

React Context API's setters work very slow

我的目标是实现这样的功能,例如根据 child 的子链接 expanded/collapsed 更改页面的背景渐变。为此,我使用了 useContext 挂钩,但我注意到即使一切都按预期工作,背景渐变确实发生了变化,但它需要大约 20 秒才能重新呈现 collapsed/expanded 的子链接。我只在 SO 上看到过一个同样问题的问题,但我真的不明白如何解决它。问题是上下文提供者的所有 children 由于上下文中的状态改变而被重新呈现。 我会上传一个 codesandbox 示例,但我被要求不要上传,所以这里是最可重现的代码片段。我怎样才能使用该功能并防止 children 重新渲染,从而使一切运行得更快?请帮助:)

const Wrapper = styled('div')<{linksExpanded : boolean}>`
  background-image: linear-gradient(180deg, pink 0.2%, #FFFFFF 3%);
  background-color: white;
  ${({theme})=>theme.breakpoints.up('md')} {
    background-image: linear-gradient(180deg, pink 1.5%, #FFFFFF 5.5%);
  }
  ${({linksExpanded , theme})=>linksExpanded && `
    background-image: linear-gradient(180deg, pink 2.5%, #FFFFFF 4.25%);
    ${theme.breakpoints.up('md')} {
      background-image: linear-gradient(180deg, pink 6.5%, #FFFFFF 10%);
    }
  `}
`

const Home = () =>
{
  const { linksExpanded } = useContext(HomeDataContext);
  return (
            <Wrapper linksExpanded={linksExpanded}>
              <About />
              <Links />
              <Contacts />
              <Gallery />
            </Wrapper>
  );
};

export default Home;

export const Landing: FC = () => <PageSkeleton
        pageContent={
          <HomeDataContextProvider><Home /></HomeDataContextProvider>}
      />;


const Links = () => {
  const { currentCakes, linksExpanded, setLinksExpanded } = useContext(HomeDataContext);
  const renderLinks = () => (
    <S.List style={{maxHeight: !linksExpanded ? '0px' : '1000px'}}>
      {currentCakes.map((el: CakeDescription, key : number) => (
        <li key={el.name}>
          <a href='https://google.com'>Link</a>
        </li>
      ))}
    </S.List>
  );
  const handleLinksExpand = () => {
    setLinksExpanded(!linksExpanded);
  };
  return (
    <S.Container>
        <S.ExpandableLinkContainer>
          <ExpandableLink
            onClick={handleLinksExpand}
            open={linksExpanded}
            title='Cakes'
          />
        </S.ExpandableLinkContainer>
        {renderLinks()}
    </S.Container>
  );
};
export default Links;

上下文终于:

export type HomeDataState = {
  currentCakes: Array<CakeDescription>;
  setCurrentCakes?: any;
  linksExpanded: boolean;
  setLinksExpanded?: any;
};

const initialValue: HomeDataState = {
  currentCakes: [],
  linksExpanded: false,
};

export const HomeDataContext = createContext(initialValue as HomeDataState);

export const HomeDataContextProvider: FC = ({ children }) => {
  const [linksExpanded, setLinksExpanded] = useState(initialValue.linksExpanded);
  const [currentCakes, setCurrentCakes] = useState<CakeDescription[]>(initialValue.cakes);
  const fetchCakes = async () => {
    setCurrCakes(
      await myAxiosConstruct.get(process.env.api);
    );
  };
  useEffect(() => {
    fetchCakes();
  }, []);
  
  return (
    <HomeDataContext.Provider
      value={{
        currentCakes,
        linksExpanded,
        setLinksExpanded,
      }}
    >
      {children}
    </HomeDataContext.Provider>
  );
};

由于您的 Home 组件正在使用 HomeDataContext,无论 属性 更新什么,React 都会重新渲染整个组件,包括每个子组件(Wrapper, About, Links, Contact, Gallery) 所以这是正确的行为。

我们在这里可以做的是将 Context 一分为二:LinksContextCakesContext,它们都有各自的 Provider,然后重构应用程序如下:

  1. renderLinks() 现在是一个组件 (Cakes),只有在更新 currentCakes 时才会重新渲染。以前在每次状态更改时都会重新呈现。
  2. 通过将 Cakes 包装在 memo 中,我们可以防止在 Home 更新时 React 重新渲染子项。
  3. 我们也应该在 ContactsGallery 组件中使用 memo 以防止相同的重新渲染。

试试吧,希望对你有帮助! 可以在此处找到有关如何优化和良好做法的完整说明:React Context, All in One

const Wrapper = styled('div')<{linksExpanded : boolean}>`
  background-image: linear-gradient(180deg, pink 0.2%, #FFFFFF 3%);
  background-color: white;
  ${({theme})=>theme.breakpoints.up('md')} {
    background-image: linear-gradient(180deg, pink 1.5%, #FFFFFF 5.5%);
  }
  ${({linksExpanded , theme})=>linksExpanded && `
    background-image: linear-gradient(180deg, pink 2.5%, #FFFFFF 4.25%);
    ${theme.breakpoints.up('md')} {
      background-image: linear-gradient(180deg, pink 6.5%, #FFFFFF 10%);
    }
  `}
`

export const Home = () =>
{
  const { linksExpanded, setLinksExpanded } = useContext(LinksContext);
  return (
            <Wrapper linksExpanded={linksExpanded}>
              <About />
              <Links linksExpanded={linksExpanded} setLinksExpanded={setLinksExpanded} />
              <Contacts />
              <Gallery />
            </Wrapper>
  );
};

export const Landing = () => <PageSkeleton
        pageContent={
          <HomeDataContextProvider>
            <Home />
          </HomeDataContextProvider>
        } />;

export const Cakes = memo(() => {
  const { currentCakes } = useContext(CakesContext)

  return currentCakes.map((el, key) => (
    <li key={el.name}>
      <a href='https://google.com'>Link</a>
    </li>
  ))
})

export const Links = ({ linksExpanded, setLinksExpanded }) => {
  const handleLinksExpand = () => {
    setLinksExpanded(!linksExpanded);
  };

  return (
    <S.Container>
        <S.ExpandableLinkContainer>
          <ExpandableLink
            onClick={handleLinksExpand}
            open={linksExpanded}
            title='Cakes'
          />
        </S.ExpandableLinkContainer>
        <S.List style={{maxHeight: !linksExpanded ? '0px' : '1000px'}}>
          <Cakes/>
        </S.List>
    </S.Container>
  );
};