8

Redux你是个好人,只是我们不合适

 3 years ago
source link: https://zhuanlan.zhihu.com/p/347046116
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Redux你是个好人,只是我们不合适

当聊到 React 状态管理方案,很多人第一反应是 Redux

Redux 为什么这么有名,个人观点,2个原因:

  1. 出现时间早,当时社区还没有更好的状态管理解决方案
  2. React 核心团队光环加持。 Redux 的作者 「Dan」 开发初版 Redux 后便加入 React 团队。另一位联合作者 「Andrew」 也来自 React 核心团队
JzYJfab.jpg!mobile

合适的出现时机加上大名气,催生 Redux 相关生态在社区快速发展,成为很多前端团队标配。

当谈论状态管理时,通常在谈什么

当谈论 「状态管理」 时,一般会从 「广度」「深度」 两个方面来。

rARrUvN.jpg!mobile

广度上,在其之后涌现的解决方案,似乎都在对标 Redux ,提出自己独到的解决方案。比如:

  • 对标 Redux 的单向数据流, Mobx 使用双向数据绑定
  • 对标 Redux「全局状态」 理念, recoil 提出 「原子状态」 理念

深度上, Redux 社区不断拓展,涌现了基于 Redux 的中间件,比如 Redux-Saga

在中间件之上,又涌现了更全面的解决方案,比如基于 Redux-SagaDVA

除了这两个纬度,还有其他视角么?

其实,我们可以从问题的本质出发。

前端,需要哪些状态?

从页面交互角度看,状态来源分为两种:

  • IO操作缓存的数据
  • 用户交互的中间状态

IO操作缓存的数据

前端最常见的IO操作是从服务端请求数据。

如果没使用 「状态管理」 方案,常见方式是请求数据后保存在组件 state 中,如:

function App() {
  const [data, updateData] = useState(null);
  
  useEffect(() => {
    fetchData('/api/user').then(data => updateData(data))
  }, [])

  // 处理data
}

当使用 「状态管理」 方案如 Redux ,会将请求的数据序列化后保存在 「全局状态」 中。

用户交互的中间状态

交互的中间状态,比如 isLoadingisOpen ,同样保存在组件内部。

当是可复用组件、或状态需要跨组件层级传递,通常使用 Context API

再大范围的状态会使用 「状态管理」 方案。

可以看到,不管对于 「IO操作缓存的数据」 还是 「用户交互的中间状态」 ,常规方案是:一视同仁。

这就又回到了讨论 「广度」 (使用哪种状态)与 「深度」 (多深入的使用这种状态管理方案)。

但事实上,这两种状态的特性是不同的。

处理缓存的状态管理

对于第一种情况,不管是服务端请求、 localStorageindexedDB ,本质上说,都可以归类为 缓存

所以,相比 Redux 等常规状态管理方案,缓存处理方案可能更合适。

对于 缓存 ,常见的需求是:

  • 数据状态,加载中?加载完成?发生错误?
  • 缓存失效后的更新
  • 复用缓存数据

React 技术栈, SWRreact-query 都是优秀的解决方案。这里以 SWR 举例:

BVZ77zr.jpg!mobile

对于刚才的例子:

function App() {
  const [data, updateData] = useState(null);
  
  useEffect(() => {
    fetchData('/api/user').then(data => updateData(data))
  }, [])

  // 处理data
}

SWR 使用一个 useSWR 解决:

function App() {
  const { data, error } = useSWR('/api/user', fetcher)
  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

让我们来看 SWR 如何满足如上三个需求:

  • 数据状态:通过 useSWR 的返回值参数判断请求状态。比如 !error && !data 代表 「请求中」
  • 缓存失效后的更新: SWR 本身是 stale-while-revalidate 缩写,一种基于RFC 5861的缓存更新策略。
  • 复用缓存数据: SWR 会以 请求urlkey ,请求对应 promisevalue 缓存数据,达到多个重复请求复用缓存的目的。

处理用户交互的状态管理

对于 「用户交互」 产生的状态管理需求,比如:全局 modal 的开关状态,页面皮肤切换。

我们可以按项目类型、复杂度选择合适的状态管理方案。

ZZzeQry.jpg!mobile

横向来看,不同类型项目适合不同状态管理:

  • 前端逻辑很重的工具类项目(比如富文本编辑器),需要完备的 redoondo 逻辑, Redux「单向不可变数据流」 是不二的选择。
  • 后台管理系统,逻辑不复杂,但是繁琐。则 Mobx「双向数据绑定」 开发效率可能更高。

纵向来看,我们需要考量项目的复杂度:

类似官网、逻辑不复杂的 SPA 、个人项目, 「完全没必要」 使用额外的状态管理方案。原生 Context API 是你最佳的选择。

需要小团队合作的项目,复杂度不高的情况下, Context API 就能满足全部需要,只不过需要一点点写法上的规范约束团队同学。这时候可以选择 Unstated

uy2Ufqy.jpg!mobile

Unstated 核心代码只有40行,为项目带来规范的状态管理约束的同时不会引入额外的学习成本

只有对于更复杂的项目,才应该考虑 ReduxMobx 这类解决方案。

总结

对于 「状态管理」 方案的选择,我们可以遵循如下原则选择:

  1. 区分 「缓存」「用户交互」 对应的状态,区别对待
  2. 对于 「用户交互」 的状态,根据项目类型、复杂度选择合适方案

Redux 虽好,可不要滥用哦~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK