原理分析之Vuex,Redux

397 阅读4分钟

Vuex

基本使用

vue add vuex

状态和状态变更

state保存数据状态,mutations用于修改状态,store.js :

export default new Vuex.Store({ 
    state: { count:0 }, 
    mutations: { 
        increment(state) { 
            state.count += 1; 
        } 
    } 
})

使用状态

<template> 
    <div> 
        <div>{{$store.state.count}}</div> 
        <button @click="add">加一</button> 
    </div> 
</template> 
<script>
    export default {
        methods:{
            add(){
                this.$store.commit('increment')
            }    
        }
    }
</script>

派生状态 - getters

从state派生出新状态,类似计算属性:

export default new Vuex.Store({ 
    getters: { 
        left(state) { 
            // 计算剩余数量 
            return 10 - state.count; 
        } 
    } 
})

使用getters

<span>还剩{{$store.getters.left}}个</span> 

动作 - actions

复杂业务逻辑,类似于controller:

export default new Vuex.Store({ 
    actions: { 
        increment({ getters, commit }) { 
            // 添加业务逻辑 
            if (getters.left > 0) { 
                commit("increment"); 
                return true;
            }
            return false;
        },
        asyncIncrement({ dispatch }) { 
            // 异步逻辑返回Promise 
            return new Promise(resolve => { 
                setTimeout(() => { 
                    // 复用其他action 
                    resolve(dispatch("increment")); 
                }, 1000); 
            }); 
        }, 
    } 
})

使用actions:

<template> 
    <div id="app"> 
        <button @click="asyncAdd">异步加一</button> 
        </div> 
</template> 
<script> 
    export default { 
        methods: { 
            add() { 
                // 即使action执行同步代码返回的结果依然是promise 
                this.$store.dispatch("increment").then(result => { 
                    if (!result) {  alert("失败"); } 
                }); 
            },
            asyncAdd() { 
                this.$store.dispatch("asyncIncrement").then(result => { 
                    if (!result) { alert("失败"); } 
                }); 
            } 
    };
</script>

模块化

按模块化的方式编写代码,store.js:

const count = { 
    namespaced: true, 
    // ... 
};
export default new Vuex.Store({ 
    modules: {a: count} 
}); 

使用变化:

//使用时加上前缀:模块名/
<template> 
    <div id="app"> 
        <div>{{$store.state.a.count}}</div> 
        <p>{{$store.getters['a/score']}}</p> 
        <button @click="add">加一</button> 
        <button @click="addAsync">异步加一</button> 
    </div> 
</template> 
<script> 
export default {
    methods: { 
        add() { 
            this.$store.commit("a/increment"); 
        },
        addAsync() { 
            this.$store.dispatch("a/incrementAsync"); 
        } 
    } 
};
</script> 

源码分析

实现一个Store的类,里面需要包含dispatche方法,commit方法,然后本身是一个插件,需要实现install方法,并混入到vue的生命周期中。

开始写源码

class Store(){
    //options是配置对象store.js,里面包含了state,actions,mutations,getters...
    constructor(options={}){
        //把传递进来的state作响应话处理
        this.state = new Vue({
            data:options.state
        })
        //保存mutations
        this.mutations = options.mutations || {}
        //保存actions
        this.actions = options.actions || {}
        // 保存getters
        this.getters = {}
        // 拦截getters
        Object.keys(options.getters).forEach(item=>{
            this.registerGetter(this,options.getters[item],item)
        })
    }
    //实现dispatche方法
    dispatche(type,arg){
        //type是actions中的函数名,执行action时,注入getter,commit,state,dispatche...
        this.actions[type]({
            getter:options.getter,
            commit:this.commit,
            state:this.state,
            dispatche:this.dispatche,
        },arg)
    }
    //实现commit方法,这里用箭头函数,action中会调用commit
    commit=(type,arg)=>{
        //type是mutations中的函数名,执行mutation时,注入state
        this.mutations[this.type](this.state,arg)
    }
    // 定义一个拦截getters的方法
    const registGetter = functions(store,fn,name) =>{
        Object.defineProperty(store.getters,name,{
            get:function(){
                return fn(store.state)    //注入state
            }
        })
    }
}
//实现插件机制
function install(Vue){
    Vue.mixin({
        beforeCreate(){
            if(this.$options.store){
                //挂载全局对象$store
                Vue.prototype.$store = this.$options.store
            }
        }
    })
}
export default {Store,install}

Redux

流程控制

store保存状态,dispatch(action)派发一个更新操作,reducer纯函数负责状态更新,getState获取状态。

什么是reducer

reducer 就是⼀个纯函数,接收旧的 state 和 action,返回新的state。

(previousState, action) => newState

什么是reduce? 组件聚合

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));

