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
参考文章:
Redux的核心概念,实现代码与应用示例的更多相关文章
- Redux 核心概念
http://gaearon.github.io/redux/index.html ,文档在 http://rackt.github.io/redux/index.html .本文不是官方文档的翻译. ...
- redux核心思路和代码解析
最近在公司内部培训的时候,发现很多小伙伴只是会用redux.react-redux.redux-thunk的api,对于其中的实现原理和数据真正的流向不是特别的清楚,知其然,也要知其所以然,其实red ...
- 领域驱动设计(DDD)部分核心概念的个人理解
领域驱动设计(DDD)是一种基于模型驱动的软件设计方式.它以领域为核心,分析领域中的问题,通过建立一个领域模型来有效的解决领域中的核心的复杂问题.Eric Ivans为领域驱动设计提出了大量的最佳实践 ...
- Javascript本质第一篇:核心概念
很多人在使用Javascript之前都至少使用过C++.C#或Java,面向对象的编程思想已经根深蒂固,恰好Javascript在语法上借鉴了Java,虽然方便了Javascript的入门,但要深入理 ...
- [程序设计语言]-[核心概念]-02:名字、作用域和约束(Bindings)
本系列导航 本系列其他文章目录请戳这里. 1.名字.约束时间(Binding Time) 在本篇博文开始前先介绍两个约定:第一个是“对象”,除非在介绍面向对象语言时,本系列中出现的对象均是指任何可以有 ...
- spring技术核心概念纪要
一.背景 springframework 从最初的2.5版本发展至今,期间已经发生了非常多的修正及优化.许多新特性及模块的出现,使得整个框架体系显得越趋庞大,同时也带来了学习及理解上的困难. 本文阐述 ...
- Maven的几个核心概念
POM (Project Object Model) 一个项目所有的配置都放置在 POM 文件中:定义项目的类型.名字,管理依赖关系,定制插件的行为等等.比如说,你可以配置 compiler 插件让它 ...
- Maven介绍,包括作用、核心概念、用法、常用命令、扩展及配置
由浅入深,主要介绍maven的用途.核心概念(Pom.Repositories.Artifact.Build Lifecycle.Goal).用法(Archetype意义及创建各种项目).maven常 ...
- 《JavaScript核心概念》基础部分重点摘录
注:<JavaScript核心概念>适合深入了解JavaScript,比我买的<JavaScript框架设计>语言和内容丰富多了(可能是我水平尚浅吧). 1. 作用域 var ...
随机推荐
- 11.Django基础九之中间件
一 前戏 我们在前面的课程中已经学会了给视图函数加装饰器来判断是用户是否登录,把没有登录的用户请求跳转到登录页面.我们通过给几个特定视图函数加装饰器实现了这个需求.但是以后添加的视图函数可能也需要加上 ...
- Spring Boot(三) 使用Lombok
C#写的多了用习惯了众多的语法糖,再写起来Java总会有一些非常不舒服的地方.比如用惯了C#的属性在用起来Java的属性,写起来就会感觉不够优雅.如:定义一个Person类 public cl ...
- httpclient整理
package com.yjl.util; import net.sf.json.JSONObject; import org.apache.commons.lang3.StringUtils; im ...
- 虚拟机ubuntu 网速慢的解决方法
其实虚拟机网速慢,我觉得就两个限制因素.一个是虚拟机的内存,内存小了,上传和下载的速率就慢了,就像内存小的虚拟机跑得慢是一个道理:还有一个就是网络连接方式,这里我使用的是桥接,之前我使用的NAT模式, ...
- uC/OS-III 时间管理(二)
时间管理就是一种建立在时钟节拍上,对操作系统任务的运行实现时间上管理的一种系统内核机制. 常用以下五个函数: OSTimeDly() OSTimeDlyHMSM() OSTimeDlyResume() ...
- ServiceStack.Redis高效封装和简易破解
1.ServiceStack.Redis封装 封装的Redis操作类名为RedisHandle,如下代码块(只展示部分代码),它的特点: 1)使用连接池管理连接,见代码中的PooledClientMa ...
- MongoDB 学习笔记之 索引选项和重建索引
索引选项: {background:true}在后台创建索引,索引在构建过程中,其他客户端仍然可以查询数据,不会阻塞. db.comments.createIndex({anonymous: 1},{ ...
- spring源码分析系列4:ApplicationContext研究
ApplicationContext接口 首先看一下一个最基本的上下文应该是什么样子 ApplicationContext接口的注释里写的很清楚: 一个基本applicationContext应该提供 ...
- ReentrantLock源码学习总结 (一)
[^ ]: 以下源码分析基于JDK1.8 ReentrantLock 示例 private ReentrantLock lock = new ReentrantLock(true); public v ...
- CSS3属性—— line-clamp控制文本行数
说明: 限制在一个块元素显示的文本的行数. -webkit-line-clamp 是一个 不规范的属性(unsupported WebKit property),它没有出现在 CSS 规范草案中. 为 ...