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
Action Creator就是生成action的方法。
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中做这些操作:
修改传入的参数
执行有副作用的操作,如API请求和路由跳转
调用非纯函数,如
Date.now()
或Math.random()
一个纯净的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({}, state, {
todos: [
...state.todos,//ES6大法好
{
text: action.text,
complete: false
}
]
});
case TOGGLE_TODO:
return Object.assign({}, state, {
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({}, todo, {
completed: !todo.completed
});
}
return todo;
});
}
}
function todoApp(state = initialState, action){
switch(action.type){
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
});
case ADD_TODO:
case TOGGLE_TODO:
return Object.assign({}, state, {
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 = [], 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({}, todo, {
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
getState()
方法用来获取statedispatch(action)
用来分发action,进而更新statesubscribe(listener)
注册reducer,并返回一个取消注册的函数
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保存起来,这一次的数据流结束。