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 一分为二:LinksContext
和 CakesContext
,它们都有各自的 Provider,然后重构应用程序如下:
renderLinks()
现在是一个组件 (Cakes
),只有在更新 currentCakes
时才会重新渲染。以前在每次状态更改时都会重新呈现。
- 通过将
Cakes
包装在 memo
中,我们可以防止在 Home
更新时 React 重新渲染子项。
- 我们也应该在
Contacts
和 Gallery
组件中使用 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>
);
};
我的目标是实现这样的功能,例如根据 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 一分为二:LinksContext
和 CakesContext
,它们都有各自的 Provider,然后重构应用程序如下:
renderLinks()
现在是一个组件 (Cakes
),只有在更新currentCakes
时才会重新渲染。以前在每次状态更改时都会重新呈现。- 通过将
Cakes
包装在memo
中,我们可以防止在Home
更新时 React 重新渲染子项。 - 我们也应该在
Contacts
和Gallery
组件中使用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>
);
};