Why React Contexts Are Great and Why We Didn’t Use Them
(还是翻译)
前言
Web 应用程序维护状态以实现更高级的用户交互,当我们将电子邮件输入网站时,我们希望该网站能够记住这个电子邮件,而不是在显示的时候一遍又一遍地重复输入它。
在 React 渲染中记住信息的一种方法是使用状态,特别是 useContext 钩子。我查看了 useContext 并开始在项目中使用它们,但最终改为了使用组件传参。这是我在这个过程中学到的。
什么是 React Contexts?
React Contexts 允许读取和写入状态对象,以任何形式存入 useContext 挂钩,并通过 provider 组件在任何组件层级结构中访问。状态可以是任何变量和函数的集合。
provider 是一个 React 组件,它设置初始状态并允许所有后代访问改状态。这种简单性使得 React Contexts 变得很灵活。
它还允许在 Web 应用程序中使用多个不同的Contexts,具体取决于应用程序中呈现的组件。你可以使用状态本身的一部分函数来修改 React Contexts 中的变量。
使用 React Contexts 的好处包括易于导入,并且能够将 useContext 挂钩添加到任何组件而不需要修改传参。
我们在哪发现的问题?
我们了解了useContext,并开始在我们的项目中使用它,以存储用户工作流中所有步骤的信息。我们添加了工作流所需的信息,并很快看到Contexts状态随着函数和变量增多而扩展。这本身并不是一个大问题。状态类型都在一个文件中,保持可读。
问题是,修改当前工作流变量不需要任何权限。当组件导入useContext时,它会获得完全的读写权限,工作流难以判断是否是意外操作导致变量修改的操作。在开发前的四周内,该状态发生了有趣的行为,我们可以单机一个按钮,这个更改将会通过多个步骤和组件进行传播。然而,即使小团队中每个人对组件库都很熟悉,这样也会使代码变得很难维护。
我们做了什么?
我们决定转移到一个状态对象,该对象从一个组件传递到另一个组件。好处是我们可以在每一层进行检查,以确保只给暴露给组件所需要的状态和功能。这有利于我们缩小每个组件的职责,提高代码可读性。
我们发现React Contexts支持简单数据共享。然而这个工具需要有规律地来有效控制Contexts中的内容。我们团队是否因为没有使用React Contexts而失去了机会?这是我们理解上的问题还是工具本身的问题?请在评论区告诉我,并且我们始终尝试为每个项目找到正确的模式。
留言
评论1
谢谢你的分享,使用全局Contexts听起来更像是一种反面模式(指的是在实践中明显出现但又低效或是有待优化的设计模式)。我发现有价值的是使用多个Contexts,每个Contexts都专注于某个域。这样组件将只获得所需要的东西。
评论2
在我看来,如果不使用 setter 函数,你反对的观点就站不住脚。
比较好的方案是只允许父组件去修改状态。
如果你只是希望某些不同的组件能设置状态, 你也可以将Contexts拆成状态和 setter Contexts。只要 setter 引用稳定,只更改状态的组件只需要访问后者,也不会由于状态更新而重新渲染。这样你可以通过查找 setter 而锁定到更改状态的子级。
这可能在某些项目中不起作用,但我觉得你重新引入了Contexts避免的东西:prop-drilling(在 React 中经常会碰到父组件一层一层地传参到子组件,这种行为叫做prop-drilling,drilling是下钻的意思)。
评论3
从我对你项目的有限理解来看,我不太确定你为什么选择在项目中用Contexts API。听起来你构建了很多用户表单,但没有人主张在本地组件或工作流中使用Contexts。你这样说是因为这在代码库中是不长久的。Contexts 不是用来替代状态的,它也不是用来替代像 redux 这种状态管理工具的,而是用来替代深层组件树,或者不相干的组件(无共同父级),以便轻松访问共同的状态值。
评论4
这根本不是Contexts的使用方式,官方文档会在你使用之前就告诉你这一点。不太清楚博客里是如何保证这一点的,因为“我们尝试了一个工具,却不知道的他是如何工作的,当我们不知道它是如何工作时,我们就放弃了它”。
评论5
Immense swag here ngl (不知道这个咋翻译)
评论6
所以你用 useContext 取代了 Props下钻……
我认为 Contexts 是行的,但不是直接以原生的方式使用,因为它会导致没必要的re-render,我们需要以冗余的方式包装它,这样我们就可以使用 memoization,因为 useContext 将在组件内使用,它不关心 useMemo
评论7
实际上,我找到了一种修改useReducer和createContext的方法,以便只为特定组件提供我想要读取的属性。
我注意到,一旦在具有全局状态的组件上使用Context,它将在每次更改状态中的一个项时重新读取。这是因为当你创建一个上下文时,它有一个键“Provider”,和键“Consumer”,你需要删除Consumer和Provider,并为它们放置自己的代码,以处理注册自己的监听器和何时更新,而不是React自己做这项工作。当reducer更新状态时,需要更改useReducer。您可以通过在回调认为必要时强制更新来实现这一点
个人总结
以为是技术分享,但实际上是针对React的Contexts使用上的讨论,这个也有使用过,但确实【评论6】的说法是现在项目插件中比较主流的,如开发中使用的useModel,是umi框架中的一个Hooks。(诸如其他的插件,我也还没去了解过,有缘再分享这种插件。)