简约版redux 的实现

一、为什么使用redux?
  • React本身只留给我们stateprops来管理应用数据的状态,context只是实验性质的一种传递数据的方式。也就是说,React本身并没有很好地提供应用状态管理的解决方案,在一些小应用中我们可能感觉不到状态管理是一个问题,但是随着应用的复杂度增加,这个问题会暴露得越来越明显。这时候我们就需要使用到Redux。
  • 既然React推荐我们尽量少得编写有状态组件,那么我们何不干脆把一个应用的数据状态统一在一个地方管理?这便是Redux的理念。
  • Redux把应用的数据统一存储在一个全局对象当中。把应用所有的数据交互和状态改变,统一用固定形式的action动作对象来描述,经由名为reducer的方法来判断不同的动作来如何改变应用状态,最后我们通过Store对象来负责执行action动作,获取state应用状态,订阅状态改变时的触发的事件。
  • 事实上redux只是提供了一种应用状态管理问题的解决思路,它的原码也是比较少的,我们甚至可以使用一些基本的函数来方法来模拟实现redux 的全部功能,即使我们在应用中使用了redux,编写的大部分也都是原生JS函数和对象,也就是说我们只需要理解redux 的理念,甚至不需要使用redux这个库本身,也能通过他的方式编写代码解决问题。
二、redux的主要功能以及如何使用?
1. redux的主要功能:
  • store里记录所有的状态(state);
  • 需要改变状态的时候,告诉专员(dispatch)要做什么(action);
  • 处理的人(reducer)拿到stateaction生成新的state。
2. 使用方法:
  • 通过reducer新建store,随时通过store.getState()获取状态;
  • 当需要变更状态的时候,通过store.dispatch(action)来修改状态;
  • reducer函数接收stateaction,返回新的state,可以store.subscrible()监听每次修改。
3. redux的基本使用:
index.js

import { createStore } from 'redux';

//新建reducer,根据老的state和action,生成新的state
const  counter= (state = 0,action) => {
    switch(action.type){
        case 'ADD' :
            return state + 1 ;
        case 'REMOVE' :
            return state - 1;
        default:
            return state;
    }
}
  
//新建store
const store = createStore(counter);

const init = store.getState();  
console.log(init);//0

const listener = () => {
    const current = store.getState();
    console.log(current);
}

//通过subscribe订阅事件,每次dispatch的时候会自动触发listener事件
store.subscribe(listener);

//dispatch派发事件,传递一个action
store.dispatch({type:'ADD'});//1

store.dispatch({type:'REMOVE'});//0

注意:
  • store.dispatch()方法可以传递给组件,内部可以调用修改状态;
  • subscribe()方法添加一个变化监听器。每当dispatch action 的时候就会执行,state 树中的一部分可能已经变化。我们可以在回调函数里调用来拿到当前 state。subscribe()方法订阅render函数,每次修改状态都会重新渲染。
4. react和redux一起使用:
  • a. 首先新建一个index.redux.js文件夹,把与redux 相关的内容单独移到这个文件夹下;
  • b. 然后删除index.js下的代码,进行修改,
  • 新建一个App.js。
index.js

import React from 'react';
import ReactDom from 'react-dom';
import { createStore } from 'redux';
import App from './App';
import { counter } from './index.redux';

const store = createStore(counter)
function render(){
    ReactDom.render(
        <App store={store} />,
        document.getElementById('root')
    )
}
render();
store.subscribe(render);

App.js

import React, { Component } from 'react';
import { add,remove } from './index.redux';

class App extends Component {
    render(){
        const store = this.props.store;
        const num = store.getState();
        return(
            <div>
                <p>{ `数值为:${num}` }</p>
                <button onClick={ () => store.dispatch(add())}>加1</button>
                <button onClick={ () => store.dispatch(remove())}>减1</button>
            </div>
        )
    }
}

export default App;
index.redux.js

//action 常量
const ADD = 'ADD';
const REMOVE = 'REMOVE';


