Redux是一种JavaScript的状态管理容器,是一个独立的状态管理库,可配合其它框架使用,好比React。引入Redux主要为了使JavaScript中数据管理的方便,易追踪,避免在大型的JavaScript应用中数据状态的使用混乱状况。Redux 试图让 state 的变化变得可预测,为此作了一些行为限制约定,这些限制条件反映在 Redux 的三大原则中。html
本文会介绍Redux的几个基本概念和坚持的三大原则,以及完整的回路一下Redux中的数据流。在了解以上这些概念以后,用本身的代码来实现一个简版的Redux,而且用本身实现的Redux结合React框架,作一个简单的TodoList应用示例。但愿本文对于初识Redux的同窗有一个清晰,全面的认识。node
Redux就是用来管理状态数据,因此第一个概念就是状态数据,state就是存放数据的地方,根据应用须要,通常定义成一个对象,好比:react
{ todos: [], showType: 'ALL', lastUpdate: '2019-10-30 11:56:11' }
web应用,全部的数据状态变动,都是由一个行为触发的,好比用户点击,网络加载完成,或者定时事件。在简单应用里面,咱们通常都是在行为触发的时候,直接修改对应的数据状态,可是在大型复杂的应用里面,修改同一数据的地方可能不少,每一个地方直接修改,会形成数据状态不可维护。web
Redux引入了action的概念,每一个要改变数据状态的行为,都定义成一个action对象,用一个type来标志是什么行为,行为附带的数据,也都直接放在action对象,好比一个用户输入的行为:npm
{ type: 'INPUT_TEXT', text: '今天下午6点活动碰头会议' }
而后经过dispatch触发这个action,dispatch(action)redux
状态,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; } }
数据的更新已经在reducer中完成了,在一些响应式的web应用中,咱们每每须要监听数据状态的变化,这个时候就能够用subscribe了app
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() } )
整个应用的数据都在state,而且只有这一个state,这么作的目的是方便管理,整个应用的数据就这一份,调试方便,开发也方便,能够在开发的时候用本地的数据。并且开发同构应用也很方便,好比服务端渲染,把服务端的数据所有放在state,做为web端初始化时候的数据
state的数据对外只读,不能直接修改state,惟一能够修改的方式是触发action,而后经过reducer来处理。
由于全部的修改都被集中化处理,且严格按照一个接一个的顺序执行,所以不用担忧竞态条件(race condition)的出现。 Action 就是普通对象而已,所以它们能够被日志打印、序列化、储存、后期调试或测试时回放出来。
先说明下什么是纯函数,纯函数指的是函数内部不修改传入的参数,无反作用,在传参必定的状况下,返回的结果也是必定的。Redux中的Reducer须要设计成存函数,不能直接操做传入的state,须要把改变的数据以一个新的state方式返回。
其实上面讲Redux基本概念的时候已经大概的说了下数据流向方式了,就是: view->action->reducer->state->view,用文字来表述就是,首先因为页面上的某些事件会触发action,经过dispatch(action)来实现,而后经过reducer处理,reducer(state, action)返回一个新的state,完成state的更新,固然对于响应式的应用,会触发listener(),在listener里面获取最新的state状态,完成对应视图(view)的更新。这就是整个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开发一个简单的Todo工具,页面主要功能点
一、能够添加Todo事项
二、点击事项会切换事项的完成状态
三、能够切换展现所有/已完成/待完成事项
这个实例是基于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和入口文件了,准备分这么几个组件
一、待办事项Todo
二、输入框 AddTodo
三、待办事项列表TodoList
四、底部展现类型切换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
参考文章: