React是管理应用程序状态所需的全部内容

管理状态可以说是任何应用程序中最难的部分。这就是为什么有这么多的状态管理库可用,而且每天都有更多的库出现(甚至有些库是建立在其他库之上的。。。npm上有数百个“更简单的Redux”的摘要)。尽管状态管理是一个很难解决的问题,但我认为,使之如此困难的一个原因是我们经常过度设计解决问题的方法。

有一个状态管理解决方案,我个人一直在使用React,随着React钩子的发布(以及对React上下文的大量改进),这种状态管理方法已经大大简化。

我们经常把React组件当作乐高积木来构建我们的应用程序,我想当人们听到这些时,他们会认为这不包括状态方面。我个人解决状态管理问题的方法背后的“秘密”是考虑应用程序的状态如何映射到应用程序的树结构。

redux如此成功的原因之一是react redux解决了支柱钻井问题。事实上,通过简单地将组件传递到某种神奇的connect函数中,就可以在树的不同部分共享数据,这一点非常棒。它对reducer/action creators/etc.的使用也很棒,但我相信redux的普遍存在是因为它解决了开发者的道具钻削痛点。

这就是我只在一个项目中使用redux的原因:我经常看到开发人员把他们所有的状态都放到redux中。不仅是全局应用程序状态,还包括本地状态。这会导致很多问题,尤其是当您维护任何状态交互时,它涉及到与reducer、action creator/type和dispatch调用的交互,这最终导致必须打开许多文件并在头脑中跟踪代码,以确定发生了什么以及它对代码库其余部分的影响。

很明显,对于真正全局的状态来说,这是很好的,但是对于简单状态(比如模态是开放的还是表单输入值状态),这是一个大问题。更糟糕的是,它的规模并不是很好。应用程序越大,这个问题就越难解决。当然,您可以连接不同的reducer来管理应用程序的不同部分,但是间接遍历所有这些action creator和reducer并不是最佳的。

将所有应用程序状态都放在一个对象中也会导致其他问题,即使您没有使用Redux。当一个反应<提供程序上下文>获取一个新值,使用该值的所有组件都将更新并必须呈现,即使它是只关心部分数据的函数组件。这可能会导致潜在的性能问题。(React reduxv6也尝试使用这种方法,直到他们意识到它不能正确地与hooks一起工作,这迫使他们在v7中使用不同的方法来解决这些问题。)但我的观点是,如果您的状态在逻辑上更为分离,并且位于React树中更靠近它的位置,那么就不会出现这个问题。

这是真正的关键,如果您使用React构建应用程序,那么您的应用程序中已经安装了状态管理库。你甚至不需要npm安装(或纱添加)它。它不需要为用户额外增加字节,它与npm上的所有React包集成,而且React团队已经对它进行了很好的记录。它自己反应。

React是一个状态管理库

当您构建React应用程序时,您将组装一组组件,以组成一个组件树,从<App/>开始,到<input/>、<div/>和<button/>结束。您不需要在一个中心位置管理应用程序呈现的所有低级复合组件。相反,你让每个单独的组件来管理它,它最终成为构建UI的一种非常有效的方法。你也可以用你的状态来做这件事,而且很可能你今天也会这样做:

function Counter() {
const [count, setCount] = React.useState(0)
const increment = () => setCount(c => c + 1)
return <button onClick={increment}>{count}</button>
}
function App() {
return <Counter />
}

请注意,我在这里所说的一切也适用于类组件。钩子只是让事情变得简单一点(特别是我们马上要讨论的上下文)。

