React 中, 为什么要用 Context? 直接使用全局变量不是更方便吗?

2022-05-07 14:19:05 +08:00
 FaiChou

未免有点标题党, 且先看我写的这个 demo:

import React from 'react';
let obj = null;
let listener = null;
function getObj() { return obj; }
function setListener(l) { listener = l; }
function setObj(o) {
  obj = o;
  if (listener) { listener(o); }
}
function useObj() {
  const [, forceUpdate] = React.useReducer(x=>x+1,0);
  React.useEffect(() => {
    setListener(() => forceUpdate())
  }, [])
  return getObj();
}
function Component1() {
  return (
    <div>
      <button onClick={() => {setObj(Math.random())}}>setrandom</button>
    </div>
  )
}
function Component2() {
  const o = useObj();
  return (
    <div>
      {o}
    </div>
  )
}
export default function App() {
  return (
    <div>
      <Component1 />
      <Component2 />
    </div>
  );
}

先说下 Context 一个缺点, 当数据改变时, 在 Context.Provider 下的节点都会重新执行, 这样很多不用其数据的节点也会被打扰, 昨天的帖子讨论过: 用 Context+Hooks 替代 Redux. 认真看了下大家的评论, 要么是用 memo 来固定住不想被打扰的组件, 要么使用一个订阅模式来刷新并通知.

与其这么麻烦, 不如直接用上面代码中的方法. 毕竟 Context 可以看作是一个全局的数据, 任意节点想使用这个数据时候, 还是需要 import 这个 Context.

上面代码和 redux 很像, 有一个 listener, 但 redux 需要靠 connect 绑定组件来订阅刷新, connect 利用了 Context.Provider+订阅+useSyncExternalStore 这个 API 来实现的.

还不如直接简简单单使用上面这种方式, 当节点使用全局数据时候, 使用自定义 hook 插一个 listener 进去, 当数据变动, 进行 forceUpdate. 这样也会避免了牵一发动全身的全部刷新, 只有使用 useObj() 的组件才会被刷新.

ps. 我写的项目少, 只是看文档时候产生的一点想法. 求大佬指正

3474 次点击
所在节点    React
12 条回复
codefever
2022-05-07 14:24:46 +08:00
没有 Context 的时候父组件向子组件传递 props 属性只能在组件树上自上而下进行传递,但是有些属性并不是组件树的每层节点都有相同的需求,这样我们再逐层传递 props 就显得代码很繁琐笨重。
noe132
2022-05-07 14:31:13 +08:00
假设你写了个第三方库,一些组件依赖一些父组件的状态,但它们不是直接父子关系,而且可能被用户随意组合,这时候 context 就是非常好的工具
XCFOX
2022-05-07 15:08:00 +08:00
你说的很对,用全局变量再加上 render-optimized 是个不错的方案,valtio 就是这么干的
https://github.com/pmndrs/valtio
XCFOX
2022-05-07 15:15:39 +08:00
转念一想,其实大家早就看不惯 React 这种函数式的贪婪更新机制。
所以后来的 Vue3 、Svelte 、Solid 都是监听变化按需更新,可以说它们比 React 更 reactive ,性能比 React 好不少,也没有到处 useMemo 、memo 的烦恼。
vue3 是我觉得最舒服的,reactive 对象可以作为全局变量存在于组件之外,这样极大方便了组件间通信。不过话说回来,全局变量还是得小心地用,不然会有内存驻留的风险。
yyfearth
2022-05-07 15:50:31 +08:00
@FaiChou 用 Context 而不是 Global
如果你是写可复用组件 而不是你例子里面这种单例组件 你就完全没办法用 global

用 Context 本来就是要避免平凡的更新里面的东西 如果只需要取值 不需要刷新 可以用 Context 传递 Ref

我写全局单例组件的时候 一般也用 Global 的多 但是可复用组件 就不能用 Global 了
yyfearth
2022-05-07 15:53:00 +08:00
@XCFOX React 的 virtual dom 就是那个时代的产物
那个时代没有现在这么强大的工具链可以做到 Svelte 那样
要兼容浏览器也没有 Proxy 来做到原生 reactive

有了强大的编译器和现代浏览器 确实完全不需要 virtual dom 了
rioshikelong121
2022-05-07 15:55:22 +08:00
redux 也支持 hook API (useDispatch, useSelector) 啊,也不需要显式使用 connect ,我觉得这就够了。
比你个方案好的是支持 middleware 这样的机制。
FaiChou
2022-05-07 15:59:25 +08:00
@rioshikelong121 的确, 代替的是 react-redux 而没有代替 redux, 因为 redux 支持中间件.
gouflv
2022-05-07 17:03:17 +08:00
context 是可以分层级管理的,作用域更小,同时还具有生命周期
bojue
2022-05-07 23:50:05 +08:00
@codefever Content 能避免逐层传递呀
chenliangngng
2022-05-08 12:30:54 +08:00
登录状态我这里就是全局变量+闭包控制的,没有用 redux 也没有用 hooks 也没有用 context 。原因很简单,我希望这个状态和 react 生态解耦。一些其他需要用到登录状态的中间件,我没必要用 react 的方式去写

看你业务场景吧,适合就是好的。微前端场景,完全可以全局变量+闭包;全局状态的场景,可以用 redux 也可以用 context ,而且尽量减少全局状态,尽量减少状态,少用状态管理,因为这会让项目指数级复杂化;一个模块的状态,用 context 是最好的,在模块最顶级文件使用,可以保证其他模块不去用,不需要 props 传参
shiye515
2022-05-08 22:42:42 +08:00
自己一个人开发的时候随便,很多人一起合作的时候根本找不到全局变量是被谁在什么时候改了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/851357

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX