JavaScript 的状态容器 Redux
2016-06-03 本文已影响362人
Nodelover
Redux
JavasSript 的状态容器
跟 React 没有关系,Redux 支持 React、Angular、Ember、JQuery 甚至 JavaScript。
Action、Reducer、Store

三大原则
- 单一数据源
- State 是可读的
- 使用纯函数来执行
Action
概念:
- 记录了用户行为的数据的载体
- Action 是 Store 数据的唯一来源
定义:
- Action 是一个 JavaScript 对象
- Action 内有一个 Type 字段
- Action 通常被定义为字符串常量
- 尽量减少在 Action 中传递的数据
设计 Todo 所需的 Action
var actionAddTodo = {
type: 'ADD_TODO',
text: '吃饭'
};
var actionCompleteTodo = {
type:'COMPLETE_TODO',
index:2
};
var actionSelectFilter = {
type:'SETFILTER',
filter:'SHOW_ALL'
};
Action 函数
// 创建对象的工厂模式
function createAction(text){
var o = new Object();
o.type = ADD_TODO;
o.text = text;
return o;
}
function addTodo(text){
return {
type: 'ADD_TODO',
text
}
}
State
概要
- 存放程序数据的一颗 🌲 ,或者说是一个数据库。
- State 是只读的,可能是一个 JavaScript 对象、数组、Immutable.js 的数据结构
- 唯一能更新 State 的方法就是触发 Action ,使用 Store 的 Dispatch 更新 State 。
为什么强调 State 只读
- 单一数据源,State 是程序的唯一数据源
- 确保视图和网络请求只能表现出我想要修改 State , 然后通过触发 Action 修改
- 只有唯一更新 State 方法,我们可以更容易的实现撤销 / 重做这类应用
设计 State
Todo 的任务列表
var initState = {
filter: 'SHOW_ALL',
todos: []
}
设计 State 注意事项
- 应该尽量是 State 可以轻松的转化为 JSON
- 尽可能把 State 规范化,不存在嵌套
- 把所有数据都放到一个对象里,每个数据以 ID 作为主键
- 把 State 想象成一个数据库
//方式一
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
//方式二
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}
Object.assign(target,...source) 函数
把所有的源对象属性复制到目标对象并放回。
// 用法一
var o1 = {a:1};
var o2 = {b:2};
var o3 = {c:3};
var obj1 = Object.assign(o1,o2,o3);
console.log(o1); // {a:1,b:2,c:3}
console.log(obj1); // {a:1,b:2,c:3}
// 用法二
var o4 = {a:1,b:2};
var o5 = {b:3,c:4};
var obj2 = Object.assign({},o4,o5);
console.log(obj2); // {a:1,b:2,c:3,d:4}
怎么使用 Object.assign()
必须保证 Reducer 是一个纯函数,我们不能改变传入的 State,所以我们需要使用 Object.assign({},state)
复制一个 State
var state = {filter:'SHOW_ALL',todos:['x1','x2']};
var obj3 = Object.assign({},state,{todos:[state.todos[1],'x3']})
console.log(obj3)
Reducer
var createStore = Redux.createStore;
var combineReducers = Redux.combineReducers;
var applyMiddleware = Redux.applyMiddleware;
const ADD_TODO = 'ADD_TODO';
const COMPLETE_TODO = 'COMPLETE_TODO';
const SETFILTER = 'SETFILTER';
const FILTER = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETE: 'SHOW_COMPLETE',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}
function addTodo(text){
return {
type: ADD_TODO,
text
}
}
function completeTodo(index){
return {
type: COMPLETE_TODO,
index
}
}
function selectFilter(filter){
return {
type: SETFILTER,
filter
}
}
var initState = {
filter:'SHOW_ALL',
todos: []
}
function todoApp(state = initState,action){
switch(action.type){
case SETFILTER: return Object.assign({},state,{
filter: action.filter
});
case ADD_TODO: return Object.assign({},state,{
todos:[...state.todos,{text: action.text,complete: false}]
});
case COMPELETE_TODO: return Object.assign({},state,{
todos: return [
...state.slice(0, parseInt(action.index),
Object.assign({},state[action.index],{
completed: true
}),
...state.slice(parseInt(action.index) + 1)
]
});
default:
return state;
}
}
拆分 Reducer
将不存在以来关系的字段拆分给不同的子 Reducer 管理。
例如 Filter 和 Todos 俩个字段不存在相互依赖。
function setFilter(state = FILTER.SHOW_ALL,action){
switch(action.type){
case SETFILTER:
return action.filter;
default:
return state;
}
}
function todos(state = [], action){
switch(action.type){
case ADD_TODO:
return [...state, {
text: action.type,
completed: false
}];
case COMPLETE_TODO:
return [
...state.slice(0,parse(action.index)),
Object.assign({},state[action.index],{
completed: true
}),
...state.slice(parseInt(action.index) + 1)
];
default:
return state;
}
}
combineReducers 的使用
将每个 Reducer 拼接起来返回一个完整的 state
函数部分源码
// reducers -> Object
// example -> reducers = { filter: setFilter, todos: todos}
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers) // array -> ['filter','todos']
var finalReducers = {}
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i] // When i = 0 The key = filter
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
var finalReducerKeys = Object.keys(finalReducers)
// ......
return function combination() {
var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]
var action = arguments[1]
var hasChanged = false
var nextState = {}
for (var i = 0; i < finalReducerKeys.length; i++ ) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action)
// ......
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
return hasChanged ? nextState : state
}
}
使用例子
var todoApp = combineReducers({
filter: setFilter,
todos: todos
})
Store
维持应用所有 State 的一个对象,也可是说一个方法的集合
var store = createStore(todoApp)
Store 的方法
- getState
- dispatch 唯一能改变 state 的函数
- subscribe 增加监听,当 dispatch action 的时候就会触发
- replaceReducer 替换当前用来计算的 reducer
var createStore = Redux.createStore;
var combineReducers = Redux.combineReducers;
var applyMiddleware = Redux.applyMiddleware;
const ADD_TODO = 'ADD_TODO';
const COMPLETE_TODO = 'COMPLETE_TODO';
const SETFILTER = 'SETFILTER';
const FILTER = {
SHOW_ALL:'SHOW_ALL',
SHOW_COMPLETE:'SHOW_COMPLETE',
SHOW_ACTIVE:'SHOW_ACTIVE'
}
function addTodo(text){
return {
type:ADD_TODO,
text
}
}
function completeTodo(index){
return {
type:COMPLETE_TODO,
index
}
}
function selectFilter(filter){
return {
type:SETFILTER,
filter
}
}
var initState = {
filter:'SHOW_ALL',
todos:[]
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [...state, {
text: action.text,
completed: false
}];
case COMPLETE_TODO:
return [
...state.slice(0, parseInt(action.index)),
Object.assign({}, state[action.index], {
completed: true
}),
...state.slice(parseInt(action.index)+ 1)
];
default:
return state;
}
}
function setFilter(state = FILTER.SHOW_ALL,action){
switch(action.type){
case SETFILTER:
return action.filter;
default:
return state;
}
}
var todoApp = combineReducers({
filter:setFilter,
todos:todos
});
var store = createStore(todoApp);
var unsubscribe = store.subscribe(()=>{
console.log(store.getState());
});
console.log('添加吃饭');
store.dispatch(addTodo('吃饭'));
console.log('添加睡觉');
store.dispatch(addTodo('睡觉'));
console.log('完成吃饭');
store.dispatch(completeTodo(0));
console.log('添加打豆豆');
store.dispatch(addTodo('打豆豆'));
console.log('完成睡觉');
store.dispatch(completeTodo(0));
console.log('setFilter');
store.dispatch(selectFilter(FILTER.SHOW_COMPLETE));
unsubscribe();
小结
- Action 用户发起一个动作请求的请求内容
- State 保存应用现有的状态
- Reducer 根据 Action 请求内容的 Type 字段去匹配要进行的动作与修改的状态
- Store 存储库,把 Reducer 传入 createStore 的构造器中得到,只有通过它的 dispatch 方法传入一个 Action 请求内容,之后自动去 Reducer 中匹配 Type 字段,之后去修改相应的状态