Redux
首先是一个状态管理
该文档是跟着九库文档学习的:
假设有一个对象
修改状态
如果我们希望修改后能够被通知呢?那么此时就需要发布订阅模式来解决
let state = {
count: 1
}
let listeners = [];
function subscribe(listener) {
listener.push(listener)
}
function changeState(count) {
state.count = count;
for(let i = 0; i <listeners.length; i++) {
const listener = listeners[i];
listener();
}
}
目前算是可以了,但是存在两个问题:
- 状态管理器只能管理count,不通用
- 公共的代码要封装起来
可以尝试封装下:
function createStore(initState) {
let state = initState;
let listeners = [];
function subscribe(listener) {
listeners.push(listener);
}
function changeState(newState) {
state = newState;
for(let i = 0; i < listeners.length; i ++) {
const listener = listeners[i];
listener();
}
}
function getState() {
return state;
}
return {
subscribe,
changeState,
getState
}
}
使用一下:
let initState = {
counter: {
count: 0,
},
info: {
name: ''
}
}
let store = createStore(initState);
store.subscribe(() => {
console.log('state', store.getState());
})
store.changeState({
...store.getState(),
info: {
name: 'test'
}
})
于是一个简单的状态管理雏形就有了:createStore、changeState、getState
但是上面的函数还存在一个问题,就是可以随意改动状态的值,为此我们需要分两步来解决这个问题:
- 制定一个state修改计划,告诉store修改计划是什么
- 修改store.changeState方法,告诉它修改state的时候按照我们的计划修改
设置一个plan函数,用来接收现有state和一个action要执行的操作,返回经过改变后的state
function plan(state, action) {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1
};
case 'DECREMENT':
return {
...state,
count: state.count - 1
}
default:
return state;
}
}
然后我们将这个计划告诉store,store.changeState以后改变要按照我们的计划修改
function createStore(plan, initState) {
let state = initState;
let listeners = [];
function subscribe(listeners) {
listeners.push(listener);
}
function changeState(action) {
state = plan(state, action);
for(let i = 0; i < listeners.length; i ++) {
const listener = listeners[i];
listener();
}
}
function getState() {
return state;
}
return {
subscribe,
changeState,
getState
}
}
尝试使用
let initState = {
count: 0
}
let store = createStore(plan, initState);
store.subscribe(() => {
let state = store.getState();
console.log(state.count);
})
store.changeState({
type: 'INCREMENT'
})
到这里,我们给plan和changeState换一个名字,plan改为reducer,changeState改成dispatch
到这里我们会发现reducer的问题:项目中我们有大量的state, 每个state都需要计划函数,如果全部写在一起无法管理,所有的计划写在一个reducer函数里面,会导致reduer函数及其庞大复杂。按照组件维度会拆分出多个reducer函数,然后通过一个函数合并起来。
我们来管理两个state,一个counter,一个info。
let state = {
counter: {
count: 0
},
info: {
name: 'xxx'
}
}
它们各自的reducer
/* counterReducer, 一个子reducer,接收的 state 是 state.counter */
function counterRenducer(state, action) {
switch(action.type) {
case 'INCREMENT':
return {
count: state.count + 1
}
case: 'DECREMENT':
return {
...state,
count: state.count - 1
}
default:
return state;
}
}
/* InfoReducer 子reducer 接收 state 是 state.info */
function InfoReducer(state, action) {
switch (action.type) {
case 'SET_NAME':
return {
...state,
name: action.name
}
case 'SET_DESCRIPTION':
return {
...state,
description: action.description
}
default:
return state;
}
}
我们需要一个函数来合并多个reducer函数合并成一个reducer函数
function combineReducers(reducers) {
const reducersKeys = Object.keys(reducers); // [ 'counter', 'info' ]
return combination(state = {}, action) {
// 生成新的state
const nextState = {};
for(let i = 0; i < reducersKeys.length; i++) {
const key = reducerskeys[i];
const reducer = reducer[i];
const previousStateForKey = state[key];
const nextStateForKey = reducer(previousStateForKey, action);
nextState[key] = nextStateForKey;
}
return nextState;
}
}
// 使用
const reducer = combineReducers({
counter: counterReducer,
info: InfoReducer
})
ok,我们已经把reducer按维度拆分了,通过combineReducers合并起来。但是state还是写在一起,会造成state树很庞大,因此需要对state拆分
// counter 自己的 state 和 reducer 写在一起
let initState = {
count: 0
}
function counterReducer(state, action) {
// 如果state没有初始值,那就给他初始值
if(!state) {
state = initState;
}
switch(action.type) {
// ...code
}
}
// createStore函数需要修改下
function dispatch(action) {
state = reducer(state, action);
}
// 用来获取不匹配任何计划的type来获取初始值
dispatch({ type: Symbol() })
ok,目前为止我们已经有一个基本完整的状态管理流程了,但是如果我们想要对dispatch进行扩展或者重写,增强dispatch的功能的
假设我们有一个记录日志的中间件
const store = createStore(reducer);
const next = store.dispath;
// 重写dispatch
store.dispatch = (action) => {
console.log('this state', store.getState());
next(action);
}
// 使用
store.dispatch({
type: 'INCREMENT'
})
还有一个记录异常的中间件
const store = createStore(reducer);
const next = store.dispath;
store.dispatch = (action) => {
try {
next(action);
} catch (err) {
console.error('错误': err)
}
}
如果需要多个中间件进行合作呢?
store.dispatch = (action) => {
try {
console.log('this state', store.getState());
console.log('action', action);
next(action);
} catch {
console.log('错误', err)
}
}
看起来我们将两个中间件合并了,如果后面中间件变多了怎么处理?
这个时候我们就需要将每个中间件单独提取出来
// logger middleware
const loggerMiddleware = (next) => (action) => {
console.log('this state', store.getState());
console.log('action', action);
next(action);
}
// exception middleware
const exceptionMiddleware = (next) => (action) => {
try {
next(action);
} catch(err) {
console.log('错误', err);
}
}
// 使用
const store = createStore(reducer);
const next = store.dispatch;
store.dispatch = exceptionMiddleware(loggerMiddleware(next));
这个时候如果我们想要把js文件独立出去,会发现一些中间件依赖store,那么就需要再封装一次了~
const store = createStore(reducer);
const next = store.dispatch;
const loggerMiddleware = (store) => (next) => (action) => { // code... }
const exceptionMiddleware = (store) => (next) => (action) => { // code... }
const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
store.dispatch = exception(logger(next));
// 单独文件
import loggerMiddleware from './middlewares/loggerMiddleware';
import exceptionMiddleware from './middlewares/exceptionMiddleware';
import timeMiddleware from './middlewares/timeMiddleware';
...
const store = createStore(reducer);
const next = store.dispatch;
const logger = loggerMiddleware(store);
const exception = exceptionMiddleware(store);
const time = timeMiddleware(store);
store.dispatch = exception(time(logger(next)));
这种虽然实现了中间件的独立,但是当中间件多的时候,还需要一个合并中间件的方法来统一管理
我们定义一个applyMiddleware方法
const applyMiddleware = (...middlewares) => (oldCreateStore) => (reducer, initState) => {
// 生成初始store
const store = oldCreateStore(reducer, initState);
// 给每个middleware传store,
// const chain = [exception, logger];
const chain = middlewares.map(middleware => middleware(store));
let dispatch = store.dispatch;
// 实现 exception(logger(dispatch))
// [a,b,c] => a(b(c))
const reDispatch = chain.reduce((prev, curr) => (...arg) => prev(curr(...arg)));
store.dispatch = reDispatch(store.dispatch);
return store;
}
我们通过applyMiddleware复写了store的dispatch方法,返回了新的store,因此我们目前有两种store
// 没有中间件的createStore
import { createStore } from './redux';
const store = createStore(reducer, initState);
// 存在中间件的 createStore
const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, loggerMiddleware);
const newCreateStore = rewriteCreateStoreFunc(createStore);
const store = newCreateStore(reducer, initState);
可以肯定不符合我们需要的,因此我们需要将createStore增加点判断兼容一下存在中间件的情况
const createStore = (reducer, initState, rewriteCreateStoreFunc) => {
if(rewriteCreateStoreFunc) {
const newCreateStore = rewriteCreateStoreFunc(createStore);
return newCreateStore(reducer, initState);
}
}
最终用法如下:
const rewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, loggerMiddleware);
const store = createStore(reducer, initState, rewriteCreateStoreFunc);
完整的Redux
增加退订功能
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
// 退订
const unsubscribe = store.subscribe(() => {
let state = store.getState();
console.log(state.counter.count);
});
/*退订*/
unsubscribe();
省略initState
function craeteStore(reducer, initState, rewriteCreateStoreFunc){
if (typeof initState === 'function'){
rewriteCreateStoreFunc = initState;
initState = undefined;
}
...
}
compose
我们写的applyMiddleware中,把[A,B,C]转换成A(B(C(next))),是如下实现:
const chain = [A, B, C];
let dispatch = store.dispatch;
chain.reverse().map(middleware => {
dispatch = middleware(dispatch);
})
而redux提供了compose方式
const chain = [A, B, C];
dispatch = compose(...chain)(store.dispatch);
export default function compose(...funcs) {
if(funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
bindActionCreator
使用闭包,隐藏dispatch和actionCreator
const reducer = combineReducers({
counter: counterReducer,
info: infoReducer
})
const store = createStore(reducer);
// 返回 action 的函数叫做 actionCreator
function increment() {
return {
type: 'INCREMENT'
}
}
function setName(name) {
return {
type: 'SET_NAME',
name,
}
}
const actions = {
increment: function() {
return store.dispatch(increment.apply(this, arguments))
},
setName: function() {
return store.dispatch(setName.apply(this, arguments))
}
}
/* 其他地方在实现自增的时候,根本不知道 dispatch,actionCreator等细节 */
actions.increment(); // 自增
actions.setName('codedreamfy'); // 名称
简化一下
const actions = bindActionCreators({ increment, setName }, store.dispatch);
/* 核心的代码在这里,通过闭包隐藏了 actionCreator 和 dispatch */
function bindActionCreator(actionCreator, dispatch) {
return () => dispatch(actionCreator.apply(this, arguments))
}
/* actionCreators 必须是 function 或者 object */
const bindActionCreators = (actionCreators, dispatch) => {
if(typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch);
}
if(typeof actionCreator !== 'object' || actionCreator === null) {
throw new Error()
}
const keys = Object.keys(actionCreator);
const boundActionCreators = {};
for(let i = 0; i< keys.length; i++){
const key = keys[i];
const actionCreator = actionCreators[key];
if(typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
}
}
return boundActionCreator;
}
该源码参考来源于九库前端,在此感谢
Redux
首先是一个状态管理
该文档是跟着九库文档学习的:
假设有一个对象
修改状态
如果我们希望修改后能够被通知呢?那么此时就需要发布订阅模式来解决
目前算是可以了,但是存在两个问题:
可以尝试封装下:
使用一下:
于是一个简单的状态管理雏形就有了:createStore、changeState、getState
但是上面的函数还存在一个问题,就是可以随意改动状态的值,为此我们需要分两步来解决这个问题:
设置一个plan函数,用来接收现有state和一个action要执行的操作,返回经过改变后的state
然后我们将这个计划告诉store,store.changeState以后改变要按照我们的计划修改
尝试使用
到这里,我们给plan和changeState换一个名字,plan改为reducer,changeState改成dispatch
到这里我们会发现reducer的问题:项目中我们有大量的state, 每个state都需要计划函数,如果全部写在一起无法管理,所有的计划写在一个reducer函数里面,会导致reduer函数及其庞大复杂。按照组件维度会拆分出多个reducer函数,然后通过一个函数合并起来。
我们来管理两个state,一个counter,一个info。
它们各自的reducer
我们需要一个函数来合并多个reducer函数合并成一个reducer函数
ok,我们已经把reducer按维度拆分了,通过combineReducers合并起来。但是state还是写在一起,会造成state树很庞大,因此需要对state拆分
ok,目前为止我们已经有一个基本完整的状态管理流程了,但是如果我们想要对dispatch进行扩展或者重写,增强dispatch的功能的
假设我们有一个记录日志的中间件
还有一个记录异常的中间件
如果需要多个中间件进行合作呢?
看起来我们将两个中间件合并了,如果后面中间件变多了怎么处理?
这个时候我们就需要将每个中间件单独提取出来
这个时候如果我们想要把js文件独立出去,会发现一些中间件依赖store,那么就需要再封装一次了~
这种虽然实现了中间件的独立,但是当中间件多的时候,还需要一个合并中间件的方法来统一管理
我们定义一个
applyMiddleware方法我们通过
applyMiddleware复写了store的dispatch方法,返回了新的store,因此我们目前有两种store可以肯定不符合我们需要的,因此我们需要将
createStore增加点判断兼容一下存在中间件的情况最终用法如下:
完整的Redux
增加退订功能
省略initState
compose
我们写的
applyMiddleware中,把[A,B,C]转换成A(B(C(next))),是如下实现:而redux提供了compose方式
bindActionCreator
使用闭包,隐藏dispatch和actionCreator
简化一下
该源码参考来源于九库前端,在此感谢