Redux的核心概念,实现代码与应用示例

Redux是一种JavaScript的状态管理容器,是一个独立的状态管理库,可配合其它框架使用,比如React。引入Redux主要为了使JavaScript中数据管理的方便,易追踪,避免在大型的JavaScript应用中数据状态的使用混乱情况。Redux 试图让 state 的变化变得可预测,为此做了一些行为限制约定,这些限制条件反映在 Redux 的三大原则中。

本文会介绍Redux的几个基本概念和坚持的三大原则,以及完整的回路一下Redux中的数据流。在了解以上这些概念之后,用自己的代码来实现一个简版的Redux,并且用自己实现的Redux结合React框架,做一个简单的TodoList应用示例。希望本文对于初识Redux的同学有一个清晰,全面的认识。

Redux的几个基本概念

一、数据存储 - state

Redux就是用来管理状态数据,所以第一个概念就是状态数据,state就是存放数据的地方,根据应用需要,一般定义成一个对象,比如:

{

    todos: [],

    showType: 'ALL',

    lastUpdate: '2019-10-30 11:56:11'

}


二、行为触发 - action

web应用,所有的数据状态变更,都是由一个行为触发的,比如用户点击,网络加载完成,或者定时事件。在简单应用里面,我们一般都是在行为触发的时候,直接修改对应的数据状态,但是在大型复杂的应用里面,修改同一数据的地方可能很多,每个地方直接修改,会造成数据状态不可维护。

Redux引入了action的概念,每个要改变数据状态的行为,都定义成一个action对象,用一个type来标志是什么行为,行为附带的数据,也都直接放在action对象,比如一个用户输入的行为:

{

    type: 'INPUT_TEXT',

    text: '今天下午6点活动碰头会议'

}

然后通过dispatch触发这个action,dispatch(action)

三、行为响应 - reducer

状态,action的概念了解了,当action触发的时候,肯定要修改state数据,在讲解action的时候有说过,不能直接修改state,我们需要定义一个reducer来修改数据,这个reducer就是一个行为响应函数,他接收当前state,和对应的action对象,根据不同的action,做相应的逻辑判断和数据处理,然后返回一个新的state。

注意,一定是返回一个新的state,不能直接修改参数传入的原state,这是redux的原则之一,后面会讲到。

function reducer ( state = [], action ) {

    switch ( action.type ) {

        case 'INPUT_TEXT':

            return [...state, {text: action.text, id: Math.random() }]

        default:

            return state;

    }

}


四、数据监听 - subscribe

数据的更新已经在reducer中完成了,在一些响应式的web应用中,我们往往需要监听数据状态的变化,这个时候就可以用subscribe了

redux内部保存一个监听队列,listeners,可以调用subscribe来往listeners里面增加新的监听函数,每次reducer修改完state之后,会逐个执行监听函数,而监听函数可以获取已经更新过的state数据了

listeners = [];

subscrible( listener ) {

    listeners.push( listener );

    return function () {

        let index = listeners.index( listener );

        listeners.splice( index, 1 );

    }

}

dispatch( action ) // 触发 action

reducer(state, action)

listeners.map( ( listener ) => {

    listener()

} )


Redux的几大原则

一、单一数据原则

整个应用的数据都在state,并且只有这一个state,这么做的目的是方便管理,整个应用的数据就这一份,调试方便,开发也方便,可以在开发的时候用本地的数据。而且开发同构应用也很方便,比如服务端渲染,把服务端的数据全部放在state,作为web端初始化时候的数据

二、state只读

state的数据对外只读,不能直接修改state,唯一可以修改的方式是触发action,然后通过reducer来处理。

因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心竞态条件(race condition)的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。

三、使用纯函数

先说明下什么是纯函数,纯函数指的是函数内部不修改传入的参数,无副作用,在传参一定的情况下,返回的结果也是一定的。Redux中的Reducer需要设计成存函数,不能直接操作传入的state,需要把改变的数据以一个新的state方式返回。

Redux中的数据流

其实上面讲Redux基本概念的时候已经大概的说了下数据流向方式了,就是: view->action->reducer->state->view,用文字来表述就是,首先由于页面上的某些事件会触发action,通过dispatch(action)来实现,然后通过reducer处理,reducer(state, action)返回一个新的state,完成state的更新,当然对于响应式的应用,会触发listener(),在listener里面获取最新的state状态,完成对应视图(view)的更新。这就是整个redux中的数据流描述,如下图所示:

