Prev: Redux学习笔记-Vol.1-介绍
Action
Action是把数据从应用传到store的有效载荷。它是store数据的唯一来源,一般通过store.dispatch()
将action传到store。
举个栗子:
const ADD_TODO = 'ADD_TODO'; //一个action可以表达为: { type: ADD_TODO,text: 'Build my first Redux app' }
说白了,action就是一个普通的javascript对象,但是有一点要注意:约定这个表示action的对象必须有一个type字段,来表示将要执行的动作。
尽量减少在action中传递数据
Action Creator
function addTodo(text){ return { type: 'ADD_TODO',text } }
在Redux中,只需把action creator的结果返回给dispatch()
即可发起一次dispatch过程。
dispatch(addTodo(text));
或者,创建一个被绑定的action creator来自动dispatch:
const boundAddTodo = (text) => dispatch(addTodo(text)); boundAddTodo();
目前为止,我们写好了一个action.js
//action type export const ADD_TODO = 'ADD_TODO'; export const TOGGLE_TODO = 'TOGGLE_TODO'; export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'; //其它常量 export const VisibilityFilters = { SHOW_ALL: 'SHOW_ALL',SHOW_COMPLETED: 'SHOW_COMPLETED',SHOW_ACTIVE: 'SHOW_ACTIVE' }; //action creator export function addTodo(text){ return { type: ADD_TODO,text } } export function toggleTodo(index){ return { type: TOGGLE_TODO,index } } export function setVisibilityFilter(filter){ return { type: SET_VISIBILITY_FILTER,filter } }
Reducer
有了action以后,现在需要reducer来指明如何更新state。
State结构
要明确的一点是,在Redux应用中,所有的state都被保存在一个单一的对象中。
举个栗子,一个todo应用,需要保存两种不同的数据
当前选中的任务过滤条件
完整的任务列表
{ visiibilityFilter: 'SHOW_ALL',todos: [ { text: 'Consider using Redux',complete: true },{ text: 'Keep all state in a single tree',complete: false } ] }
处理action
Reducer是一个纯函数,接受旧的state和action,返回新的state,形如:
(prevIoUsState,action) => newState
保持reducer纯净非常重要,永远不要在reducer中做这些操作:
一个纯净的reducer是什么样的呢?
只要传入的参数相同,返回计算得到的下一个state就一定相同。
好,开始写reducer。
import { VisibilityFilters } from './actions'; const initialState = { visibilityFilter: VisibilityFilter.SHOW_ALL,todo: [] }; function todoApp(state = initialState,action){ switch (action.type){ case SET_VISIBILITY_FILTER: return Object.assign({},state,{ visibilityFilter: action.filter }); default: return state; } }
注意:
不要修改state。上面的代码中只是使用
object.assign()
创建了一个副本。在
default
的情况下,返回旧的state
。在未知的情况下,一定要返回旧的state!
处理多个action
先增加两个ADD_TODO
和TOGGLE_TODO
case ADD_TODO: return Object.assign({},{ todos: [ ...state.todos,//ES6大法好 { text: action.text,complete: false } ] }); case TOGGLE_TODO: return Object.assign({},{ todos: state.todos.map(function(todo,index){ if (index === action.index){ return Object.assign({},todo,{ completed: !todo.completed; }); } return todo; }); });
拆分reducer
function todos(state = [],action){ switch(action.type){ case ADD_TODO: return [ ...state,{ text: action.text,completed: false } ]; case TOGGLE_TODO: return state.map(function(todo,index){ if (index === action.index){ return Object.assign({},{ completed: !todo.completed }); } return todo; }); } } function todoApp(state = initialState,action){ switch(action.type){ case SET_VISIBILITY_FILTER: return Object.assign({},{ visibilityFilter: action.filter }); case ADD_TODO: case TOGGLE_TODO: return Object.assign({},{ todos: todos(state.todos,action) }); default: return state; } }
todos依旧接受state,但是这里的state变成了一个数组,todoApp只把需要更新的那一部分state传给todos。这就是reducer合成,是开发Redux应用最基础的模式。
用同样的方法把visibilityFilter拆分出来:
function visibilityFilter(state = SHOW_ALL,action){ switch(action.type){ case SET_VISIBILITY_FILTER: return action.filter; default: return state; } }
然和修改总的reducer
function todoApp(state = {},action){ return { visibilityFilter: visibilityFilter(state.visibilityFilter,action),todos: todos(state.todos,action) }; }
合并的事,就交给Redux帮你来做:
import { combineReducers } from 'redux'; const todoApp = combineReducers({ visibilityFilter,todos }); export default todoApp;
ES6大法好
combineReducers
接受的是一个对象,返回一个函数。由此,我们可以把所有的顶级的reducer放到一个独立文件中,通> > 过export
暴露出每个reducer函数,然后用import * as reducer
引入一个以他们名字作为key的Object:import { combineReducers } from 'redux'; import * as reducer from './reducers'; const todoApp = combineReducers(reducer); //ES6大法好!
截至目前,我们得到了一个完整的reducers.js ↓
import { combineReducers } from 'redux'; import { ADD_TODO,TOGGLE_TODO,SET_VISIBILITY_FILTER,VisibilityFilters } from './actions'; function visibilityFilters(state = SHOW_ALL,action){ switch(action.type){ case SET_VISIBILITY_FILTER: return action.filter; default: return state; } } function todos(state = [],{ completed: !todo.completed }); } return todo; }); default: return state; } } const todoApp = combineReducer({ visibilityFilters,todos }); export default todoApp;
Store
在学Store之前,我们先来回顾一下action和reducer:
Action:用来表示“发生了什么”
Reducer:根据“发生了什么”来决定如何更新state。
现在需要知道的是,Store的作用就是上述两者联系起来。
关于Store的职责:
维持应用的state
dispatch(action)
用来分发action,进而更新state
FYI:Redux只有一个单一的Store!当逻辑复杂需要拆分的时候,请对Reducer下手,科科。
Now,我们来根据已经写好了的reducer来创建store,so easy~
import { createStore } from 'redux'; import todoApp from './reducers'; let store = createStore(todoApp);
createStore()方法可以的第二个参数是可选的,用于设置state的初始状态。
let store = createStore(todoApp,initialState);
发起actions
现在我们已经有了一个store,来看一下怎么用。
import { addTodo,toggleTodo } from './actions'; //获得state store.getState(); //注册(订阅),state没触发一次更新,打印之 let unsubscribe = store.subscribe(function(){ console.log(store.getState()); }); //发起action store.dispatch(addTodo('finish your resume.')); store.dispatch(toggleTodo(0)); //停止监听state更新 unsubscribe();
看看这一part做了什么 ↓
//创建了一个store(约定store只有一个!) import { createStore } from 'redux'; import todoApp from './reducers'; let store = createStore(todoApp);
严格的单向数据流(Redux核心)
发起:
store.dispatch(action)
;传递:store将发起的action和当前的state传递给根reducer,根reducer再将这两个参数传递给子reducer们;
更新:通过了子reducer们的各司其职以后,根reducer把他们的输出合并为一个单一的state树;
保存:store将这个reducer返回的state保存起来,这一次的数据流结束。