天津建站平台,高新区建网站外包,成都建设厅网站首页,网站备案授权书怎么填掘金链接#xff1a;https://juejin.cn/post/7368288987642232872
1,简介
在状态共享这方面#xff0c;不像 Vuex#xff0c;React 的官方并没有强力推荐某种封装方案#xff0c;所以 React 的状态管理工具五花八门#xff0c;百花齐放#xff0c; react-redux、dva、C…掘金链接https://juejin.cn/post/7368288987642232872
1,简介
在状态共享这方面不像 VuexReact 的官方并没有强力推荐某种封装方案所以 React 的状态管理工具五花八门百花齐放 react-redux、dva、Context API、mobx、recoil/Jotai、zustand,valtio. 其中就有
做什么都要 dispatch 的 redux 流派主打单向数据流包括react-redux 、dva、新星代表 zustand响应式流派 mobx。以及新星代表 valtio 以及一个很有特点的库 resso原子状态流派。来自 facebook 开源的 recoil 以及新星代表 jotai完全体 hooks 流派。hox、reto、umijs4 内置数据流包括 Vue 官方推荐的新状态管理工具 pinia 也是这个流派。 2为什么会考虑写这篇文章
在公司的项目中使用的是 dva.js 作为 状态管理但是Dva.js在编写代码时过于臃肿并且 dva.js 仓库在 2019 年开始就不再维护了不能及时跟上最新的技术发展。其在 ts 不再都没有任何提示的问题也逐步暴露。更不用说dva.js在错误处理方面极其不优雅dva.js 在处理错误时往往会导致整个应用崩溃而不是只影响出错的部分。这使得我考虑有没有一种更加优雅的方式进行React状态的管理并且能够完美兼容项目中已有的状态管理方法作为一种补充手段为开发提效。
并且随着技术不断发展我们终归是要摆脱繁琐的 dva寻找一个新的状态管理工具来减少我们这一块的代码量对于这块技术的探索就提上了日程 具体改造可以参考我写的这篇文章 https://juejin.cn/post/7321049446443384870?searchId2024051008370556AFD9AB416FDEE1C534 最终我选择了使用 zustand 作为我们新的状态管理工具引入项目为什么会选择这个呢文章的末尾会给我我考虑的理由
3市面上React状态管理库介绍
在 Web 前端开发中状态管理是一个非常重要的话题。随着 React 生态圈的不断壮大越来越多的状态管理工具应运而生。其中主要分为这几个流派
1. Redux 流派 Redux 是最传统的状态管理库强调单一数据源、不可变数据和纯函数更新。使用dispatch来触发action通过reducers处理action并返回新的state。React-Redux提供了与React集成的桥梁。 Redux 的三大原则是 单一数据源整个应用的 state 被存储在一个 object tree 中并且这个 object tree 只存在于唯一一个 store 中。State 是只读的唯一改变 state 的方法就是触发 actionaction 是一个用于描述已发生事件的普通对象。使用纯函数来执行修改为了描述 action 如何改变 state tree 你需要编写 reducers。 在 React 生态圈中有很多基于 Redux 思想的状态管理工具 例如
react-redux
React-Redux 是一个在 React 应用中使用 Redux 的官方绑定库。它提供了一种在 React 组件中连接 Redux store 的方法使得组件能够获取 store 中的 state 并触发 actions。
简单实用介绍
npm install react-redux redux //安装依赖store.js
import { createStore } from redux;// 定义初始状态
const initialState { count: 0 };// 定义根reducer它决定了状态如何随着actions而变化
const rootReducer (state initialState, action) {switch (action.type) {case INCREMENT:// 当action类型为INCREMENT时增加countreturn { ...state, count: state.count 1 };default:// 默认情况下不改变状态return state;}
};// 使用createStore创建Redux store并传入根reducer
export const store createStore(rootReducer);App.js
import React from react;
import { Provider } from react-redux;
import store from ./store;
import Counter from ./Counter;// React组件App使用Provider包裹Provider是react-redux提供的组件
// 它允许我们将Redux的store传递给组件树的任何部分
function App() {return (Provider store{store}Counter //Provider);
}export default App;Counter.js
import React from react;
import { connect } from react-redux;// mapStateToProps是一个函数它将Redux store中的状态映射到组件的props
const mapStateToProps state ({ count: state.count });// mapDispatchToProps创建了一个对象对象中的每个函数当你调用它们时
// 都会通过dispatch派发一个action这些函数也会作为props传递给组件
const mapDispatchToProps dispatch ({increment: () dispatch({ type: INCREMENT }),
});// Counter是一个普通组件它接收来自Redux store的count和increment作为props
function Counter({ count, increment }) {return (divpCount: {count}/p{/* 当按钮被点击时调用increment prop来dispatch一个INCREMENT action */}button onClick{increment}Increment/button/div);
}// 使用connect高阶组件将Counter组件连接到Redux store
// connect函数接受两个参数mapStateToProps和mapDispatchToProps
// 它返回一个新的组件这个组件能够访问Redux store中的状态和分派actions
export default connect(mapStateToProps, mapDispatchToProps)(Counter);这段代码演示了Redux最基础的使用方式创建store、定义reducer、使用Provider将store提供给组件树、以及使用connect将组件连接到Redux store。通过这种方式React组件能够获取全局状态并触发状态变更。 特点
提供了 Provider 组件用于将 Redux store 传递给 React 组件树。提供了 connect 高阶组件用于将 Redux store 中的 state 和 actions 映射到组件的 props。基于 React 的 context API 实现避免了不必要的组件重新渲染。
优点
官方维护社区支持度高有很多相关资源。高性能避免了不必要的组件重新渲染。支持中间件可以方便地扩展 Redux 的功能。
缺点
学习曲线较陡峭需要熟悉 Redux 的概念。代码冗余需要编写很多样板代码。在大项目的时候简直是灾难基本不会怎么考虑需要手动管理 state 更新逻辑。安装依赖
dva
受Redux启发为中国开发者设计提供简化版的API和模型model的概念集成了Redux、Redux-Saga和React-Redux使得状态管理更加简洁。 我认为 dva.js 更加适用于中小型的 React 项目对于大型复杂的项目来说优点难受 首先使用 npm install dva 安装 dva 依赖。
接下来创建一个 Dva 应用app.js
// 导入 dva 核心库
import dva from dva;// 初始化一个 dva 应用实例
const app dva();// 定义一个 model 来管理状态和逻辑这里是计数器的模型
app.model({// 命名空间用于区分不同模块的 action 和 reducernamespace: counter,// 初始状态state: 0,// reducers 负责处理同步操作更新 statereducers: {increment(state) {// 返回新的 state这里实现计数器加一的功能return state 1;}}
});// 导出 dva 应用实例
export default app;在应用中使用创建的 Dva 应用App.js
// 引入 React 和 dva 的 connect 函数
import React from react;
import { connect } from dva;// 引入初始化的 dva 应用实例
import app from ./app;// 定义 React 组件 Counter展示计数器状态和提供增加按钮
function Counter({ count, dispatch }) {return (divpCount: {count}/p{/* 点击按钮触发 action通过 dispatch 方法 */}button onClick{() dispatch({ type: counter/increment })}Increment/button/div);
}// 映射 state 到 Counter 组件的 props
const mapStateToProps state ({ count: state.counter });// 使用 connect 函数将 Counter 组件与 Redux store 连接起来
const ConnectedCounter connect(mapStateToProps)(Counter);// 配置路由指定根组件为 ConnectedCounter
app.router(() ConnectedCounter /);// 启动 dva 应用并挂载到页面的 #root 元素上
app.start(#root);在这个示例中我们创建了一个简单的计数器应用。首先创建一个名为 counter 的状态并定义一个用于更新 counter 的方法 increment。然后在 Counter 组件中我们使用 connect 函数将 Counter 组件与 Redux store 连接起来通过点击按钮来增加计数器的值。
特点
基于 Redux继承了 Redux 的所有特性。内置了 Redux-saga简化了异步操作的处理。提供了简洁的 API减少了样板代码。支持插件机制可以方便地扩展功能。
优点
简化了 Redux 和 Redux-saga 的使用降低了学习成本。内置了常用功能提高了开发效率。社区支持度较高有很多相关资源。
缺点
学习成本高dva.js 基于多个库需要预先理解这些库的概念。TypeScript 支持不足缺乏类型提示对 TypeScript 支持不完全。更新慢dva.js 更新和维护不积极难以跟上技术发展。错误处理不优雅错误可能导致整个应用崩溃。代码冗余大量的模版代码写起来挺累人的使用redux toolkit可以一定程度的减少。依赖多依赖众多库可能导致项目维护和升级复杂。不适合大型应用
zustand
Zustand 是一个轻量级的状态管理库在近几年都是最火热的状态管理库它提供了一种简单的方法来在 React 应用中管理全局状态。
简单实用
首先通过 npm install zustand 安装 Zustand 依赖。
接下来创建一个 Zustand store
// store.js
import create from zustand;// 使用 create 方法创建一个新的 Zustand store
const useStore create(set ({// 定义一个名为 count 的状态初始值为 0count: 0,// 定义一个名为 increment 的方法用于更新 count 状态increment: () set(state ({ count: state.count 1 }))
}));export default useStore;在应用中使用创建的 Zustand store
// App.js
import React from react;
import useStore from ./store;function Counter() {// 从 useStore 中获取 count 状态和 increment 方法const { count, increment } useStore();return (div{/* 渲染 count 状态 */}pCount: {count}/p{/* 当点击按钮时调用 increment 方法更新 count 状态 */}button onClick{increment}Increment/button/div);
}function App() {return (div{/* 渲染 Counter 组件 */}Counter //div);
}export default App;在这个示例中我们创建了一个简单的计数器应用。首先创建一个名为 count 的状态并定义一个用于更新 count 的方法 increment。然后在 Counter 组件中我们使用 useStore 获取 count 状态和 increment 方法通过点击按钮来增加计数器的值。
特点
极简的 API易于学习和使用。不依赖于 Redux避免了 Redux 的样板代码和复杂性。支持异步操作可以方便地处理异步状态更新。小巧且高性能避免了不必要的组件重新渲染。
优点
简单易用学习成本低。代码简洁减少了样板代码。轻量级适用于小型项目或者对性能要求较高的场景。
缺点
社区支持相较于 React-Redux 和 dva.jszustand 的社区规模较小相关资源较少。状态管理zustand 的轻量级和直接的状态管理方式可能不适合需要严格区分状态逻辑和界面逻辑的项目。维护和发展作为新兴库zustand 的长期维护和发展方向相对不确定有些开发者可能更倾向于选择有稳定记录的库。
2. 响应式流派
响应式编程是一种编程范式它将应用状态和 UI 变更自动关联起来。当状态发生变化时UI 会自动更新。在 React 生态圈中有一些基于响应式编程思想的状态管理工具例如
mobx
一个非常灵活的状态管理库它使用可观察对象Observable和自动更新Autorun机制来实现状态管理。
中文文档https://suprise.github.io/mobx-cn/refguide/observable-decorator.html
MobX 是一个简单可扩展的状态管理库它通过透明的函数响应式编程 (Transparent Functional Reactive Programming, TFRP) 和面向对象编程 (Object Oriented Programming, OOP) 的方式使状态管理变得简单和可扩展。
下面是一些基本的 MobX 用法
首先我们创建一个可观察的状态。MobX 可以将现有的数据结构如对象、数组和类实例转化为可观察的数据。这是通过 observable 函数实现的。
import { observable } from mobx;class TodoStore {// 使用 observable 装饰器来标记 todos 属性为可观察的observable todos [];addTodo(todo) {this.todos.push(todo);}
}接下来我们定义一个动作。动作是改变状态的唯一途径。在 MobX 中动作可以是任何修改状态的函数。使用 action 函数或装饰器来标记一个函数为动作。
import { observable, action } from mobx;class TodoStore {observable todos [];// 使用 action 装饰器来标记 addTodo 为动作actionaddTodo(todo) {this.todos.push(todo);}
}然后我们创建一个计算值。计算值是基于状态自动派生的值。它们可以是任何值并且只有当它们的依赖项发生变化时才会重新计算。
import { observable, computed } from mobx;class TodoStore {observable todos [];addTodo(todo) {this.todos.push(todo);}// 使用 computed 装饰器来创建一个计算值computed get todoCount() {return this.todos.length;}
}接着我们定义一个反应。反应是当状态发生变化时自动运行的副作用如 I/O 操作、DOM 操作。MobX 提供了几种类型的反应包括 autorun、reaction 和 when。
import { observable, autorun } from mobx;class TodoStore {observable todos [];addTodo(todo) {this.todos.push(todo);}
}const store new TodoStore();// 使用 autorun 创建一个反应当 todos 发生变化时自动运行
autorun(() {console.log(store.todos.length);
});最后我们创建一个观察者组件。MobX 可以自动追踪观察者组件的渲染函数中使用的状态并在状态发生变化时重新渲染组件。
import React from react;
import { observer } from mobx-react;
import todoStore from ./store;// 使用 observer 装饰器来标记 TodoListView 为观察者组件
observer
class TodoListView extends React.Component {render() {return (div{todoStore.todos.map((todo, index) div key{index}{todo}/div)}button onClick{() todoStore.addTodo(New Todo)}Add Todo/button/div);}
}mobx是一个非常典型的响应式状态管理工具。他的工作流程大致如下
用户在view层触发某个事件事件触发action执行通过action来修改statestate更新后computed Values也会根据依赖重新计算属性值状态更新后会触发reactions来响应这次状态变化的一些操作重新渲染组件、打印日志… MobX 的出现给 Redux 带来了很大的冲击。
通过使用装饰器observer / observable可以使普通的组件监听变量变化并自动渲染完全摒弃了 state。此外MobX 还提供了诸如 computed 等功能使得 React 也能享受到 Vue 组件的特性。因此许多从 Vue 转到 React 的开发者都对 MobX 赞不绝口。一时之间MobX 成为了炙手可热的技术。
然而随着时间推移MobX 的热度逐渐降低原因如下
响应式 API 与 React 的设计理念不太契合。React 本身需要通过 setState 来修改状态而 MobX 却可以直接修改 state.value。这使得刚学完 React 基础的新手在学习 MobX 时可能感到困惑。MobX 存在一些隐蔽的坑例如在向 observable 添加属性时不能直接添加而需要通过 extendObservable。这导致许多开发者在使用过程中遇到问题。如果将 MobX 应用于基础组件可能会违反高内聚原则。但如果不使用 MobX又会导致代码风格不统一。很少有组件库需要附带一套状态管理工具。响应式实际上是基于 Proxy 实现的有时会导致数据类型不符合预期。例如开发者希望传递的是一个数组但拿到的却是一个 Proxy这可能让强迫症患者难以忍受。
特点
提供了丰富的 API 和概念如可观察的数据、计算值、动作和反应等。支持异步操作可以方便地处理异步状态更新。自动管理依赖关系只更新需要更新的组件避免不必要的重新渲染。
优点
简化状态管理自动处理状态更新和依赖跟踪减少了手动管理状态的复杂性。性能优化只在需要时更新计算值和反应避免了不必要的计算和渲染。易于集成可以与多种前端框架如 React、Vue 等结合使用提供了丰富的生态系统和工具。
缺点
学习成本相对较高需要理解和掌握 MobX 的各种概念和原理。理念和 React 的设计理念不符合对 TypeScript 支持不是很友好。隐藏性由于 MobX 的自动管理机制有时候很难掌握状态的流动和反应的触发时机。不透明性MobX 的内部实现较为复杂如果需要深入理解和定制可能会比较困难。 以上就是 MobX 的基本用法。更多细节和高级用法请参考官方文档。风格自由。如果没有统一团队的代码风格那你可能会在store中看到各种各样的代码。
valtio
Valtio 是一个简单、轻量级的状态管理库它基于 JavaScript Proxy 对象实现。Valtio 的主要目标是提供一个简单易懂的 API让你能够在 React 应用中轻松地创建和管理可变状态。
下面是一些基本的 Valtio 用法
首先我们创建一个代理状态。使用 proxy 函数创建一个代理状态。代理状态是一个可观察的对象你可以像操作普通对象一样操作它。
import { proxy } from valtio;const todoStore proxy({todos: [],addTodo: function (todo) {this.todos.push(todo);},
});接下来我们在 React 组件中使用代理状态。使用 useProxy React Hook 将代理状态与 React 组件关联。当代理状态发生变化时组件将自动重新渲染。
import React from react;
import { useProxy } from valtio;
import todoStore from ./store;function TodoListView() {// 使用 useProxy Hook 关联 todoStoreconst snapshot useProxy(todoStore);return (div{snapshot.todos.map((todo, index) (div key{index}{todo}/div))}button onClick{() todoStore.addTodo(New Todo)}Add Todo/button/div);
}export default TodoListView;然后我们创建一个派生状态。派生状态是根据代理状态自动计算得到的。使用 derived 函数创建派生状态。
import { proxy, derived } from valtio;const todoStore proxy({todos: [],addTodo: function (todo) {this.todos.push(todo);},
});// 使用 derived 函数创建派生状态
todoStore.todoCount derived((state) state.todos.length);最后我们在组件中使用派生状态。与代理状态一样使用 useProxy Hook 在组件中关联派生状态。
import React from react;
import { useProxy } from valtio;
import todoStore from ./store;function TodoListView() {const snapshot useProxy(todoStore);return (div{snapshot.todos.map((todo, index) (div key{index}{todo}/div))}divTodo Count: {snapshot.todoCount}/divbutton onClick{() todoStore.addTodo(New Todo)}Add Todo/button/div);
}export default TodoListView;Valtio 的特点和优缺点
特点
简洁Valtio 的 API 非常简洁只需要了解几个函数和概念就可以开始使用。易用Valtio 支持直接修改代理状态无需使用特殊的函数或方法。轻量Valtio 的代码量非常小对项目的影响较小。
优点
易于理解Valtio 的状态是普通的 JavaScript 对象可以直接修改和访问符合直觉。灵活Valtio 支持多种数据类型和结构可以灵活地组织和管理状态。
缺点
新兴Valtio 是一个相对新的库社区和生态可能不如一些成熟的状态管理库。兼容性由于 Valtio 基于 Proxy在不支持 Proxy 的环境如 IE中无法使用。
以上就是 Valtio 的基本用法。更多细节和高级用法请参考 GitHub 仓库。
3. 原子状态流派
原子状态是一种将应用状态划分为多个独立、可组合的小状态的思想。这种做法有助于提高状态管理的灵活性和可维护性。在 React 生态圈中有一些基于原子状态思想的状态管理工具例如
recoil
Recoil 是 Facebook 开发的一款实验性的状态管理库专为 React 应用设计。它提供了一种简洁、高效的方式来管理组件间共享的状态。 设计的思路是将状态原子化。atom和selector是Recoil的两个核心。 atom一个原子是一个共享状态的片段。 selector一个组件可以订阅一个原子来获取/设置它的值。 Recoil 的使用
创建一个原子Atom
原子是 Recoil 中的基本状态单元。使用 atom 函数创建一个原子。
import { atom } from recoil;const todoListState atom({key: todoListState, // 唯一标识符default: [], // 默认值
});在组件中读取和修改原子状态
使用 useRecoilState Hook 在组件中读取和修改原子状态。
import React from react;
import { useRecoilState } from recoil;
import { todoListState } from ./store;function TodoListView() {const [todos, setTodos] useRecoilState(todoListState);const addTodo () {setTodos([...todos, New Todo]);};return (div{todos.map((todo, index) (div key{index}{todo}/div))}button onClick{addTodo}Add Todo/button/div);
}export default TodoListView;创建一个选择器Selector
选择器是基于原子状态派生的状态。使用 selector 函数创建一个选择器。
import { selector } from recoil;const todoCountState selector({key: todoCountState,get: ({ get }) {const todos get(todoListState);return todos.length;},
});在组件中使用选择器
使用 useRecoilValue Hook 在组件中读取选择器的值。
import React from react;
import { useRecoilValue } from recoil;
import { todoCountState } from ./store;function TodoCount() {const todoCount useRecoilValue(todoCountState);return divTodo Count: {todoCount}/div;
}export default TodoCount;Recoil 的特点、优缺点
特点
原子性Recoil 将状态划分为原子和选择器有助于更好地组织和管理状态。高效Recoil 使用订阅机制只有在状态发生变化时才会触发重新渲染。与 React 集成Recoil 的 API 设计和 React Hooks 非常相似可以与 React 无缝集成。
优点
易于上手Recoil 的 API 与 React Hooks 非常相似学习曲线较平缓。精细的更新控制Recoil 只有在状态发生变化时才会触发组件重新渲染提高了渲染性能。状态依赖管理Recoil 的选择器可以自动处理状态依赖关系简化了状态派生的管理。
缺点
实验性Recoil 目前仍处于实验阶段API 可能发生变化生态和社区相对较小。不支持class组件。如果你是要更换项目的状态管理工具应该看看原本项目中有没有用到class组件。API比较多上手稍微有点成本。消费一个状态比较繁琐需要import两个东西
jotai
类似Recoil更轻量级专注于原子状态管理和响应式编程强调简洁性和性能。 Jotai 的使用
创建一个原子Atom
原子是 Jotai 中的基本状态单元。使用 atom 函数创建一个原子。
import { atom } from jotai;const todoListAtom atom([]); // 默认值在组件中读取和修改原子状态
使用 useAtom Hook 在组件中读取和修改原子状态。
import React from react;
import { useAtom } from jotai;
import { todoListAtom } from ./store;function TodoListView() {const [todos, setTodos] useAtom(todoListAtom);const addTodo () {setTodos([...todos, New Todo]);};return (div{todos.map((todo, index) (div key{index}{todo}/div))}button onClick{addTodo}Add Todo/button/div);
}export default TodoListView;创建一个派生原子Derived Atom
派生原子是基于其他原子状态派生的状态。使用 atom 函数创建一个派生原子。
import { atom } from jotai;const todoCountAtom atom((get) {const todos get(todoListAtom);return todos.length;
});在组件中使用派生原子
与普通原子一样使用 useAtom Hook 在组件中读取派生原子的值。
import React from react;
import { useAtom } from jotai;
import { todoCountAtom } from ./store;function TodoCount() {const [todoCount] useAtom(todoCountAtom);return divTodo Count: {todoCount}/div;
}export default TodoCount;Jotai 的特点、优缺点
特点
简洁Jotai 的 API 非常简洁只需要了解几个函数和概念就可以开始使用。原子性Jotai 将状态划分为原子有助于更好地组织和管理状态。轻量Jotai 的代码量非常小对项目的影响较小。
优点
易于上手Jotai 的 API 简单直观学习曲线平缓。精细的更新控制Jotai 只有在状态发生变化时才会触发组件重新渲染提高了渲染性能。状态依赖管理Jotai 的派生原子可以自动处理状态依赖关系简化了状态派生的管理。
缺点
新兴Jotai 是一个相对新的库社区和生态可能不如一些成熟的状态管理库。不适用于非 React 项目Jotai 是专为 React 设计的无法在非 React 项目中使用。
区别
Jotai 和 Recoil 都是为 React 设计的状态管理库它们都采用了原子性的状态管理模型但在具体的设计和实现上还是有一些区别的。
API 设计
Recoil 的 API 设计更丰富一些除了基本的原子和选择器还提供了许多其他的 API如原子族atomFamily、选择器族selectorFamily、异步选择器asynchronous selectors等可以满足更复杂的状态管理需求。
而 Jotai 的 API 设计更简洁一些只有原子和派生原子两种状态单元API 数量较少但也足够满足大部分的状态管理需求。
状态更新
Recoil 和 Jotai 在状态更新上的策略也有所不同。Recoil 采用了类似于 Redux 的不可变状态更新策略每次更新都会返回一个新的状态。这种策略可以提供更好的时间旅行调试支持但可能会带来一些性能开销。
而 Jotai 则采用了可变状态更新策略每次更新可以直接修改状态。这种策略可以提供更快的更新性能但可能会带来一些调试难度。
社区和生态
Recoil 是由 Facebook 开发并维护的社区和生态相对更成熟一些。而 Jotai 则是由独立开发者开发和维护的社区和生态相对较小。
总的来说Recoil 和 Jotai 各有优势选择哪一个主要取决于你的具体需求和喜好。如果你需要更丰富的 API 和更好的社区支持可能会倾向于选择 Recoil如果你需要更简洁的 API 和更快的更新性能可能会倾向于选择 Jotai。
4. 完全体 hooks 流派
在 React Hooks 推出之后许多开发者开始尝试使用 hooks 进行状态管理。这种做法使得状态管理更加简单和直观。在 React 生态圈中有一些基于 hooks 的状态管理工具例如
hox
Hox 是一个基于 React Hooks 的状态管理库它提供了一种简单、轻量级的方式来管理组件间共享的状态。
Hox 的使用
创建一个自定义 Hook
在 Hox 中状态管理是通过自定义 Hook 实现的。首先创建一个自定义 Hook。
import { useState } from react;function useTodoList() {const [todos, setTodos] useState([]);const addTodo (todo) {setTodos([...todos, todo]);};return { todos, addTodo };
}export default useTodoList;使用 createModel 创建模型
将自定义 Hook 传递给 createModel 函数创建一个模型。
import { createModel } from hox;
import useTodoList from ./useTodoList;const todoListModel createModel(useTodoList);在组件中使用模型
使用 useModel Hook 在组件中读取和修改模型的状态。
import React from react;
import { useModel } from hox;
import todoListModel from ./todoListModel;function TodoListView() {const { todos, addTodo } useModel(todoListModel);return (div{todos.map((todo, index) (div key{index}{todo}/div))}button onClick{() addTodo(New Todo)}Add Todo/button/div);
}export default TodoListView;Hox 的特点、优缺点
特点
基于 React HooksHox 的状态管理是基于 React Hooks 的与 React 的编程模型高度一致。简洁Hox 的 API 非常简洁只需要了解几个函数和概念就可以开始使用。轻量Hox 的代码量非常小对项目的影响较小。
优点
易于上手Hox 的 API 简单直观学习曲线平缓。与 React 集成Hox 的 API 设计和 React Hooks 非常相似可以与 React 无缝集成。状态共享Hox 支持跨组件共享状态可以轻松地管理全局状态。
缺点
社区和生态相较于一些成熟的状态管理库如 Redux、MobXHox 的社区和生态相对较小。不适用于非 React 项目Hox 是专为 React 设计的无法在非 React 项目中使用。
reto
一个基于 hooks 的模块化状态管理库提供了一种类似于 Redux 的状态管理方式。
Reto 是一个基于 React Hooks 的状态管理库它的核心理念是将状态管理和 React 组件的生命周期紧密结合在一起。
Reto 的使用
创建一个 Store
在 Reto 中状态管理是通过 Store 实现的。首先创建一个 Store这是一个继承了 Reto 的 Store 类的类。
import { Store } from reto;class TodoStore extends Store {state {todos: [],};addTodo (todo) {this.setState({ todos: [...this.state.todos, todo] });};
}export default TodoStore;在组件树中提供 Store
使用 Provider 组件在组件树中提供 Store。
import React from react;
import { Provider } from reto;
import TodoStore from ./TodoStore;
import TodoListView from ./TodoListView;function App() {return (Provider of{TodoStore}TodoListView //Provider);
}export default App;在组件中使用 Store
使用 useStore Hook 在组件中读取和修改 Store 的状态。
import React from react;
import { useStore } from reto;
import TodoStore from ./TodoStore;function TodoListView() {const todoStore useStore(TodoStore);return (div{todoStore.state.todos.map((todo, index) (div key{index}{todo}/div))}button onClick{() todoStore.addTodo(New Todo)}Add Todo/button/div);
}export default TodoListView;Reto 的特点、优缺点
特点
基于 React HooksReto 的状态管理是基于 React Hooks 的与 React 的编程模型高度一致。简洁Reto 的 API 非常简洁只需要了解几个函数和概念就可以开始使用。轻量Reto 的代码量非常小对项目的影响较小。
优点
易于上手Reto 的 API 简单直观学习曲线平缓。与 React 集成Reto 的 API 设计和 React Hooks 非常相似可以与 React 无缝集成。状态共享Reto 支持跨组件共享状态可以轻松地管理全局状态。
缺点
社区和生态相较于一些成熟的状态管理库如 Redux、MobXReto 的社区和生态相对较小。不适用于非 React 项目Reto 是专为 React 设计的无法在非 React 项目中使用。
umijs4 内置数据流
umijs4.x 正式推出后我注意到它内置了一套和 hox 一模一样的数据流方案。我大概翻了一下虽然它不是直接引用的 hox 但内部实现逻辑如出一辙。
它的特点是采用约定式目录结构不用专门写 createStore 而是自动帮我们引入了所有 model 目录下的 hooks并注册。在页面中则是通过统一的 useModel通过其自动生成的 namespace 引用比如 useModel(product)。但这也导致了依赖不明确的问题umi4 还特地通过编写插件的方式解决跳转问题。
但也正是它的约定式产生了一些让我觉得膈应的地方
useModel(product) 必须要通过装插件才能点击跳转。必须要在 umijs4.x 体系下才能使用无法快速复制迁移到其它的框架下使用。插件偷偷帮你实现了 createStore乍一看和普通的 hooks 完全一样其实已经持久化了。对新人学习很不友好。你写个 createStore 他还知道有不一样的地方回去查。umijs4.x 没看到文档根本不知道有这回事
UmiJS 是一个可扩展的企业级前端应用框架它内置了一套简单的数据流方案基于 React Hooks 实现。UmiJS 4 版本的内置数据流方案称为 “dumi”。
UmiJS 内置数据流的使用
创建一个 Model
在 UmiJS 中状态管理是通过 Model 实现的。首先创建一个 Model 文件。
// src/models/todo.js
import { useState } from react;export default function useTodoModel() {const [todos, setTodos] useState([]);const addTodo (todo) {setTodos([...todos, todo]);};return { todos, addTodo };
}在 .umirc.ts 或 config/config.ts 文件中配置 Model
将创建的 Model 添加到配置文件中。
// .umirc.ts 或 config/config.ts
export default {// ...plugins: [// ...[umijs/plugin-model, {}],],// ...
};在组件中使用 Model
使用 useModel Hook 在组件中读取和修改 Model 的状态。
import React from react;
import { useModel } from umi;function TodoListView() {const { todos, addTodo } useModel(todo);return (div{todos.map((todo, index) (div key{index}{todo}/div))}button onClick{() addTodo(New Todo)}Add Todo/button/div);
}export default TodoListView;UmiJS 内置数据流的特点、优缺点
特点
基于 React HooksUmiJS 内置数据流是基于 React Hooks 的与 React 的编程模型高度一致。简洁UmiJS 内置数据流的 API 非常简洁只需要了解几个函数和概念就可以开始使用。轻量UmiJS 内置数据流的代码量非常小对项目的影响较小。
优点
易于上手UmiJS 内置数据流的 API 简单直观学习曲线平缓。与 UmiJS 集成UmiJS 内置数据流的 API 设计和 UmiJS 框架高度集成可以无缝使用。状态共享UmiJS 内置数据流支持跨组件共享状态可以轻松地管理全局状态。
缺点
社区和生态相较于一些成熟的状态管理库如 Redux、MobXUmiJS 内置数据流的社区和生态相对较小。不适用于非 UmiJS 项目UmiJS 内置数据流是专为 UmiJS 设计的无法在非 UmiJS 项目中使用。
pinia
虽然是Vue的状态管理库但因其理念与React的一些现代状态管理库相似故在此提及。它强调简洁、类型安全通过Stores来组织状态。
总结
React 的状态管理工具五花八门百花齐放各种工具都有其特点和适用场景。开发者在选择状态管理工具时需要根据项目需求和团队技术栈进行权衡。同时随着 React 生态圈的不断发展未来可能还会出现更多新的状态管理工具。
选择合适的React状态管理流派主要取决于以下几个关键因素 项目规模和复杂度 对于小型项目或者快速原型开发轻量级的状态管理方案更为合适如Zustand、Jotai、Context API因为它们的学习曲线较低设置简单能够快速开始。对于中大型项目尤其是那些需要严格控制状态变更流程、有复杂数据交互和需要时间旅行调试功能的Redux及其生态如Redux Toolkit、MobX可能是更好的选择因为它们提供了强大的状态管理机制和丰富的生态系统支持。 团队熟悉度 如果团队成员对某个库已经有深入理解和使用经验比如之前项目用过Redux继续沿用可以减少学习成本和提升开发效率。若团队偏好现代React Hooks风格编程那么Recoil、Jotai或Valtio这类基于Hooks的库可能更受欢迎因为它们更贴近React最新的编程模式。 性能和可维护性 对于性能敏感的应用考虑使用像Valtio这样利用Proxy实现的轻量级响应式状态库或Zustand这样设计高效的状态管理器。可维护性方面清晰的数据流向和易于理解的架构很关键。Redux通过严格的action-reducer模式保证了这一点而MobX的自动追踪机制减少了手动管理状态的复杂度。 未来发展趋势和技术栈整合 关注社区活跃度、库的维护情况以及是否与你的其他技术栈如TypeScript、Next.js等有良好的集成。新兴的库如Resso、Hox、Reto等如果它们提供的特性和你的项目需求高度匹配且社区反馈积极也可以考虑采用但需注意评估其成熟度和长期支持的可能性。 个人或团队偏好 开发者的个人喜好和团队的编程哲学也很重要。一些团队可能偏好声明式编程如MobX而另一些可能更倾向于函数式编程风格如Redux。
最终建议是先进行小规模的PoCProof of Concept测试用实际代码尝试几种不同的状态管理方案看哪一种最符合项目需求、团队协作习惯和预期的开发效率。同时保持灵活性随着项目的发展状态管理策略也可以适时调整。
综合比较
特性/库react-reduxdvazustandmobxvaltiorecoiljotai基于ReduxRedux独立响应式Proxy独立独立性能中等中等高高高高高学习曲线陡峭较陡适中适中适中适中适中API复杂度高中等低低低中等低类型安全适中适中优秀适中适中适中适中异步处理需要中间件内置内置内置内置内置内置状态共享Context内置无需Provider响应式无需Provider需要Recoil Context无需Provider状态更新不变更新不变更新不变更新响应式更新响应式更新不变更新响应式更新生态工具丰富差已经不再维护丰富比较火热社区生态逐渐丰富适中适中新兴生态欠佳新兴生态欠佳特点官方Redux绑定简化版Redux轻量、Hooks响应式核心轻量、Proxy原子化状态轻量、原子化状态优势官方维护社区资源丰富简化Redux使用轻量、易于上手、高性能响应式编程自动追踪由Facebook开发社区支持轻量级易于理解和使用使用场景大型应用中小型应用各种规模应用需要响应式特性的应用需要细粒度状态控制的应用需要复杂状态管理的应用需要轻量级状态管理的应用
PS为什么不选 React-Redux / dva.js尽管这些工具在状态管理方面非常成熟但它们在大型项目中的代码冗余和复杂性较高且整体框架相对笨重这可能影响开发效率和项目的可维护性。 MobX / Valtio这些库采用了响应式编程范式与 React 的声明式特性存在一定差异。团队成员对响应式状态管理不太熟悉这可能增加学习成本不利于快速推广和应用。 Recoil / Jotai虽然这两个库在概念上相似提供了基于原子和选择器的状态管理方式但 Recoil 的 selector 功能相对复杂存在一定的上手难度。此外它们的生态系统相比其他成熟的解决方案还不够丰富许多功能仍在实验阶段可能不太适合用于对稳定性要求较高的大型企业项目。 细心点你会发现zustand / valtio / jotai 是同一个开源团队的作品而且他们的名字分别是 德语、芬兰语、日语 中的“状态”。所以不选择某个工具不是说这些工具不好他们之间并没有孰优孰劣只是面向风格不一样罢了。 我是如何选的
在综合评估了项目需求和团队技术栈后我们决定采用 Zustand 作为团队项目状态管理的改造工具。以下是我们选择 Zustand 的主要理由 学习曲线平缓Zustand 遵循单向数据流原则与 dva.js 和 Redux 类似但提供了更简洁的学习路径便于团队快速掌握和推广。 轻量级设计Zustand 的体积极为小巧gzip 压缩后仅有 1KB对项目性能的影响微乎其微。 与 React 高度集成通过利用 React 的钩子Hooks机制Zustand 提供了与函数式组件和钩子编程模型无缝衔接的状态管理方式API 设计简洁易于上手。 灵活的集成能力Zustand 能够轻松与其他 React 状态管理库如 Redux 和 MobX 共存为项目提供了渐进式迁移的可能性。 TypeScript 友好与 dva.js 等其他状态管理库相比Zustand 对 TypeScript 提供了更优的支持有助于提升代码质量和开发体验。 强大的可扩展性通过中间件机制Zustand 允许开发者以插件形式扩展其功能包括日志记录、状态持久化、异步操作处理等增加了状态管理的灵活性。 性能优化Zustand 在设计上注重性能采用高效的更新机制减少不必要的渲染同时支持状态分片和惰性初始化有效提升了大型应用的性能。 活跃的社区生态作为近年来备受瞩目的状态管理库Zustand 拥有一个活跃的开发者社区为遇到的问题提供了丰富的解决方案和高效的支持。
通过实际改造我们已实现代码量的显著减少约 33%并显著提升了代码的组织逻辑和开发效率。 改造文章
1# Zustand 状态库轻便、简洁、强大的 React 状态管理工具https://juejin.cn/post/7321049446443384870#heading-3
2我写的一个zustand npm工具zustand-cli https://www.npmjs.com/package/zustand-cli