手写redux1-基本结构

116 阅读4分钟

redux单独使用:

import {createStore} from './redux';
const ADD = 'ADD';
const MINUS = 'MINUS';
let initialState=0;
function reducer(oldState,action){
  switch(action.type){
    case ADD:
      return oldState+1;
    case MINUS:
      return oldState-1;
    default:
      return oldState;    
  }
}

let store = createStore(reducer,initialState);
let counterValue = document.getElementById('counter-value');
let addBtn = document.getElementById('add-btn');
let minusBtn = document.getElementById('minus-btn');
function render(){
  counterValue.innerHTML = store.getState();
}
render();
store.subscribe(render);
addBtn.addEventListener('click',()=>store.dispatch({type:ADD}));
minusBtn.addEventListener('click',()=>store.dispatch({type:MINUS}));
function createStore(reducer,preloadedState){
  let state = preloadedState;
  let listeners = [];
  function getState(){
    return state;
  }
  function dispatch(action){
    //根据老状态和action动作,计算新状态
    state = reducer(state,action);
    listeners.forEach(l=>l());
  }
  function subscribe(listener){
    listeners.push(listener);
  }
  return {
    getState,//用来获取当前的仓库中的状态
    dispatch,//向仓库派发动作
    subscribe,//用来订阅仓库中的状态的变化
  }
}
export default createStore;

bindActionCreators其实就是把页面用到的action放到了一起管理,实际使用场景不太多

使用及实现的案例:

let actions = {
    add(){
        return {type:ADD};
    },
    minus(){
        return {type:MINUS};
    }
}

const intialState = {number:0};
function reducer(state=intialState,action){
    switch(action.type){
        case ADD:
            return {number:state.number+1};
        case MINUS:
            return {number:state.number-1};
        default:
            return state;    
    }
}
let store = createStore(reducer);

//let boundAdd = bindActionCreators(add,store.dispatch);
//let boundMinus = bindActionCreators(minus,store.dispatch);

let boundActions = bindActionCreators(actions,store.dispatch);
class Counter1 extends Component {
    state = {number:store.getState().number}
    componentDidMount(){
        this.unsubscribe = store.subscribe(()=>{
            this.setState({number:store.getState().number});
        });
    }
    componentWillUnmount(){
        this.unsubscribe();
    }
    render() {
        return (
            <div>
                <p>{store.getState().number}</p>
                <button onClick={boundActions.add}>+</button>
                <button onClick={boundActions.minus}>-</button>
            </div>
        )
    }
}
export default Counter1;
function bindActionCreator(actionCreator,dispatch){
    return function(...args){
        return dispatch(actionCreator.apply(this,args));
    }
}
/**
 * 
 * @param {*} actionCreators action的创建者 此处可以只传一个创建者,也就是一个函数,也可以传一个对象
 * @param {*} dispatch 
 */
function bindActionCreators(actionCreators,dispatch){
   if(typeof actionCreators === 'function'){
   		 return bindActionCreator(actionCreators,dispatch)
   }    
   const boundActionCreators = {};
   for(const key in actionCreators){
       const actionCreator = actionCreators[key];
       boundActionCreators[key]=bindActionCreator(actionCreator,dispatch)
   }
   return boundActionCreators;
}
export default bindActionCreators;

redux的源码中,createStore中还会默认调用一次dispatch:dispatch({type:'@@REDXU/INIT'});

 function createStore(reducer,preloadedState){
    console.log('exec createStore')
    let state = preloadedState;
    let listeners = [];
    function getState(){
        return state;
    }
    function dispatch(action){
      //根据老状态和action动作,计算新状态
      state = reducer(state,action);
      listeners.forEach(l=>l());
    }
    function subscribe(listener){
      listeners.push(listener);
    }
    dispatch({type:'@@REDXU/INIT'});
    return {
      getState,//用来获取当前的仓库中的状态
      dispatch,//向仓库派发动作
      subscribe,//用来订阅仓库中的状态的变化
    }
  }
  export default createStore;

以Counter组件为例,代码执行的顺序为:

1、createStore(reducer)

2、createStore(reducer) -> dispatch({type:'@@REDXU/INIT'})

3、dispatch({type:'@@REDXU/INIT'}) -> reducer(state,action)

4、第3步中的reducer的第1个参数state依次从preloadedState intialState将初始值接管过来

5、这个type值为'@@REDXU/INIT'的action在reducer中没有匹配到任何的action,所以会走最后的default,即直接return state

6、用新的值执行listener监听

无论项目多大,store只维护一棵大的state树,项目开发中通常会按照业务将state分为若干类,所以需要把所有的state整合在一起

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Counter1 from './components/Counter1';
import Counter2 from './components/Counter2';
ReactDOM.render(<><Counter1/><Counter2/></>,document.getElementById('root'));

