[English](./README.md) | 简体中文 # dream-redux 一个开箱即用,简易更新的 redux 集成库。 ## 简介 **dream-redux** 是一个专门为想快速使用 redux 的开发者打造的框架,只需要写两处代码便可在 react 项目上完成 redux 家族库的搭建,并且提供了多种简化功能,使项目代码更加高效简洁。你只需要有 react 的基础知识,便能借助 **dream-redux** 轻松开发具备状态管理功能的项目了。 ## 安装 ```ssh npm install --save dream-redux ``` ## 目录 * [开始使用](#开始使用) * [了解概念](#了解概念) * [项目优势](#项目优势) * [API 基本用法](#API-基本用法) * [StoreCreator](#StoreCreator) * [useSelector](#useSelector) * [connect](#connect) * [setReducer](#setReducer) * [commitMutation](#commitMutation) * [API 高级用法](#API-高级用法) * [同时修改多个 reducer_state](#同时修改多个-reducer_state) * [在 mutation 中进行异步操作](#在-mutation-中进行异步操作) * [在 Promise 的不同状态阶段分别派发 mutation](#在-Promise-的不同状态阶段分别派发-mutation) * [dispatch 后获取最新的 store_state](#dispatch-后获取最新的-store_state) * [状态持久化存储](#状态持久化存储) ## 开始使用 1. 在 *src/store/index.js* 中初始化 store 实例: ```js // src/store/index.js import { StoreCreator } from 'dream-redux' export const { store, useSelector, setReducer, commitMutation } = new StoreCreator({ reducerConfig: { name: 'app', initialState: { // 这里输入需要进行管理的状态,如: count: 0, list: [] } } }) ``` 2. 在 react 项目根文件中引入 store 实例: ```js // src/index.jsx import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'dream-redux' import { store } from './store/index.js' import App from './App.jsx' ReactDOM.render( , document.getElementById('root'), ) ``` 这样,redux 的搭建就已经完成,可以进行项目代码的开发了。无需动手配置搭建 redux,react-redux,redux-persist 等 js 库,是不是有点意思!别着急,后面还有更多诱人的功能等着你呢!^_^ ## 了解概念 根据 redux 的架构设计,状态 state 由 store 对象管理,后文用 **store_state** 代称。 负责更新 state 的模块称为 reducer,store_state 可以只由一个 reducer 负责,称为*单 reducer 模式*。也可以分割成多个 partial state,分别交给多个 reducers 管理,称为*多 reducers 模式*。每个 reducer 管理的那一部分 state 在后文用 **reducer_state** 代称。 在 redux 的设计理念中,state 是不能被直接修改的,只能使用 store.dispatch 向 reducer 派发带有修改信息的 action,reducer 中根据 action.type 匹配到指定的修改逻辑,修改后把新的 reducer_state 返回给 store 做后续的操作。 这个过程中,当 reducer 接收到 action 后,逻辑代码不能直接对现在的 reducer_state 进行修改,而是需要构造一个全新的 reducer_state,在新 reducer_state 上做修改后返回给 store。这使得处理层级较深的 reducer_state 树时会非常麻烦,也容易出现错误。 看到这里,刚接触 redux 的朋友是不是被这个更新流程给绕晕了,再加上繁琐的初始搭建过程,使其对新手特别不友好,这也是 redux 受人诟病的缺点之一。而这也就是 dream-redux 想要解决的问题。 ## 项目优势 **dream-redux** 通过一个强大的 immutable 库 —— Immer.js 与 redux 相结合,并把 action 改造成 mutation,让开发者可以在 mutation 中 “直接” 对 reducer_state 进行修改。然后通过封装过的 dispatch 方法 —— *commitMutation* 把 mutation 当作参数执行一下,就完成了一次状态更新。无需构建 reducer 函数,无需在修改 state 后构造一个新的 state 返回,不仅减少了大量的重复代码,还降低了出错的概率。 **dream-redux** 的优势主要有: - 无需进行繁琐的初始化过程,redux 系列库和中间件的搭建仅用一行代码搞定 - 无需手写 reducer,只需要在 StoreCreator 实例化时传入 name,initialState 等配置项就能自动构建 reducer - mutation.type 可以根据需要选择不同的书写规范,无需让 type 在多个文件中反复横跳,甚至可以省略不写 - 最重要的,在 mutation.operation 中可以直接修改 reducer_state,无需自己构造全新的返回值 ## API 基本用法 #### `StoreCreator` 创建核心对象 store 和一系列 api 的类 - 用法 `new StoreCreator(config, [middlewares])` - 参数 **`config`** *object* - **reducerConfig** *object* | *array* : 必填项,reducers 的配置,格式分为单 reducer 和多 reducers 两种模式,以下为每个 reducer 的配置项: - **name** *string* : 必填项,对应 reducer 的 name,在多 reducers 时作为 store_state 的属性名,以及 mutation 中指定 reducer 用的 target - **initialState** *object* : 必填项,对应 reducer 的 state 结构和初始值 - **persist** *object* : 可选项,配置方法同 redux-persist 的 *persistConfig* - **allowOperationReturns** *boolean* : 可选项,设为 `true` 允许通过在 operation 中返回自己构造的新 state 来更新状态;默认是只能直接修改 state,返回值会被忽略 - **logger** *boolean* : 可选项,设为 `true` 时开启 redux-logger 中间件 - 参数 **`middlewares`** *array* : 可选项,需要添加中间件时以数组形式传入 - 返回项: `{ store, useSelector, setReducer, commitMutation, persistor }` ```js import { StoreCreator } from 'dream-redux' // 单 reducer 配置 const config = { reducerConfig: { name: 'app', initialState: { count: 0, list: [] } } } // 或者: // 多 reducers 配置 // 注意:当 reducerConfig 为数组时,即使只有一个 reducer 也视作多 reducers 配置 const config = { reducerConfig: [ { name: 'app', initialState: { count: 0, list: [] } }, { name: 'counter', initialState: { count: 0 } }, ] } export const { store, persistor, useSelector, setReducer, commitMutation } = new StoreCreator(config) ``` ### `useSelector` 在 hook 组件中获取目标 state 的方法,等同于 react-redux 的 useSelector - 用法 `useSelector(selectorFunc)` ```js import React from 'react' import { useSelector } from '@/store/index.js' // store 文件夹的相对目录 export default function Example() { const count = useSelector(state => state.count) const list = useSelector(state => state.list) console.log('count', count) // 'count', 0 console.log('list', list) // 'list', [] return
} ``` ### `connect` 在 class 组件中获取目标 state 的 HOC 方法,等同于 react-redux 的 connect,只需传入 mapStateToProps 参数即可,派发 mutation 的方式用 commitMutation 代替 - 用法 `connect(mapStateToProps)` ```js import React from 'react' import { connect } from 'dream-redux' const connectWrapper = connect(state => state) class Example extends React.Component { constructor(props) { super(props) this.exampleMethod = this.exampleMethod.bind(this) } exampleMethod() { const { count, list } = this.props console.log('count', count) // 'count', 0 console.log('list', list) // 'list', [] } render() { return
} } export default connect(Example) ``` ### `setReducer` 最基础的 *dispatch* API,可直接在 operation 中修改指定 target 的 reducer(简单粗暴 & 不常用) - 用法 `setReducer(target, operation, [returnPromise])` - 参数 **`target`** *string* : 必填项,目标 reducer 的 name - 参数 **`operation`** *function* : 必填项,可在方法体中直接修改指定 target 的 reducer_state - 参数 **`returnPromise`** *boolean* : 可选项,值为 `true` 时 setReducer 执行后会返回一个 Promise 对象 ```js import React from 'react' import { setReducer } from '@/store/index.js' // store 文件夹的相对目录 export default function Example() { function onBtnClick() { // 最基本的用法,直接在 state 上修改,无需通过常规的 dispatch(action) -> reducer 即可更新 reducer_state setReducer('app', state => { // state 在单 reducer 时是 store_state,多 reducers 时是 name 为 app 的 reducer_state,类型均为 Proxy state.count++ state.list.push('这是一条测试文字') }) } return } ``` ### `commitMutation` 核心 *dispatch* API,具有更新 reducer_state 的全部功能,高级版的 setReducer(正式项目中使用) - 用法 `commitMutation(mutation, [returnPromise])` - 参数 **`mutation`** *object* | *function* | *array* : 必填项,常见写法为构造一个返回 mutation 对象的函数,然后在 commitMutation 中传入参数执行;mutation 的属性如下: - **type** *string* : 可选项,此次 dispatch 行为的类型标记,为求更新过程可追踪,一般都会填 - **target** *string* | *array* : 必填项,目标 reducer 的 name - **operation** *function* | *array* : 必填项,可在方法体中直接修改指定 target 的 reducer_state - 参数 **`returnPromise`** *boolean* : 可选项,值为 `true` 时 commitMutation 执行后会返回一个 Promise 对象 *setReducer* 和 *commitMutation* 虽然更新 reducer_state 的方式和常规 redux 不同,但仍旧遵循 redux 的工作原理,即每次命令 reducer 更新后返回一个全新的 reducer_state 对象,再让其触发 react 的重新渲染 ```js import React from 'react' import { commitMutation } from '@/store/index.js' // store 文件夹的相对目录 export default function Example() { function onBtnClick() { const mutationCreator = listItem => { // 返回 mutation 对象 return { type: 'EXAMPLE_A', target: 'app', operation: state => { // state 在单 reducer 时是 store_state,多 reducers 时是 name 为 app 的 reducer_state,类型均为 Proxy state.list.push(listItem) } } } // 基本用法 commitMutation(mutationCreator('这是一条测试文字')) } return } ``` ## API 高级用法 ### 同时修改多个 reducer_state 项目开发中,出于功能划分和性能优化的考虑,我们可以把 state 划分到多个 reducers 中。这就不可避免会出现同时修改多个 reducers 中的某几个状态的情况。 - setReducer 并不具备一对多的更新操作,只能对每个要更新的 reducer 专门进行一次 setReducer 操作。 - commitMutation 则可以在只派发一次 mutation 的情况下更新多个 reducer_state。 ```js // mutations.js // 同时更新 app_reducer 的 list 和 counter_reducer 的 count export const mutationCreator = listItem => ({ type: 'EXAMPLE_MUTATE_MULTIPLE_STATES', target: ['app', 'counter'], // operation 数组各子项的 reducer_state 与 target 数组一一对应 operation: [ state => { // name 为 app 的 reducer_state state.list.push(listItem) }, state => { // name 为 counter 的 reducer_state state.count++ } ] }) ``` ### 在 mutation 中进行异步操作 有时候我们需要等待 HTTP 请求返回数据后,或者通过设置定时器来延时进行 dispatch 操作,这些都属于异步操作。但是在 operation 的方法体中,是不允许执行异步代码的。所以我们需要稍微改变一下 mutationCreator 的写法,使其可以和异步代码一起抽离出来,防止业务代码过于冗杂。目前该功能的原理和使用方法与 redux-thunk 库一致。 - 处理 HTTP 请求 ```js // mutations.js export const mutationCreator = () => { return (dispatch, getState) => { return SomeHttpRequest().then(res => { console.log(res) // { data: '这是 HTTP 请求返回的数据' } console.log(getState()) // store_state dispatch({ type: 'EXAMPLE_PROMISE', target: 'app', operation: state => { // operation 的方法体中只允许对 state 进行同步更新 state.list.push(res.data) } }) }) } } ``` - 处理 setTimeout ```js // mutations.js export const mutationCreator = () => { return dispatch => { // 使用 Promise 包裹 setTimeout,在内部 dispatch 后执行 resolve return new Promise(resolve => { setTimeout(() => { dispatch({ type: 'EXAMPLE_SETTIMEOUT', target: 'app', operation: state => { // operation 的方法体中只允许对 state 进行同步更新 state.list.push(res.data) } }) resolve() }, 2000) }) } } ``` ### 在 Promise 的不同状态阶段分别派发 mutation 有时我们在处理 Promise 对象,比如 http 请求时,需要针对 pending,fulfilled 和 rejected 状态分别派发不同的 mutation。使用 redux-thunk 也可以完成以上需求,但需要编写的代码比较繁琐。这时可以使用 redux-promise 功能的中间件进行简化。dream-redux 专门为此设计了一个特殊的 mutation 结构,方便开发者使用。 ```js // mutations.js export const mutationCreator = () => ({ type: 'FETCH_HTTP_REQUEST', target: 'app', // 在三个阶段中默认生效,各个阶段都有设置自己的 target 时可以省略 // 用 promise 承载 Promise,取消 operation promise: SomeHttpRequest(), // Promise 对象 // pending 状态 pending: { operation: state => { // ... }, }, // fulfilled 状态 success: { target: ['app', 'counter'], // 各个阶段中可以设定 target 覆盖默认值 operation: [ (state, res) => { console.log(res) // res 为 Promise.resolve 返回的值 // ... }, (state, res) => { console.log(res) // ... }, ], }, // rejected 状态 fail: { target: ['counter'], operation: (state, err) => { console.log(err) // err 为 Promise.reject 返回的值 // ... }, }, }) ``` ### dispatch 后获取最新的 store_state 下面介绍不同情况下获取 dispatch 后的最新 store_state 的方法: 1. 单次或多次 dispatch 同步 mutation,直接通过 store.getState() 同步获取 2. 单次 dispatch 异步 mutation,通过 then 获取 3. 想要 dispatch 多个异步 mutation ,等到所有 mutation 都完成更新后再获取最新 store_state,可以将多个 mutation 以数组形式传给 commitMutation,再通过 then 获取 ```js export default function Example() { // 同步 mutations function runSyncMutations() { commitMutation(sync_mutationCreator_1()) commitMutation(sync_mutationCreator_2()) console.log(store.getState()) // 直接获取最新 store_state } // 单个异步 mutation function runAsyncMutation() { commitMutation(async_mutationCreator(), true).then(state => { console.log(state) // 通过 then 获取最新 store_state }) } // 多个异步 mutations function runAsyncMutations() { commitMutation([async_mutationCreator_1(), async_mutationCreator_2()], true) .then(state => { console.log(state) // 通过 then 获取最新 store_state }) } // return ... } ``` ### 状态持久化存储 正常情况下,当页面刷新或重新加载时,store 会重新生成,之前更新过的状态会被重置。如果有些状态需要存储起来,就需要在 StoreCreator 的 config 中配置 persist 属性,配置项同 redux-persist 的 persistConfig。 ```js // src/store/index.js const config = { reducerConfig: [ { name: 'app', initialState: { count: 0, list: [] }, persist: { whitelist: ['list'] // 白名单,只有 list 状态会被缓存 // blacklist: ['list'] // 黑名单,只有 list 状态不会被缓存 } }, { name: 'counter', initialState: { count: 0 }, persist: {} // 不设 whitelist 或 blacklist,该 reducer 的所有状态都会被持久化存储 }, ] } export const { store, persistor, useSelector, setReducer, commitMutation } = new StoreCreator(config) ``` 还需要在项目根文件通过 PersistGate 引入 persistor: ```js // src/index.jsx import React from 'react' import ReactDOM from 'react-dom' import { Provider, PersistGate } from 'dream-redux' import { store, persistor } from './store/index.js' import App from './App.jsx' ReactDOM.render( , document.getElementById('root'), ) ``` 这样就完成了符合项目需求的持久化配置。