定义三个函数

function f1() {
 console.log("f1");
}
function f2() {
 console.log("f2");
}
function f3() {
 console.log("f3");
}

要想输出f1,f2,f3,可以使用聚合函数

//方法一
f1();f2();f3()
//方法二
f3(f2(f1()));
//方法三,聚合函数
function compose(...funcs) {
 if (funcs.length === 0) {
     return arg => arg
 }
 if (funcs.length === 1) {
     return funcs[0]
 }
 return funcs.reduce((a, b) => (...args) => {
     a(b(...args))
 })
}
compose(
 f1,
 f2,
 f3,
)();

redux基本使用

1. 需要⼀个store来存储数据(createStore)
2. store⾥的reducer初始化state并定义state修改规则
3. 通过dispatch⼀个action来提交对数据的修改
4. action提交到reducer函数⾥,根据传⼊的action的type,返回新的state
5. subscribe 变更订阅
6. getState 获取状态值

这里尤其要注意reduc中订阅数据变更的逻辑:

import store from './store/ReduxStore'
const render = ()=>{
 ReactDom.render(
     <App/>,
     document.querySelector('#root')
 )
}
render()
store.subscribe(render)

每次都重新调⽤render和getState太low了,想⽤更react的⽅式来写,需要react-redux⽀持

提供了两个api:
1. Provider 为后代组件提供store
2. connect 为组件提供数据和变更⽅法

react-redux基本使用

全局提供store,index.js

import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import store from './store/ReactReduxStore'
import { Provider } from 'react-redux'
ReactDom.render(
    <Provider store={store}>
         <App/>
     </Provider>,
     document.querySelector('#root') 
)

获取状态数据

import React, { Component } from "react";
import { connect } from "react-redux";
class ReactReduxPage extends Component {
 render() {
     const { num, add, minus, asyAdd } = this.props;
     return (
         <div>
             <h1>ReactReduxPage</h1>
             <p>{num}</p>
             <button onClick={add}>add</button>
             <button onClick={minus}>minus</button>
             {/* <button onClick={asyAdd}>asyAdd</button>*/}
         </div>
     );
  }
}
const mapStateToProps = state => {
   return {
       num: state,
   };
};
const mapDispatchToProps = {
   add: () => {
       return { type: "add" };
   },
   minus: () => {
       return { type: "minus" };
   },
   // 必须使用中间件才能运作
   // asyAdd: () => {
   // setTimeout(() => { 
        // return { type: "add" };
   // }, 1000);
   // },
};
export default connect(
 mapStateToProps, //状态映射 mapStateToProps
 mapDispatchToProps, //派发事件映射
)(ReactReduxPage)

异步

Redux只是个纯粹的状态管理器,默认只⽀持同步,实现异步任务⽐如延迟,⽹络请求,需
中间件的⽀持,⽐如我们试⽤最简单的redux-thunk和redux-logger

import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import counterReducer from './counterReducer'
const store = createStore(counterReducer,applyMiddleware(logger, thunk));

然后就可以进行异步操作了

asyAdd: () => dispatch => {
    setTimeout(() => {
        // 异步结束后,⼿动执⾏dispatch
        dispatch({ type: "add" });
    }, 1000);
},

redux源码分析

redux中需要实现一个createStore方法,参数(reducer,applyMiddleware),所以还需要实现applyMiddleware方法,内部维护者一个state,同时需要返回getState,dispatch,subcribe。

redux源码实现