=====================================
// Counter1.jsx
import React, { Component } from 'react'
import {bindActionCreators} from 'zredux';
import store from '../store';
import actions from '../store/actions/counter1';
console.log('import Counter1')
let boundActions = bindActionCreators(actions,store.dispatch);
 class Counter1 extends Component {

=====================================
// store/index.js
import {createStore} from 'zredux';
import rootReducer from './reducers';
console.log('import store index')
let store = createStore(rootReducer);
export default store;

=====================================
// store/reducers/index.js
import {combineReducers} from 'zredux';
import counter1 from './counter1';
import counter2 from './counter2';
console.log('import combineReducers index')
let rootReducer = combineReducers({
    counter1,
    counter2
});

export default rootReducer;

=====================================
// store/reducers/counter1
import * as actionTypes from '../action-types'
//Counter1组件对应的state
console.log('import counter1 reducers')
let intialState = {number:0,color:'black'};
//Counter1组件对应的reducer
function counter1(state=intialState,action){
    switch(action.type){
        case actionTypes.ADD1:
            return {...state,number:state.number+1};
        case actionTypes.MINUS1:
            return {...state,number:state.number-1};
        case actionTypes.CHANGE_COLOR:
            return {...state,color:action.payload};    
        default:
            return state;    
    }
}
export default counter1
console.log('import combineReducers')
function combineReducers(reducers){
    /**
     * state 老的总状态
     * action 动作
     */
    return function(state={},action){
        let nextState = {};
        //reducers = {counter1,counter2}
        for(let key in reducers){
            //nextState.counter1 = counter1(oldCounter1State,action);
            nextState[key]=reducers[key](state[key],action);
        }
        return nextState;
    }
}
export default combineReducers;

当在文件中引用自己写的redux时经常需要将import {createStore} from 'redux'这种代码改为import {createStore} from '../redux',这种改动比较繁琐,所以我们可以将createStore等文件放到一个自定义目录下,例如zindex,然后在jsconfig.json中添加配置:

{
    "compilerOptions": {
      "baseUrl": "src"
    }
}

这样就可以通过import {createStore} from 'zredux'引用了

以上述代码为例,我们分析一下store整体的执行流程:

  1. createStore.js:6 import createStore
  2. combineReducers.js:1 import combineReducers
  3. counter1.js:4 import counter1 reducers
  4. counter2.js:4 import counter2 reducers
  5. index.js:4 import combineReducers index
  6. combineReducers.js:3 exec combineReducers
  7. index.js:3 import store index
  8. createStore.js:8 exec createStore
  9. Counter1.js:5 import Counter1
  10. bindActionCreators.js:17 exec bindActionCreators
  11. Counter2.js:5 import Counter2
  12. bindActionCreators.js:17 exec bindActionCreators
  13. Counter1.js:10 register subscribe

可以看到,js会递归import(1-5步),之后在第6步中,执行combineReducers,也就是这段代码的执行:

let rootReducer = combineReducers({
    counter1,
    counter2
});

执行之后,会返回一个函数:

closure: reducers
rootReducer === function(state={},action){
  let nextState = {};
  //reducers = {counter1,counter2}
  for(let key in reducers){
    //nextState.counter1 = counter1(oldCounter1State,action);
    nextState[key]=reducers[key](state[key],action);
  }
  return nextState;
}

这个rootReducer将会作为参数传入createStore中创建出store对象来

创建store的过程中通过dispatch({type:'@@REDXU/INIT'})初始化,再执行rootReducer方法,即:

rootReducer(preloadedState, {type:'@@REDXU/INIT'})

preloadedState在此处是undefined,所以state默认为空对象

这个函数在执行时,闭包中存着所有的reducers,结构为:

{
  closure intialState = {number:0,color:'black'};
	counter1: function (state=intialState,action){
    switch(action.type){
        case actionTypes.ADD1:
            return {...state,number:state.number+1};
        case actionTypes.MINUS1:
            return {...state,number:state.number-1};
        case actionTypes.CHANGE_COLOR:
            return {...state,color:action.payload};    
        default:
            return state;    
    }
	},
  closure intialState = {number:0,color:'black'};
	counter2: function (state=intialState,action){
    switch(action.type){
        case actionTypes.ADD2:
            return {...state,number:state.number+1};
        case actionTypes.MINUS2:
            return {...state,number:state.number-1};
        case actionTypes.CHANGE_COLOR:
            return {...state,color:action.payload};    
        default:
            return state;    
    }
	},
}

这几个reducer将会依次执行,执行时由于state['counter1']和state['counter2']都是undefined,所以默认会赋值为initialState

又由于'@@REDXU/INIT'这种类型的action不匹配任何actionType,所以都走default,直接将state返回

所以本次rootReducer执行后返回的结果为:

{
	counter1: {number:0,color:'black'},
	counter2: {number:0,color:'black'},
}

dispatch中接到这个state,将其赋值给闭包state:

		closure state = preloadedState;
		function dispatch(action){
      //根据老状态和action动作,计算新状态
      state = reducer(state,action);
      listeners.forEach(l=>l());
    }

然后监听函数listeners就会挨个执行