class Counter extends React.Component {
state = {count: 0}
increment = () => this.setState(({count}) => ({count: count + 1}))
render() {
return <button onClick={this.increment}>{this.state.count}</button>
}

“好吧,Kent,在一个组件中管理一个状态元素是很容易的,但是当我需要跨组件共享状态时,您会怎么做?例如,如果我想这样做呢:“

function CountDisplay() {
// where does `count` come from?
return <div>The current counter count is {count}</div>
}
function App() {
return (
<div>
<CountDisplay />
<Counter />
</div>
)

“计数是在<Counter/>中管理的,现在我需要一个状态管理库从<CountDisplay/>访问该计数值并在<Counter/>中更新它!”

这个问题的答案和反应本身一样古老(旧?)在我记事的时候,我就在文档里写了很久:提升状态

“提升国家”合法地回答了React中的国家管理问题,这是一个坚如磐石的答案。以下是如何将其应用于这种情况:

function Counter({count, onIncrementClick}) {
return <button onClick={onIncrementClick}>{count}</button>
}
function CountDisplay({count}) {
return <div>The current counter count is {count}</div>
}
function App() {
const [count, setCount] = React.useState(0)
const increment = () => setCount(c => c + 1)
return (
<div>
<CountDisplay count={count} />
<Counter count={count} onIncrementClick={increment} />
</div>
)
}

我们刚刚改变了谁对我们的国家负责,这真的很简单。我们可以一直提升状态,直到我们的应用程序的顶端。

“当然肯特,好吧,但是道具钻的问题呢?”

好问题。您的第一道防线就是改变构建组件的方式。利用组件组成。也许不是:

function App() {
const [someState, setSomeState] = React.useState('some state')
return (
<>
<Header someState={someState} onStateChange={setSomeState} />
<LeftNav someState={someState} onStateChange={setSomeState} />
<MainContent someState={someState} onStateChange={setSomeState} />
</>
)
}

你可以这样做:

function App() {
const [someState, setSomeState] = React.useState('some state')
return (
<>
<Header
logo={<Logo someState={someState} />}
settings={<Settings onStateChange={setSomeState} />}
/>
<LeftNav>
<SomeLink someState={someState} />
<SomeOtherLink someState={someState} />
<Etc someState={someState} />
</LeftNav>
<MainContent>
<SomeSensibleComponent someState={someState} />
<AndSoOn someState={someState} />
</MainContent>
</>
)
}

如果这不是很清楚(因为它是超级做作),迈克尔杰克逊有一个伟大的视频,你可以看,以帮助澄清我的意思。

不过,最终,即使是组合也不能为您做到这一点,所以您的下一步是跳转到React的Context API中。这实际上是一个“解决方案”,但很长一段时间以来,这个解决方案是“非官方的”。正如我所说,很多人求助于react redux,因为它使用我所指的机制解决了这个问题,而不必担心react文档中的警告。但是,既然context是React API的一个官方支持的部分,那么我们可以直接使用它而没有任何问题:

// src/count/count-context.js
import * as React from 'react'
const CountContext = React.createContext()
function useCount() {
const context = React.useContext(CountContext)
if (!context) {
throw new Error(`useCount must be used within a CountProvider`)
}
return context
}
function CountProvider(props) {
const [count, setCount] = React.useState(0)
const value = React.useMemo(() => [count, setCount], [count])
return <CountContext.Provider value={value} {...props} />
}
export {CountProvider, useCount}
// src/count/page.js
import * as React from 'react'
import {CountProvider, useCount} from './count-context'
function Counter() {
const [count, setCount] = useCount()
const increment = () => setCount(c => c + 1)
return <button onClick={increment}>{count}</button>
}
function CountDisplay() {
const [count] = useCount()
return <div>The current counter count is {count}</div>
}
function CountPage() {
return (
<div>
<CountProvider>
<CountDisplay />
<Counter />
</CountProvider>
</div>
)
}

注意:这个特定的代码示例非常做作,我不建议您使用上下文来解决这个特定的场景。请阅读支柱钻井,以获得更好的理解为什么支柱钻井不一定是一个问题,往往是可取的。不要太快接触上下文!

这种方法的酷之处在于,我们可以将更新状态的常用方法的所有逻辑放在useCount钩子中:

function useCount() {
const context = React.useContext(CountContext)
if (!context) {
throw new Error(`useCount must be used within a CountProvider`)
}
const [count, setCount] = context
const increment = () => setCount(c => c + 1)
return {
count,
setCount,
increment,
}
}

你也可以很容易地用这个来说明:

function countReducer(state, action) {
switch (action.type) {
case 'INCREMENT': {
return {count: state.count + 1}
}
default: {
throw new Error(`Unsupported action type: ${action.type}`)
}
}
}
function CountProvider(props) {
const [state, dispatch] = React.useReducer(countReducer, {count: 0})
const value = React.useMemo(() => [state, dispatch], [state])
return <CountContext.Provider value={value} {...props} />
}
function useCount() {
const context = React.useContext(CountContext)
if (!context) {
throw new Error(`useCount must be used within a CountProvider`)
}
const [state, dispatch] = context
const increment = () => dispatch({type: 'INCREMENT'})
return {
state,
dispatch,
increment,
}
}

这为您提供了极大的灵活性,并将复杂性降低了一个数量级。在这样做的时候,要记住以下几点:

  • 并非应用程序中的所有内容都需要处于单个状态对象中。保持逻辑上的分离(用户设置不必与通知处于同一上下文中)。使用此方法将有多个提供程序。

  • 不是所有的上下文都需要全局访问!让状态政府尽可能靠近需要的地方。

关于第二点的更多信息。你的应用程序树可能如下所示:

function App() {
return (
<ThemeProvider>
<AuthenticationProvider>
<Router>
<Home path="/" />
<About path="/about" />
<UserPage path="/:userId" />
<UserSettings path="/settings" />
<Notifications path="/notifications" />
</Router>
</AuthenticationProvider>
</ThemeProvider>
)
}
function Notifications() {
return (
<NotificationsProvider>
<NotificationsTab />
<NotificationsTypeList />
<NotificationsList />
</NotificationsProvider>
)
}
function UserPage({username}) {
return (
<UserProvider username={username}>
<UserInfo />
<UserNav />
<UserActivity />
</UserProvider>
)
}
function UserSettings() {
// this would be the associated hook for the AuthenticationProvider
const {user} = useAuthenticatedUser()
}

请注意,每个页面都可以有自己的提供程序,其中包含其下组件所需的数据。代码拆分对这种东西也“管用”。如何将数据导入每个提供程序取决于这些提供程序使用的钩子以及如何在应用程序中检索数据,但您知道从何处开始查找(在提供程序中)如何工作。

关于为什么这个托管是有益的,请查看我的“State colosition will make your React app faster”和“colocation”博客文章。有关上下文的更多信息,请阅读如何有效地使用React context

服务器缓存与UI状态

最后我想补充一点。状态有多种类型,但每种类型的状态都可以分为两种类型:

  • 服务器缓存—实际存储在服务器上的状态,我们将其存储在客户机中以便快速访问(如用户数据)。

  • UI状态—仅在UI中用于控制应用程序交互部分的状态(如模态isOpen状态)。

当我们把两者结合在一起时,我们犯了一个错误。服务器缓存与UI状态有着本质上不同的问题,因此需要进行不同的管理。如果你接受这样一个事实:你所拥有的根本不是状态,而是一个状态缓存,那么你就可以开始正确地思考它,从而正确地管理它。

当然,您可以使用自己的useState或useReducer在这里和那里使用正确的useContext来管理它。但请允许我帮你直截了当地说,缓存是一个非常困难的问题(有人说它是计算机科学中最难的问题之一),在这个问题上站在巨人的肩膀上是明智的。

这就是为什么我对这种状态使用并推荐react query。我知道我知道,我告诉过你不需要状态管理库,但我并不认为react query是状态管理库。我认为这是个藏匿处。这真是个好主意。看看!坦纳·林斯利是个聪明的小甜饼。

性能怎么样?

当你遵循上面的建议时,性能就很少是个问题了。尤其是当你遵循有关托管的建议时。但是,在某些用例中,性能可能会有问题。当您遇到与状态相关的性能问题时,首先要检查的是有多少组件由于状态更改而被重新呈现,并确定这些组件是否真的需要由于状态更改而重新呈现。如果是这样,那么perf问题不在管理状态的机制中,而是在渲染速度上,在这种情况下,需要加快渲染速度。

但是,如果您注意到有许多组件在没有DOM更新或需要的副作用的情况下进行渲染,那么这些组件将不必要地进行渲染。在React中,这种情况一直都会发生,而且它本身通常不是问题(您应该首先集中精力快速进行不必要的重新渲染),但是如果这真的是瓶颈,那么以下是一些在React上下文中使用state解决性能问题的方法:

  • 将你的状态划分为不同的逻辑部分,而不是在一个大的存储区中,这样对状态的任何部分进行一次更新都不会触发对应用程序中每个组件的更新。

  • 优化上下文提供程序

  • 把 jotai带进来

这又是一个库的建议。的确,有些用例React的内置状态管理抽象不太适合。在所有可用的抽象中,jotai对于这些用例是最有前途的。如果您想知道这些用例是什么,那么jotai很好地解决的问题类型实际上在 Recoil: State Management for Today's React - Dave McCabe aka @mcc_abe at @ReactEurope 2020一书中得到了很好的描述。Recoil和jotai非常相似(并且解决了相同类型的问题)。但根据我和他们的(有限)经验,我更喜欢jotai。

无论如何,大多数应用程序都不需要像recoil或jotai这样的原子状态管理工具。

结论

同样,这是你可以用类组件来做的事情(你不必使用钩子)。钩子使这变得容易得多,但是您可以用React 15来实现这一理念。尽可能保持状态的本地性,并且只有在支柱钻井成为问题时才使用上下文。这样做会使您更容易维护状态交互。


本文:http://jiagoushi.pro/node/1282

讨论:请加入知识星球【首席架构师圈】或者小号【jiagoushi_pro】或者QQ群【11107777】

微信公众号 【首席架构师智库】
适合物业仔细反复阅读。
精彩图文详解架构方法论,架构实践,技术原理,技术趋势。
我们在等你,赶快扫描关注吧。
微信小号 50000人社区,激烈深度讨论:企业架构,云计算,大数据,数据科学,物联网,人工智能,安全,全栈开发,DevOps,数字化.

QQ群 深度交流企业架构,业务架构,应用架构,数据架构,技术架构,集成架构,安全架构。以及大数据,云计算,物联网,人工智能等各种新兴技术。

视频号 【首席架构师智库】
1分钟快速了解架构相关的基本概念,模型,方法,经验。
每天1分钟,架构心中熟。

知识星球 向大咖提问,近距离接触,或者获得私密资料分享。 知识星球【首席架构师圈】
微信圈子 志趣相投的同好交流。 微信圈子【首席架构师圈】
喜马拉雅 路上或者车上了解最新黑科技资讯,架构心得。 【智能时刻,架构君和你聊黑科技】
知识星球 认识更多朋友,职场和技术闲聊。 知识星球【职场和技术】

谢谢大家关注,转发,点赞和在看。

「前端架构」使用React进行应用程序状态管理相关推荐

  1. vue:无法将“vue”识别为脚本_「前端架构」React和Vue -CTO的选择正确框架的指南...

    快速总结:为项目选择正确的javascript框架或库是CTO和项目经理的基本任务.然而,选择的范围很大程度上取决于几个因素,如项目时间.学习曲线.框架性能和团队规模.这篇文章旨在指导他们选择正确的j ...

  2. 「前端架构」React,Angular还是Vue,太难选了?看完秒懂。

    我们必须承认,我们在这些框架方面有着极不平等的经验.然而,我们将尽量保持公正,并弄清楚React何时可能不是最佳选择,但首先,简短介绍: React是Facebook维护的最流行的框架.所有的酷孩子, ...

  3. 「前端架构」React和Vue -CTO的选择正确框架的指南

    快速总结:为项目选择正确的javascript框架或库是CTO和项目经理的基本任务.然而,选择的范围很大程度上取决于几个因素,如项目时间.学习曲线.框架性能和团队规模.这篇文章旨在指导他们选择正确的j ...

  4. 「前端架构」Grab的前端学习指南

    原帖可以在Github上找到.未来的学习指南将在那里更新.如果你喜欢你正在阅读的东西,给它打一颗星吧! 公司是东南亚(SEA)领先的运输平台,我们的使命是利用公司最新的技术和人才,推动SEA前进.截至 ...

  5. 「前端工程化」该怎么理解?

    大家好,我是若川.今天分享一篇「前端工程化」的好文.非广告,请放心阅读.可点击下方卡片关注我,或者查看系列文章.今天发文比较晚,以往都是定时早上7:30发文,也不知道是不是有点早. 一.什么是前端工程 ...

  6. Serverless :让「前端开发者」走向「应用研发者」

    技术的成熟度源自大规模的实践,在 Java 领域,阿里将自身的实践源源不断的反哺给微服务技术体系:在 Node.js 领域,阿里正掀起了前所未有的前端革命浪潮,将实践反哺给  Serverless 技 ...

  7. dreamweaver 正则表达式为属性值加上双引号_「前端篇」不再为正则烦恼

    作者:李一二 转发链接:https://mp.weixin.qq.com/s/PmzEbyFQ8FynIlXuUL0H-g 前言 有不少朋友都为写正则而头疼,不过笔者早已不为正则而烦恼了.本文分享一些 ...

  8. 自定义变量 配置文件_「系统架构」Nginx调优之变量的使用(3)

    在上一篇文章「系统架构」Nginx调优之变量的使用(2)中我们介绍了自定义变量和内置变量,下面我们继续接着介绍Nginx中变量的可见性和动态内置变量. 变量的可见性 nginx中的变量虽然不全是全局变 ...

  9. 分布式系统关注点(14)——「弹性架构」详解

    如果第二次看到我的文章,欢迎右侧扫码订阅我哟~  ? 本文长度为3633字,建议阅读10分钟. 坚持原创,每一篇都是用心之作- 如果我们的开发工作真的就如搭积木一般就好了,轮廓分明,个个分开,坏了哪块 ...

最新文章

  1. C++ Primer 学习笔记(第四章:表达式)
  2. 谈谈Java中的volatile
  3. ORACLE告警日志
  4. python获取用户输入的数字_Python 将用户输入的数字 提取整数谢谢
  5. CMake 构建项目Android NDK项目基础知识
  6. influxdb mysql对比_InfluxDB和MySQL的读写对比测试
  7. linux qt计算器,QT 计算器小程序
  8. js练习——动态控制表格中得行
  9. 七、WGS84 UTM
  10. 电脑模拟收银机_模拟超市的收银系统
  11. css 魔方,css 3d旋转魔方
  12. 岌岌可危 中立之争决定云计算未来
  13. java spring 中 每小时一次_spring 定时任务的 执行时间设置规则
  14. 字符移位(将大写字母移到字符串尾部并不改变相对顺序)
  15. 怎么制作游戏脚本_怎么剪游戏视频?五步教你制作绝地求生击杀合集
  16. #734. 徐老师的养花装置
  17. IOS开发UI-------button
  18. sprite实现影子
  19. 项目管理_项目范围管理
  20. 洛谷P1035题解 [NOIP2002 普及组] 级数求和

热门文章

  1. AutoHotkey实现windows快捷键设置/映射
  2. 【智慧城市】交通路口摄像头转俯视图
  3. 海康的android sdk,AndroidStudio 集成海康威視 Android SDK,集成螢石Android SDK。
  4. linux arp攻击解决方法 测试很有效
  5. java 某天所在星期_Java中计算某一天所在周的星期一和星期天的日期函数
  6. 智能手机 3D 视觉之战:苹果不再一枝独秀,Android 全面崛起
  7. 房价暴跌是必然的-价值决定价格
  8. 基于springboot的多格式转PDF
  9. 砂积石的山水盆景制作
  10. c语言ifb0是否正确的是,EDA技术与Verilog-中国大学mooc-题库零氪