因为正在开发的是一个小项目,除了登录用户信息、全局配置没有其他需要集中管理的状态,就没打算用Redux。杀鸡焉用牛刀,也有尽量少依赖第三方的强迫症,不想整合redux,react-redux和redux-thunk三个新包。当然Redux有很多优点:已经很完善且生态强大、拥有清晰的规范体系和特定的设计约束、以及精挑细选的API。遵循这种状态管理方案的最佳实践,在大型团队项目中可以很好地帮助我们约定代码规则,减少成员出错和乱套的概率。Mobx则是面向对象思维的状态管理库,我还没有用过。
这次我想试试靠React单打,一通百度有样学样用React Hook的useContext
结合useReducer
模拟了Redux的状态管理机制。实质就是一个可以处理同步/异步动作的函数,并根据动作函数更新状态的集中状态管理器。这边记录下摸索过程的心得体会。
我决定又悄咪咪分个上下两篇:占坑还没写完orz React应用状态管理(二):用React Hook模拟实现Redux
参考文章地址:使用Hooks代替Redux、React Hooks 版实现 Redux
状态管理三剑客:Action、Reducer、Store(不局限于Redux)
1. Action
Action 就是一个普通JS对象,作用除了描述对 store 数据的更改操作,更是一个把数据从应用传到store的可靠载体(数据有可能是服务器响应,用户输入等等)。它必须包含一个type
字段表明要操作的类型。
一般都是通过 store.dispatch()
把 action 传到 store。
const ADD = 'ITEMS_ADD'
{ type: ADD, payload: 'text' }
在大型项目中,把type的值显式地定义成常量会更利于团队协作。
Action 只做简单的参数传递和配置,最多做一些数据格式的转换,如把 date 转成 formatted string。尽量把数据处理逻辑放在reducer中。因为按照语意 action 就只是一个 identifier,reducer 才是负责逻辑处理的。
Action 创建函数是生成 action 的方法,这样做更方便我们传递数据。
就是像这样简单的返回一个 action:
export function completeItem(id: number) {
return {
type: ITEMS.COMPLETE,
payload: id
};
}
然后在项目中把 action 创建函数的结果传给 dispatch() 方法即可发起一次 dispatch 过程。dispatch(completeItem(id))
或者创建一个 被绑定的 action 创建函数 来自动 dispatch,再直接调用:
const boundCompleteItem = id => dispatch(completeItem(id))
boundCompleteItem(id);
2. Reducer
reducer 是一个接收旧 state 和 action,并返回新的 state 的函数。它指定了如何响应 actions 并发送到 store 。记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
它与被传入 Array.prototype.reduce(reducer, ?initialValue)
里的回调函数属于相同的类型,这就是reducer命名的由来。
/** arr.reduce(callback, [initialValue]) **/
/* 第一个参数是回调函数: (prev, curr) => prev + curr *
与redux当中的reducer模型 (previousState, action) => newState 是不是非常相似呢 */
var sum = [0,1,2,3,4].reduce((prev,curr) => prev + curr); // sum = 10
保持 reducer 纯净非常重要。永远不要在 reducer 里调用非纯函数或有执行副作用的操作(如 API 请求和路由跳转)。
不要直接修改 state
。 可以用对象展开运算符的 { ...state, ...newState }
达到合并新旧数据并返回新指针对象的目的。
一个reducer函数示例
export const initialState = [];
// reducer接收 旧state和 action 并返回新的state
export default function items(state = initialState, action) {
// 根据不同的action.type对state进行不同的操作
switch (action.type) {
case ITEMS.RESET: {
return [];
}
case ITEMS.ADD: {
return [
...state,
{
id: Date.now(),
text: action.payload,
completed: false,
},
];
}
case ITEMS.COMPLETE: {
return state.map((item) => {
if (item.id === action.payload) {
return { ...item, completed: !item.completed }
}
return item;
});
}
default: {
return state;
}
}
}
3. Store
Store 只是有几个方法的JS对象。它把Action和Reducer联系到一起,用来维持应用所有的 state 树 。 要创建一个store,只需要把根部的 reducer 函数传递给 createStore
。 改变 store 内 state 的惟一途径是对它 dispatch 一个 action。
它提供的几个主要方法:
-
getState()
:获取当前的 state; -
dispatch(action)
:分发action来更新 state; -
subscribe(listener)
:注册监听器和注销监听器。
以下是对createStore源码的模拟:
const createStore = (reducer: any)=>{
let state: any;
let listeners:[] = [];
// 用来返回当前的state
const getState = () => state;
// 根据action调用reducer返回新的state并触发listener
const dispatch=(action: any)=>{
state=reducer(state,action);
listeners.forEach((listener: any) => listener());
};
/* 这里的subscribe有两个功能
* 调用 subscribe(listener) 会使用listeners.push(listener)注册一个listener * 而调用 subscribe 的返回函数则会注销掉listener */
const subscribe = (listener: never) => {
listeners.push(listener);
return () => {
listeners:[] = listeners.filter(l=>l!==listener);
};
};
return{ getState, dispatch, subscribe };
};
创建store
import rootReducer from './reducers'
let store = createStore(rootReducer)
最后贴一个用js简单实现的基础版redux。
可以帮助我们捋清redux最基本的流程。异步代码非原创,出处: 浅谈Redux Action & Reducer & Store
下篇接着讲包含异步Action的React Hook版实现。
const store = {
state: {}, // 全局唯一的state,内部变量,通过getState()获取
listeners: [], // listeners,用来诸如视图更新的操作
dispatch: () => {}, // 分发action
subscribe: () => {}, // 用来订阅state变化
getState: () => {}, // 获取state
}
const createStore = (reducer, initialState) => {
// internal variables
const store = {};
store.state = initialState;
store.listeners = [];
// api-subscribe
store.subscribe = (listener) => {
store.listeners.push(listener);
return () => {
store.listener = store.listeners.filter(l => l !== listener);
};
};
// api-dispatch
// 根据 action 调用reducer返回新的state,并触发listener
store.dispatch = (action) => {
store.state = reducer(store.state, action);
store.listeners.forEach(listener => listener());
};
// api-getState
store.getState = () => store.state;
return store;
};
// reducer
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
let store = createStore(counter)
store.subscribe(() =>
console.log(store.getState())
)
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
网友评论