Redux的实现代码(非官方)

在对Redux的基本概念和几大原则熟悉了之后,可以实现一个自己的Redux了,当然我们一般都直接用官方的npm包,这里自己实现的比较简单,没有做什么入参验证,异常处理之类的,主要是加深下对Redux的理解。下面直接贴代码了,对应的概念都有注释。

// redux.js

// 创建state的函数

// 传入reducer 和初始化的state

function createStore( reducer, initState ) {

    let ref = {};

    let listeners = [];

    let currentState = initState;

    // dispath函数,用来触发action

    function dispatch ( action ) {

        // 触发的action,通过reducer处理

        currentState = reducer( currentState, action )

        // 处理完成后,通知listeners

        for ( let i in listeners ) {

            let listener = listener[ i ];

            listener();

        }

        return action;

    }

    // 返回当前的state

    function getState () {

        return currentState;

    }

    // 订阅state变化, 传入listener,返回取消订阅的function

    function subscribe ( listener ) {

        listeners.push( listener );

        return function () {

            let index = listeners.indexOf( listener );

            if ( index > -1 ) {

                listeners.splice( index, 1 );

            }

        }

    }


    ref = {

        dispatch: dispatch,

        subscribe: subscribe,

        getState: getState

    };

    return ref;

}

function combineReducers( reducers ) {

    return function ( state, action ) {

        let finalState = {};

        let hasChanged = false;

        for ( let key in reducers ) {

            let reducer = reducers[ key ]

            if ( typeof reducer === 'function' ) {

                let keyState = reducer( state && state[ key ], action );

                hasChanged = hasChanged || keyState !== state[ key ];

                finalState[ key ] = keyState;

            }

        }

        return hasChanged ? finalState : state;

    }

}

export { createStore, combineReducers }

是不是觉得怎么才这么点代码,就是这么点代码,而且还包含了一个combineReducers辅助函数,下面再贴一点使用示例代码

// reducer函数,用于处理action

function reducer( state = [], action ) {

    switch( action.type ) {

        case 'INPUT_TEXT':

            return [ ...state, { text: action.text, key: Math.random(), isDo: false }];

        case 'TOGGLE_TODO':

            return state.map( ( item ) => {

                if ( item.key === action.id ) {

                    return {...item, isDo: !item.isDo };

                }

            } );

        default:

            return state;

    }

}

let store = createStore( reducer );

// 在用户输入一条Todo时候

console.log(store.getState());

store.dispatch( { type: 'INPUT_TEXT', text: '这里是一条待办事项' } );

console.log(store.getState());

//在用户点击一条Todo Item的时候,切换完成状态

console.log(store.getState());

store.dispatch( { type: 'TOGGLE_TODO', id: item.key } )

console.log(store.getState());


Redux与React的结合应用示例

下面,利用Redux结合React开发一个简单的Todo工具,页面主要功能点

1、可以添加Todo事项

2、点击事项会切换事项的完成状态

3、可以切换展示全部/已完成/待完成事项

这个实例是基于react,react-redux完成的,项目搭建用的是create-react-app,利用react-redux提供的接口,将redux中的state和action集成到组件中,需要读者熟悉create-react-app的使用,以及react-redux的主要接口功能,以下贴出主要代码,感兴趣的同学可以自己搭建实现

首先定义好state数据结构和action以及对应的reducer

state包含两部分,一是todos,待办事项列表,二是showType,展示类型

action包含这么三种,一是添加新的Todo,二是切换事项完成状态,三是切换展示类型,分别定义好

actions.js

// actions.js

let nextTodoId = 0

export const addTodo = text => {

    return {

        type: 'ADD_TODO',

        id: nextTodoId++,

        text

    };

};

export const setShowType = showType => {

    return {

        type: "SET_SHOW_TYPE",

        showType

    };

};

export const toggleTodo = id => {

    return {

        type: 'TOGGLE_TODO',

        id

    };

};

reducers.js

const todos = ( state = [], action ) => {

    switch ( action.type ) {

        case 'ADD_TODO':

            return [

                ...state,

                {

                    id: action.id,

                    text: action.text,

                    isDo: false

                }

            ];

        case 'TOGGLE_TODO':

            return state.map( todo => {

                return todo.id === action.id ? {...todo, isDo: !todo.isDo } : todo;

            } );

        default:

            return state;

    }

}

