React Context API 的 setter 工作很慢

Posted

技术标签:

【中文标题】React Context API 的 setter 工作很慢【英文标题】:React Context API's setters work very slow 【发布时间】:2021-10-04 22:34:51 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

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

    renderLinks() 现在是一个组件 (Cakes),只有在更新 currentCakes 时才会重新渲染。以前每次状态更改都会重新渲染。 通过将 Cakes 包裹在 memo 中,我们可以防止在 Home 更新时 React 重新渲染子节点。 我们也应该在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>
  );
;

【讨论】:

效果很好,非常感谢!再次感谢您的精彩解释!

以上是关于React Context API 的 setter 工作很慢的主要内容,如果未能解决你的问题,请参考以下文章

React API

React Context : 从 API 获取数据并在 React 组件中发生某些事件时调用 API

[React] Use the new React Context API

React Context:提供 Api Manager 的实例

从 React 的 Context API 组件重定向

React 16 的 Portal API 是否要取代 Context API?