export function createStore(reducer,enhancer){
    //enhancer=applyMiddleware(...)
    if(enhancer){
        //如果由中间件,就使用中间件处理
        return enhancer(createStore)
    }
    //维护的状态
    let currentState = undefined
    //监听者
    let currentListeners  = []
    //获取状态的函数
    getState(){
        return currentState
    }
    //派发事件额函数
    dispatch(action){
        //修改state
        curentState = reducer(curentState,action)
        //通知监听者更新
        currentListeners.forEach(func=>func())
        //返回action供中间件使用,如打印日志
        return action
    }
    //订阅的函数
    subcribe(listener){
        currentListeners.push(listener)
    }
    //初始化currentState,走reducer中的默认流程,type匹配不上即可
    dispatch({type:'init-state-xxx'})
    //返回一个对象,包含上面的方法
    return {getState,dispatch,subscribe}
}


//中间件实现
export function applyMiddleware(...middlewares){
    //enhance(createStore)
    return createStore => (...args) =>{
        const store = createStore(...args)
        //获取dispatch,目的是为了加工返回一个新的dispatch
        let dispatch = store.dispatch
        // 传递给中间件的参数,中间件就可以获取状态,改变状态了
        let params = {
            getState:store.getState,
            dispatch:dispatch
        }
        //链式调用中间件
        const middlewareChain = middlewares.map(md=>md(params))
        //聚合函数加工dispatch
        dispatch = compose(...middlewareChain)(store.dispatch)
        //返回的是被加工处理了的dispatch
        return {...store,dispatch}
    }
}

reduc-thunk,redux-logger

function logger({ getState }) {  
    //compose(...middlewareChain)(store.dispatch)
    return dispatch => action => {    
        action.type && console.log(action.type + "执行啦!");    
        return dispatch(action);  
    };
}
function thunk({ getState}) {
    //compose(...middlewareChain)(store.dispatch)
    return dispatch => action => {    
        //asyAdd: () => (dispatch,getState) => {
            //setTimeout(() => {
                //dispatch({ type: "add" });
            //}, 1000);
        //},
        //上面这种情况就是action就是function
        if (typeof action === "function") {     
             return action(dispatch, getState);    
        } else {
            return dispatch(action);    
        }
     };
}

react-redux源码分析

1.实现⼀个⾼阶函数connect,可以根据传⼊状态映射规则函数和派发器映射规则函数映射需
的属性(mapStateToProps,mapDispatchToProps),可以处理变更检测和刷新任务。
2.实现⼀个Provider组件可以传递store。

react-redux源码实现

//hooks实现Provider,connect
import React, {useState,useEffect,useContext} from 'react'
//创建context
const Context = React.createContext()
//导出Provide组件
export function Provider({store,children}){
    return <Context.Provider value={store}>{children}</Context.Provider>
}
//导出高阶组件connect,接受mapStateToProps,mapDispatchToProps,和一个组件作为参数,返回一个组件
export const connect = (
    mapStateToProps:state=>state,    //mapStateToProps的默认值
    mapDispatchToProps: {}
) => Cmp => props =>{
    //从Provider的value中获取store
    const store = userContext(Context)
    //定义一个方法获取最新的moreProps,初始化和数据变更时都要用
    getMoreProps(){
        const stateProps = mapStateToProps(store.getState()),
        //{add:()=>{type:'add'}}=> add:dispatch(type:'add')
        const dispatchProps = bindActionCreators({
            mapDispatchToProps,
            store.dispatch
        })
        return {...stateProps,...dispatchProps }
    }
    //对新加入的props做响应式处理
    useEffect(()=>{
        store.subscribe(()=>{
            //获取最新的数据并setState
            setMorePros(getMoreProps())
        })
    },[])
    //将要map的所有新属性放在moreProps中
    const [moreProps,setMorePros] = useState(getMoreProps())
    //返回一个添加了新属性的组件
    return <Cmp {...props} {...moreProps} />
}

// 定义一个工具函数转换mapDispatchToProps 
// {add:()=>{type:'add'}}=> add:()=>dispatch(type:'add')
function bindActionCreators(actionCreators,dispatch){
    var obj = {}
    for(var key in actionCreators){
        obj[key] = dispatch(actionCreators[key])
    }
    return obj
}