前言
平时使用React
做开发的同学对Redux
都不会陌生,这是一个基于flux
架构的十分优秀的状态管理库。这是Redux
官方文档对它的描述。
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。
我们在学习的过程中,在能够使用它完成一些日常开发之后,如果要比较深入的了解一个库,可以去看看它的源码,而Redux
的源码其实并不长。进而,我们可以模仿手写一个简单的Redux
,提升coding能力的同时也有助于消化优秀的第三方库中蕴含的思想。
基础用法和对源码的部分解读之前也有已经发过,感兴趣的小伙伴可以移步Redux学习笔记---简单使用以及源码阅读
接下来咱们就开始手写一个简单的Redux
吧!
不可变数据
Redux
中有一个概念是不可变数据,指的是我们改变store
里面的数据时不可以直接去对它赋值,而是需要使用一个方法去修改。使用这种方式有以下好处:
- 易于调试,当
store
发生变化时,可以记录前后变化的状态,很容易借此开发出类撤销、回退等功能 - 易于推测,由于需要触发了
action
变化,store
才会变化,通过触发的action
,我们可以判断当前的状态是什么。
而配合起一些Immutable
库,还可以提升工程的性能。仅需要判断当旧的state
与新的state
不是同一个对象时,才去更新组件。而不需要去做一些深层次的遍历判断每个值是否相等。
创建Store
实例
先来一个创建store
的方法,而且对于状态管理来说,应该保证使用的时候全局单一实例。故有以下思路:
- 以闭包的形式创建
state
- 用方法来修改
state
-
state
改变后可通知外部钩子,故引入发布订阅机制,调用subscribe
方法订阅回调函数(listener
应为函数类型),再在state
被改变的时候触发所订阅的回调函数,即changeState
方法中遍历listeners
中存储的函数。
如下封装代码
function createStore(initState) {
let state = initState
let listeners = []
//订阅
function subscribe(listener) {
if(typeof listener == 'function'){
listeners.push(listener)
}
}
function changeState(newState) {
listeners.forEach(func=>func())
state = newState
}
function getState() {
return state
}
return {
subscribe,
changeState,
getState
}
}
依据上述代码,可以如下创建一个store
const initState = {
user:'David',
age:18
}
//创建store
const store = createStore(initState)
//订阅回调函数
store.subscribe(() => {
let state = store.getState();
console.log(`${state.user.name}:${state.user.age}`);
});
store.subscribe(() => {
let state = store.getState();
console.log(state.counter.count);
});
store.changeState({
...store.getState(),
user: {
name: 'Jack',
age: 19
}
});
store.changeState({
...store.getState(),
counter: {
count: 20
}
});
可以看到上述版本中有一个明显的缺陷:改变数据的时候是直接把一整个对象存进去的,这样对于我们开发的时候跟踪状态十分不便。所以接下来还是要引入reducer
和action
。
reducer和action
先来回忆一下Redux中是如果用reducer和action配合,修改state的。主要分为以下几步:
- 首先我们会在
actionTypes.js
文件中写好所有action
的类型。例如
// actionTypes.js
export const ADD_COUNT = 'ADD_COUNT'
2.然后在对应的action
文件中引入该type
,返回对应的type
类型,如果此时有额外的数据加入,一般会加多一个payload
字段。
// actions/Count.js
import {ADD_COUNT} from "../actionTypes"
export function addCount() {
return {
type: ADD_COUNT
}
}
3.根据action
返回的type
,触发reducer对应的方法
// reducers/Count.js
import {ADD_COUNT} from "../actionTypes";
const initialState = {
count: 0
}
export function Count(state = initialState, action) {
const count = state.count
switch (action.type) {
case ADD_COUNT:
return {
count: count + 1
}
default: return state
}
}
4.编写好reducer
和action
之后,创建store
//store.js
import {createStore} from 'redux'
import {Count} from './reducers/Count'
const store = createStore(Count)
export default store
5.最后,如下使用:
import store from './store'
import { addCount } from './actions/Count'
store.dispatch(addCount())
由上述看来,我们是要去实现一个dispatch
方法。根据action
返回的type
,去触发对应的reducer
重新计算更新state
。所以咱们的createStore
方法可以改造如下:
function createStore(initState, reducer) {
let state = initState
let listeners = []
function subscribe(listener) {
listeners.push(listener)
}
function dispatch(action) {
listeners.forEach(item => item())
//主要是这一句,将粗暴的changeState改成对应的reducer去修改。
state = reducer(state, action())
}
function getState() {
return state
}
return {
subscribe,
dispatch,
getState
}
}
使用如下:
let initState = {
count: 0
}
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return {
...state,
count: state.count + 1
}
break;
default:
return state
break;
}
}
let store = createStore(initState, reducer)
const ADD = 'ADD'
function add(){
return {
type:ADD
}
}
//使用它
store.dispatch(add())
中间件
由于业务的多样性,单纯的修改 dispatch 和 reducer 显然不能满足大家的需要,因此redux提供了自由组合的、可插拔的中间件机制。在日常开发中,我们常常串联不同的中间件来满足我们的开发需求。在redux进行数据流改变时,中间件可以截获action,并对它进行修改。
记录日志中间件
中间件的编写,主要是重写了dispatch
方法,先用next
缓存之前的dispatch
方法,再处理完中间件逻辑之后,调用的next
方法其实就是调用一开始的action
。
let store = createStore(initState, reducer)
let next = store.dispatch
store.dispatch = action => {
//打印log
console.log(`action:${action}`)
console.log(`state:${store.getState()}`)
//调用真正触发的action
next(action)
console.log(`next state : ${store.getState()}`)
}
记录异常中间件
了解了中间件的编码逻辑之后,我们很容易再开发出一个记录异常的中间件,如下:
let store = createStore(initState, reducer)
let next = store.dispatch
store.dispatch = action => {
try{
next(action)
}catch(e){
throw new Error(e)
}
}
多中间件组合
使用Redux中,我们常常使用多个中间件串联,有了上述的经验之后,我们很轻松的可以如下组合起来。
let store = createStore(initState, reducer)
let next = store.dispatch
const loggerMiddleware = function (next) {
return function (action) {
console.log('this state', store.getState());
console.log('action', action);
next(action);
console.log('next state', store.getState());
}
}
const exceptionMiddleware = function (next) {
return function (action) {
try {
next(action);
} catch (err) {
console.error('错误报告: ', err)
}
}
}
//重写dispatch方法,依次调用完中间件逻辑后,最后再触发真正的action
store.dispatch = exceptionMiddleware(loggerMiddleware(next))
const ADD = 'ADD'
function add(){
return {
type:ADD
}
}
store.dispatch(add())
console.log(store.getState())
最后贴一张图,方便大伙儿理解~
浏览器debugger
最后
行文至此,感谢阅读,如果您喜欢的话,可以帮忙点个like哟~
欢迎转载,但要注明出处哟~
网友评论