const showType = ( state = 'SHOW_ALL', action ) => {

    switch ( action.type ) {

        case 'SET_SHOW_TYPE':

            return action.showType;

        default:

            return state;

    }

}

const todoList = combineReducers({

    todos,

    showType

})

export { todoList }


至此,数据状态redux部分算完成了,接下来实现对应的Component和入口文件了,准备分这么几个组件

1、待办事项Todo

2、输入框 AddTodo

3、待办事项列表TodoList

4、底部展示类型切换Tab

// component.js

import { connnect } from 'react-redux';

import { addTodo, setShowType, toggleTodo } from './actions'

const Todo = ( { onClick, completed, text } ) => (

    <li onClick={onClick} style={{ textDecoration: completed ? 'line-through' : 'none' }}>

        {text}

    </li>

)

const AddTodo = ( { dispatch } ) => {

    let input;

    return (

        <div>

            <form

                onSubmit={ e => {

                    e.preventDefault()

                    if ( !input.value.trim() ) {

                        return;

                    }

                    dispatch( addTodo( input.value ) )

                    input.value = ''

                }}

            >

                <input ref={ node => {input = node } } />

                <button type='submit'>Add Todo</button>

            </form>

        </div>

    )

}

AddTodo = connect()( AddTodo );

const TodoList =  ( { todos, onTodoClick } ) => {

    return (

        <ul>

            {todos.map( todo => (

                <Todo key={todo.id} {...todo} onClick={ () => onTodoClick( todo.id ) } />

            ) )}

        </ul>

    ) };


const getShowTodoList = ( todos, showType ) => {

    switch( showType ) {

        case 'SHOW_ISDO':

            return todos.filter( item => item.isDo );

        case 'SHOW_ACTIVE':

            return todos.filter( item => !item.isDo );

        case 'SHOW_ALL':

        default :

            return todos;

    }

}

const mapStateToProps = state => {

    return {

        todos: getShowTodoList ( state.todos, state.showType)

    };

};

const mapDispatchToProps = dispatch => {

    return {

        onTodoClick: id => {

            dispatch( toggleTodo( id ) );

        }

    };

}

const ShowTodoList = connect(

    mapStateToProps,

    mapDispatchToProps

)( TodoList );


 const Tab = () => (

    <p>

        Show: { ' ' }

        <FilterLink filter='SHOW_ALL'>ALL</FilterLink>

        { ', ' }

        <FilterLink filter='SHOW_ACTIVE'>ACTIVE</FilterLink>

        { ', ' }

        <FilterLink filter='SHOW_ISDO'>ISDO</FilterLink>

    </p>

)

export { AddTodo, ShowTodoList, Tab }


入口文件 index.js

import React from 'react';

import ReactDOM from 'react-dom';

import { Provider } from 'react-redux';

import { createStore } from './redux';

import todoList from './reducers'

import {AddTodo, ShowTodoList, Tab } from './component'

let store = createStore( todoApp );

ReactDOM.render(

    <Provider store={store}>

        <div>

            <AddTodo />

            <ShowTodoList />

            <Tab />

        </div>

    </Provider>

    , document.getElementById('root'));


主要代码完成,npm start 运行,功能截图如下

文章同步发布:https://www.geek-share.com/detail/2783420870.html

参考文章:

原生实现一个react-redux的代码示例

用React实现一个完整的TodoList的示例代码

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

推荐阅读更多精彩内容

  • 1.首先理解reac的工作模式:React对UI层进行了完美的抽象,我们写Web界面时几乎做到完全的去DOM化,在...
    katherine_a120阅读 1,400评论 0 3
  • redux学习笔记 学习动机 随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任...
    flura阅读 357评论 0 0
  • Actions Actions是用于存放数据的载体,通过store.dispatch()函数来将数据从app发送到...
    放风筝的小小马阅读 761评论 0 1
  • 写在开头 本片内容主要为本人在阅读redux官方文档中基础和进阶部分的学习笔记。由于本人能力有限,所以文章中可能会...
    前端开发爱好者阅读 1,173评论 0 4
  • 把小盆友的生日忘了,每天过的急匆匆的,被家庭工作的各种琐事包围着,像个陀螺不停的转。 但是我并不抱怨,因为忙碌是责...
    虾米_2f81阅读 115评论 0 1