//reducer
//新建reducer,根据老的state和action,生成新的state
export const  counter= (state = 0,action) => {
    switch(action.type){
        case ADD :
            return state + 1 ;
        case REMOVE :
            return state - 1;
        default:
            return state;
    }
}


//action creator
export const add = () => {
    return {
        type:ADD
    }
}
export const remove = () => {
    return {
        type:REMOVE
    }
}
5. redux-thunk异步处理action:
  • redux异步处理,使用applyMiddleware开启thunk中间件,这时action可以返回函数值,使用dispatch提交action
  • 主要对上面的代码进行了解耦,以及来使用redux-thunkapplyMiddleware来处理异步action
index.js

import React from 'react';
import ReactDom from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import App from './App';
import { counter, add, remove,addAsync } from './index.redux';

const store = createStore(counter,applyMiddleware(thunk))
function render(){
    ReactDom.render(
        <App store={store} add={add} remove={remove} addAsync={addAsync}/>,
        document.getElementById('root')
    )
}
render();
store.subscribe(render);

App.js

import React, { Component } from 'react';
//import { add, remove } from './index.redux';

class App extends Component {
    render(){
        const store = this.props.store;
        const add = this.props.add;
        const remove = this.props.remove;
        const addAsync = this.props.addAsync;
        const num = store.getState();
        return(
            <div>
                <p>{ `数值为:${num}` }</p>
                <button onClick={ () => store.dispatch(add())}>加1</button>
                <button onClick={ () => store.dispatch(remove())}>减1</button>
                <button onClick={ () => store.dispatch(addAsync())}>异步2秒后加1</button>
            </div>
        )
    }
}

export default App;

index.redux.js

//action 常量
const ADD = 'ADD';
const REMOVE = 'REMOVE';


//reducer
//新建reducer,根据老的state和action,生成新的state
export const  counter= (state = 0,action) => {
    switch(action.type){
        case ADD :
            return state + 1 ;
        case REMOVE :
            return state - 1;
        default:
            return state;
    }
}


//action creator
export const add = () => {
    return {
        type:ADD
    }
}
export const remove = () => {
    return {
        type:REMOVE
    }
}

//使用applyMiddleware 来处理异步action
export const addAsync = () => {
    //thunk 插件的使用,这里可以返回函数
    return dispatch => {
        setTimeout(() => {
            //异步结束后,手动执行dispatch
            dispatch(add());
        },2000)
    }
}
6. 下面使用react-redux:
  • 在使用react-redux的时候我们可以忘记subscribe,只需要记住reducer,actiondispatch即可;
  • react-redux提供了providerconnect两个接口;
  • provider组件在应用的最外层传入store即可,只用一次;
  • connect负责从外部获取组件所需要的参数;
index.js

import React from 'react';
import ReactDom from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import App from './App';
import { counter } from './index.redux';


const store = createStore(counter,compose(
    applyMiddleware(thunk),
    window.devToolsExtension()
));


ReactDom.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root')
)


App.js

import React, { Component } from 'react';
//import { add, remove } from './index.redux';
import { connect } from 'react-redux';
import { add, remove, addAsync } from './index.redux';


