热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

Redux是一种JavaScript的状态管理容器,是一个独立的状态管理库,可配合其它框架使用,比如React。引入Redux主要为了使JavaScript中数据管理的方便,易追踪

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中的数据流描述,如下图所示:

image.png

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 } ) => (
    
        {text}
    
)
const AddTodo = ( { dispatch } ) => {
    let input;
    return (
        


                            OnSubmit={ e => {
                    e.preventDefault()
                    if ( !input.value.trim() ) {
                        return;
                    }
                    dispatch( addTodo( input.value ) )
                    input.value = ''
                }}
            >
                 {input = node } } />
                Add Todo
            
        

    )
}
AddTodo = connect()( AddTodo );
const TodoList =  ( { todos, onTodoClick } ) => {
    return (
        

                {todos.map( todo => (
                     onTodoClick( todo.id ) } />
                ) )}
            

    ) };
    
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 = () => (
    


        Show: { ' ' }
        ALL
        { ', ' }
        ACTIVE
        { ', ' }
        ISDO
    


)
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(
    


        


            
            
            
        

    
    , document.getElementById('root'));

 

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

image.png

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

参考文章:

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

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



推荐阅读
  • 本文介绍如何利用Python中的Epoll机制构建一个高效的Web服务器,该服务器能够处理多个并发连接,并向每个连接的客户端返回预定义的响应文本。通过使用Epoll,服务器可以实现高性能的I/O多路复用。 ... [详细]
  • 本文介绍如何通过自定义控件LoadLayout实现ListView的上拉加载更多和下拉刷新功能。LoadLayout支持上拉加载,而下拉刷新则利用了android.support.v4.widget.SwipeRefreshLayout组件。 ... [详细]
  • 本文档详细介绍了Robot Framework的基础知识、安装配置方法及其实用技巧。从环境搭建到编写第一个测试用例,涵盖了一系列实用的操作指南和最佳实践。 ... [详细]
  • Java 动态代理详解与示例
    本文详细介绍了Java中的动态代理机制,包括如何定义接口、实现类和代理处理器,并通过具体示例演示了动态代理的创建和使用过程。 ... [详细]
  • 本文详细探讨了JSP环境下数据库连接的实现方法,包括环境配置、代码示例以及常见的连接问题及其解决方案。 ... [详细]
  • 本文介绍了Android中常见的动画类型及其应用场景,通过具体的代码示例展示了如何在Activity跳转时添加平滑过渡效果,提升用户体验。 ... [详细]
  • 下面根据配置文件,来说明一些底层与webservices的关系:回顾一下servlet的映射模式。我们知道,servlet是从javax.servlet.http.HttpServ ... [详细]
  • 应用程序配置详解
    本文介绍了配置文件的关键特性及其在不同场景下的应用,重点探讨了Machine.Config和Web.Config两种主要配置文件的用途和配置方法。文章还详细解释了如何利用XML格式的配置文件来调整应用程序的行为,包括自定义配置、错误处理、身份验证和授权设置。 ... [详细]
  • 本文探讨了一个项目中遇到的挑战,即如何通过技术手段解决不同菜单项触发时,跨域IFrame页面的高度自适应问题。通过创建中介页面和利用JavaScript与Cookie机制,实现无缝的用户体验。 ... [详细]
  • Node.js中子进程的创建与管理详解
    本文深入探讨了Node.js中如何使用child_process模块来创建和管理子进程,包括exec、spawn和fork三种方法的具体应用及其实现细节。 ... [详细]
  • CGroups: 资源管理和控制
    CGroups(Control Groups)是Linux内核提供的一个功能,旨在限制、记录和隔离进程组使用的物理资源,如CPU、内存和I/O等。它通过精细的资源管理,支持现代容器技术如Docker的资源限制需求。 ... [详细]
  • Flask框架入门指南:Windows平台下的首个Python 2.7项目
    本文将指导您如何在Windows平台上使用Python 2.7搭建一个简单的Flask应用,包括项目结构的创建、基本路由的设置以及HTML模板的设计。 ... [详细]
  • 本人最近在学习python,在看了一些教程后,用python写了一个简单的云音乐播放器,下面把主要代码贴上来,其中用到了gi ... [详细]
  • 本文详细介绍了HTML5中的文件操作API,包括FileList、Blob、File和FileReader等重要JavaScript对象的接口定义及其功能特性。 ... [详细]
  • 本文探讨了使用Lighttpd与FastCGI实现分布式部署的方法。通过在中心服务器上配置Lighttpd负责请求转发,同时在多个远程服务器上运行FastCGI进程来处理实际业务逻辑,从而提高系统的负载能力和响应速度。 ... [详细]
author-avatar
李da寕
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有