一、为什么要用redux
react和vue都是单向数据流去管理状态。而两个非父子组件之间通信就相对麻烦,特别是跨多级组件之间的通讯。所以两个框架都有相应的状态管理器,如redux vuex mobx等优秀框架。而其中大致的设计思想就是发布订阅模式,通过管理容器存储需要共享的数据,中央管理,多处派发,解决了跨级通讯的痛点。看了redux源码后,体悟很深,自己动手写了一个。
参考文章:
https://www.jianshu.com/p/e984206553c2
https://github.com/bricksert/blog/issues/22
二、什么时候用redux
阮一峰老师的博客Redux 入门教程(一)里面这样说:
不需要使用 Redux的场景:
用户的使用方式非常简单
用户之间没有协作
不需要与服务器大量交互,也没有使用 WebSocket
视图层(View)只从单一来源获取数据
需要使用redux的场景:
用户的使用方式复杂
不同身份的用户有不同的使用方式(比如普通用户和管理员)
多个用户之间可以协作
与服务器大量交互,或者使用了WebSocket
View要从多个来源获取数据
三.具体实现
版本一:发布订阅模式
counterReducer = function (state, action) {
switch (action.type) {
case 'INCREAMENT':
return {
...state,
count: state.count + 1
};
case 'DECREAMENT':
return {
...state,
count: state.count - 1
};
default: // 没有匹配的action,则返回原来的state
return state
}
}
const createStore = function (reducer, initState) {
let listeners = [];
state = initState;
function subscribe(listener) {
listeners.push(listener);
}
function dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => { // 执行所有subscrible的回调方法
listener();
})
}
function getState() {
return state;
}
return {
subscribe,
dispatch,
getState
}
}
1.对用到state的地方进行状态监听
2.对action.type进行约束,如果reducer中有对应,则进行修改,如果没有则不修改
// 使用redux_v1
const store = createStore(counterReducer,{
count: 20
})
store.subscribe(() => {
console.log(store.getState())
})
store.dispatch({
type: 'INCREAMENT'
})
store.dispatch({ // dispatch一个不存在的action,state并不会改变
type: 'SET_PRICE',
price: 111
})
对于版本一来说,reducer 是一个计划函数,接收老的 state,按计划返回新的 state。而项目中肯定有多个state, 每个state都需要对应的reducer。并且在创建store时也不可能将所有的state写在一起。我们必须将reducer和state进行拆分,随时可以添加新的state和reducer。
版本二:拆分reducer和state
const bookInit = {
name: '小葵花妈妈课堂',
price: 20
}
function bookReducer(state, action) {
if (!state) { // 若不存在,则将state赋值
state = bookInit
}
switch (action.type) {
case 'SET_NAME':
return {
...state,
name: action.name
};
case 'SET_PRICE':
return {
...state,
price: action.price
};
default:
return state
}
}
const counterInit = {
count: 10
}
function counterReducer(state, action) {
if (!state) {
state = counterInit
}
switch (action.type) {
case 'INCREAMENT':
return {
...state,
count: state.count + 1
};
case 'DECREAMENT':
return {
...state,
count: state.count - 1
};
default:
return state
}
}
function combineReducer(reducers) { // 拆分redux核心
const reducerKeys = Object.keys(reducers);
return function combination(state, action) {
let nextState = {}
for (let i = 0; i < reducerKeys.length; i++) {
let key = reducerKeys[i];
let reducer = reducers[key];
let prevState = state[key];
nextState[key] = reducer(prevState, action);
}
return nextState;
}
}
const createStore = function (reducer, initState = {}) {
let listeners = [];
state = initState;
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() { // 退订
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
function dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => {
listener();
})
}
function getState() {
return state;
}
dispatch({ // 在创建store是dispatch一个不匹配任何 type 的 action。其目的是获取初始值
type: Symbol() // 标识唯一性
})
return {
subscribe,
dispatch,
getState
}
}
这样我们就实现了一个七七八八的redux,其中combineReducer是核心实现。可以仔细看看。
const reducer = combineReducer({
counter: counterReducer,
book: bookReducer
})
const store = createStore(reducer) // 不给初始值,让其获取
unsubscribe = store.subscribe(() => {
console.log(store.getState())
})
store.dispatch({
type: 'INCREAMENT'
})
unsubscribe() // 退订后下面的dispatch不会执行回调
store.dispatch({
type: 'SET_PRICE',
price: 200
})
四.中间件思想
什么是中间件?
个人认为中间件就是程序中可织入的,可重用的,与业务逻辑无关的组件。是用来提供额外功能的。
比如现在有一个需求,在每次修改 state 的时候,记录下来 修改前的 state ,为什么修改了,以及修改后的 state。实现一个日志组件,直接上代码
const reducer = combineReducer({
counter: counterReducer,
book: bookReducer
})
const store = createStore(reducer) // 不给初始值
unsubscribe = store.subscribe(() => {
// todo
})
const next = store.dispatch;
store.dispatch = function (action) {
console.log('action:',JSON.stringify(action), )
console.log('修改前:',store.getState(), )
next(action);
console.log('修改后:',store.getState(), )
console.log('---------------------------------------')
}
store.dispatch({
type: 'INCREAMENT'
})
store.dispatch({
type: 'SET_PRICE',
price: 18
})