React状态管理的一些思考(上篇)

5,090 阅读5分钟

众所周知,react可以抽象为一个函数: UI = render(data) 。这意味着我们可以专注于构建data,而不用考虑如何更新视图。 但随着SPA(single page application)单页面应用复杂度的提高,状态也会变大,管理状态的难度也会增加。 为了更好的管理应用的状态,或许会考虑状态使用react状态管理的库,但是该考虑和如何选择?

状态

首先我们要知道什么是状态,举个例子:当我们和我们的应用交互的时候,比如我们点击了一个button,会弹一个对话框,或者UI会有变化,或者发送一个请求。 随着点击事件,我们的应用有对应的响应。可以肯定的是这个时刻的 状态 跟点击之前的不一样了,我们应用进入了一个新的状态。比如说标志是否打开弹窗的变量、AJAX请求的数据,这些变量我们都可以称之为状态。 广泛的讲,应用的状态就是这些变量。它可以是React中使用useState(函数组件)或者this.state(类组件)保存的数据,也可以保存第三方状态管理库里。

前端状态管理设计——优雅与妥协的艺术

抉择

在React中如果我们需要储存状态,我们一般会选择以下三种途径方式:

  • 储存在当前组件里面,函数组件里面可以使用useState、useReducer这些hooks,类组件可以存在this.state里面。

  • 储存在第三状态管理库的仓库(store)里,相关的库有:Redux、MobX、Recoil....

  • 自己维护状态,例如我们可以直接存在window里。。。

相应的如果改变状态,我们可以:

  • 当我们调用useState、useReducer、this.setState的时候,React将会自动重新渲染,这是React提供给我们的机制。

  • 如果我们使用第三方库,例如Redux、MobX、Recoil等。。。他们会在适当的时机,然后调用Ract的API触发重新渲染。

  • 如果自己维护了数据,例如保存在window里,就需要自己实现状态改变触发重新渲染的逻辑,但是不建议。 其实react并不在意你把数据放在哪,react处理的只是data -> UI的映射。

在绝大多数情况下,我们使用React提供的组件级别的状态管理API就完全足够了。

简单状态共享场景

但是其实如果我们只用React提供的API,我们常常会遇到以下状态共享问题:

  1. prop drilling问题,即props传递的层级过深,我们需要手动传递props,比较麻烦。

  2. 树上距离较远的两个组件需要共享状态,在react中一个做法是,提取props到他们相邻最近的祖先节点中,回到第一种情况。

  3. 子组件向父组件传递数据,只能通过回调的方式。

造成这些问题的根本原因还是React的 单向数据流 的设计思想,React恨不得把所有状态都保存在顶层。 当然单向数据流的设计有利有弊,好处就是每个组件的依赖(数据流入)都很清晰,坏处就是状态共享问题。 于是,React提出了Context API。但是我们需要明确的是 Context API解决的其实是依赖注入的问题 ,它本身并不是状态管理工具,具体可以看看这篇 Blogged Answers: Why React Context is Not a "State Management" Tool 事实上有基于Context API + Hooks封装库。 jamiebuilds/unstated-next源码 十分简单。 看过源码也就能发现,其实是会有性能问题的,更改状态后,所有使用context的组件都会重新渲染。所以他们只适用于需要快速上手、对性能要求不高的 简单场景

网络密集型的场景

这种场景常见于中后台项目,需要频繁从服务端请求数据,请求后的数据需需要进行加载状态、缓存和过期管理,这种场景可以使用React-query或者SWR。 以React-query特性来说:

  • 使用hooks API

  • 封装了许多前端异步请求的逻辑

  • 数据缓存控制,自动更新数据

  • 重复请求合并

  • 分页和延迟加载

  • 。。。

用react-query解决你一半的状态管理问题

复杂场景

我觉得可能真正的 复杂场景 有:

  • 用户的使用方式复杂

  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)

  • 多个用户之间可以协作

  • 与服务器大量交互,或者使用了WebSocket

  • View要从多个来源获取数据

  • 。。。

总而言之,在多状态、多交互、多数据源、数据耦合等复杂场景中,就需要使用 复杂状态管理库 。 按照设计思想,可以大概把他们分为四类:

  • 单向数据流:Redux, Zustand

  • 响应式数据流/Proxy:Mobx, Valtio

  • 原子模式Atomic:Recoil, Jotai

  • Stream: rxjs(Reactive Extension JavaScript)

这里要说一下Redux、Mobx等都是比较偏底层的并且是框架无关的,他们常常有框架关联的库如react-redux,mobx-react。

总结

在React生态里面,状态管理的库有很多,我们需要根据自己的需求选择适合业务场景的状态管理库。 接下来的文章将会介绍React里使用的比较多的几个主流状态管理库的主要思想和原理。