首页 > 解决方案 > React Context API 的 setter 工作很慢

问题描述

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

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

标签: javascriptreactjstypescriptstatereact-context

解决方案


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

我们在这里可以做的是将 Context 分成两部分:LinksContextCakesContext,分别使用各自的 Provider ,然后重构应用程序如下:

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

试试看,希望对你有帮助!可以在此处找到有关如何优化和良好实践的完整说明: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>
  );
};

推荐阅读