class App extends Component {
    render(){
        /*const store = this.props.store;
        const add = this.props.add;
        const remove = this.props.remove;
        const addAsync = this.props.addAsync;
        const num = store.getState();*/
        //const { num, add, remove, addAsync } = this.props;
        const num = this.props.num;
        const add = this.props.add;
        const remove = this.props.remove;
        const addAsync = this.props.addAsync;
        return(
            <div>
                <p>{ `数值为:${num}` }</p>
                <button onClick={ add }>加1</button>
                <button onClick={ remove }>减1</button>
                <button onClick={ addAsync }>异步2秒后加1</button>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        num : state
    }
}


const mapDispatchToProps = { add, remove, addAsync };

export default connect(mapStateToProps,mapDispatchToProps)(App);

index.redux.js


//action 常量
const ADD = 'ADD';
const REMOVE = 'REMOVE';


//reducer
//新建reducer,根据老的state和action,生成新的state
export const  counter= (state = 0,action) => {
    switch(action.type){
        case ADD :
            return state + 1 ;
        case REMOVE :
            return state - 1;
        default:
            return state;
    }
}


//action creator
export const add = () => {
    return {
        type:ADD
    }
}
export const remove = () => {
    return {
        type:REMOVE
    }
}

//使用applyMiddleware 来处理异步action
export const addAsync = () => {
    //thunk 插件的使用,这里可以返回函数
    return dispatch => {
        setTimeout(() => {
            //异步结束后,手动执行dispatch
            dispatch(add());
        },2000)
    }
}
三、模拟实现redux:

模拟实现createStore

export const createStore = (reducer) => {
    //当前的内部状态currentState,刚开始为空
    let currentState = {};
    //监听器数组
    let currentListeners = [];

    //getState函数获取当前应用的状态并返回
    const getState = () => {
        return currentState;
    }

    //订阅事件函数,传递一个listener监听器
    const subscribe = (listener) => {
        //把当前的监听器push到监听器数组currentListeners内
        currentListeners.push(listener);
    }

    //dispatch事件函数,传入action
    const dispatch = (action) => {
        /* 调用传递进来的reducer函数,把当前的内部状态currentState和action
        *  传入reducer,并且把reducer调用后的结果赋值给当前的状态currentState
        */
        currentState = reducer(currentState,action);

        //在dispatch的时候,执行一下每一个监听器函数
        currentListeners.forEach( v =>v() );

        //返回action
        return action;
    }

    //在初始化的时候执行一个默认的action
    dispatch({type:'@@redux/INIT'})

    //最后返回getState,subscribe,dispatch三个函数
    return { getState, subscribe, dispatch };
}

模拟实现Provider

export class Provider extends React.Component{
    static childContextTypes = {
        store: PropTypes.object
    }
    getChildContext(){
        return {store:this.store}
    }
    constructor(props, context){
        super(props, context)
        this.store = props.store
    }
    render(){
        return this.props.children
    }
}

模拟实现connect

export const connect = (mapStateToProps=state=>state,mapDispatchToProps={}) =>(WrapComponent)=>{
    return class ConnectComponent extends React.Component {
        static contextTypes = {
            store:PropTypes.object
        }
        constructor(props,context){
            super(props,context);
            this.state = {
                props:{}
            }
        }

        componentDidMount(){
            //从Provider中用context获取store
            const { store } = this.context;
            //每当dispa的时候,调用一次this.update()更新一下props
            store.subscribe(()=>this.update())

            //页面初识化的时候,更新一下props
            this.update();
        }

        //获取mapStateToProps和mapDispatchToProps放入this.props里
        update(){
            //从Provider中用context获取store
            const { store } = this.context;
            //connect内部执行mapStateToProps返回state
            const stateProps = mapStateToProps(store.getState());
            //方法需要dispatch,bindActionCreators把actionCreator用dispatch包了一层
            const dispatchProps = bindActionCreators(mapDispatchToProps,store.dispatch)
            this.setState({
                props:{
                    ...this.state.props,
                    ...stateProps,//整合本身的props和stae里面获取到的props,也就是把state放入到prop里面
                    ...dispatchProps//把action放入里面
                }
            })
        }

        render(){
            return <WrapComponent {...this.state.props}></WrapComponent>
        }
    }
}

模拟实现bindActionCreators

//绑定creator和dispatch
//相当于dispatch(actionCreator())返回state
 const bindActionCreator = (creator,dispatch) => {
    //返回一个用dispatch包了一层的actionCreator
    return (...args) => dispatch(creator(...args))
 }

//传入actionCreators,dispatch
export const bindActionCreators = (creators,dispatch) => {
    let bound = {};
    Object.keys(creators).forEach((v) => {
        //得到每一个actionCreator
        let creator = creators[v]
        bound[v] = bindActionCreator(creator,dispatch)
    })
    return bound;

    /*return Object.keys(creators).reduce((ret,item) => {
        ret[item] = bindActionCreator(creators[item],dispatch);
        return ret;
    },{})*/
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容