前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React Context中的性能优化

React Context中的性能优化

原创
作者头像
Mirone
修改2020-11-23 16:55:53
1.9K0
修改2020-11-23 16:55:53
举报
文章被收录于专栏:MironeMirone

许多人将React Context用作某种内置的redux。

Jack就是其中之一,

他将所有全局状态合并到一个大的对象中,得到一个'单一数据源',并把它塞进provider。

然后他找到子组件,调用useContext并挑选需要的属性。

所有事情看起来都很完美,直到有一天,他发现他的应用慢到难以使用。

一个糟糕的例子

考虑以下代码,它也许是React context的最糟实践了。

代码语言:txt
复制
const Ctx = React.createContext();

const SideMenu = () => {
  const { setHideSideMenu, hideSideMenu } = useContext(Ctx);
  return (
    <aside>
      <Menu hide={hideSideMenu} />
      <button onClick={() => setHideSideMenu(x => !x)}>toggle</button>
    </aside>
  );
};

const UserDashBoard = () => {
  const { user, setUser } = useContext(Ctx);
  React.useEffect(() => {
    fetchUser().then((data) => setUser(data.user));
  }, []);
  
  return <User username={user} />
};


const App = () => {
  const [user, setUser] = React.useState('');
  const [hideSideMenu, setHideSideMenu] = React.useState(false);
  const [clock, setClock] = React.useState(Date.now());

  React.useEffect(() => {
    const interval = setInterval(() => {
      setClock(Date.now())
    }, 1000)
    return () => {
      clearInterval(interval);
    }
  }, []);
  
  return (
    <Ctx.Provider value={{
      user,
      setUser,
      hideSideMenu,
      setHideSideMenu,
    }}>
      <Clock time={clock} />
      <SideMenu />
      <UserDashBoard />
    </Ctx.Provider>
  );
}

让Context值的更新可控

这里的第一个问题是每一个context的consumer每秒都会收到一个更新通知。

clock状态会导致App组件的更新,使一个新的Ctx.Provider的值被创建。

(如果你无法理解这种行为,也许我的前一篇博客能够帮助到你。)

所以如果你需要将对象或数组当作context的值,

请使用类似useMemouseReducer之类的方式来避免不必要的创建。

代码语言:txt
复制
const App = () => {
  const [user, setUser] = React.useState('');
  const [hideSideMenu, setHideSideMenu] = React.useState(false);

  const ctx = React.useMemo(() => ({
    user,
    setUser,
    hideSideMenu,
    setHideSideMenu,
  }), [user, hideSideMenu]);

  ...

  return <Ctx.Provider value={ctx}>...</Ctx.Provider>;
}

记住你的选择

子组件可能只使用context中的一部分值,

然而context的值是作为整体来更新的。

如果你的组建需要高额的成本来重渲染,

记住你选择的值可能是一个好的选择。

例如, 如果我们想记住SideMenu组件的选择,

我们有两个选项:

  1. 将组件拆分为两个并对内部的组件调用memo
代码语言:txt
复制
const SideMenuInner = React.memo(({ setHideSideMenu, hideSideMenu }) => {
  return (
    <aside>
      <Menu hide={hideSideMenu} />
      <button onClick={() => setHideSideMenu(x => !x)}>toggle</button>
    </aside>
  );
});

const SideMenu = () => {
  const { setHideSideMenu, hideSideMenu } = React.useContext(Ctx);
  return (
    <SideMenuInner
      setHideSideMenu={setHideSideMenu}
      hideSideMenu={hideSideMenu} />
  );
};

我们可以抽象出一个HOC来做这件事:

代码语言:txt
复制
const ConsumeWithSelector = (Component, context, selector) => {
  const ctx = selector(React.useContext(context));
  return React.memo(props => <Component {{ ...props, ...ctx }} />);
}
  1. 在组件中使用useMemo方法。
代码语言:txt
复制
const SideMenu = () => {
  const { setHideSideMenu, hideSideMenu } = useContext(Ctx);
  return React.useMemo(() => (
    <aside>
      <Menu hide={hideSideMenu} />
      <button onClick={() => setHideSideMenu(x => !x)}>toggle</button>
    </aside>
  ), [hideSideMenu, setHideSideMenu]);
};

拆分Context

当使用context时我们不应该尝试去构建类似"单一数据树"之类的东西,

它将让应用非常难以优化。

对于大多数场景,context可以按职责拆分成多个。

例如,在之前的例子中,我们可以把context拆分成

HideSideMenuCtxUserCtx,

甚至拆分成HideSideMenuState, HideSideMenuSetter, UserStateUserSetter

代码语言:txt
复制
const App = () => {
  const [user, setUser] = React.useState('');

  ...

  return (
    <UserState.Provider value={user}>
      <UserSetter.Provider value={setUser}>
        ...
      </UserSetter.Provider>
    </UserState.Provider>
  );
}

然而,拆分得太碎可能反而导致应用难以维护。

在这之中没有银弹。

我们应该根据自己的场景来做出取舍。

然而,我们至少需要能够预期不同的策略将有怎样的表现。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一个糟糕的例子
  • 让Context值的更新可控
  • 记住你的选择
  • 拆分Context
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档