Redux 核心概念
http://gaearon.github.io/redux/index.html ,文档在 http://rackt.github.io/redux/index.html 。本文不是官方文档的翻译。你可以在阅读官方文档之前和之后阅读本文,以加深其中的重点概念。 根据该项目源码的习惯,示例都是基于 ES2015 的语法来写的。 Redux 是应用状态管理服务。虽然本身受到了 Flux 很深的影响,但是其核心概念却非常简单,就是 Map/Reduce 中的 Reduce。 我们看一下 Javascript 中 const initState = '';
const actions = ['a','b','c'];
const newState = actions.reduce(
( (prevState,action) => prevState + action ),initState
);
从 Redux 的角度来看,应用程序的状态类似于上面函数中的 这个计算函数被称之为 Immutable StateRedux 认为,一个应用程序中,所有应用模块之间需要共享访问的数据,都应该放在 Redux 没有规定用什么方式来保存 newState.todos === prevState.todos 从而避免 Deep Equal 的遍历过程。 为了确保这一点,在你的 `let myStuff = [ {name: 'henrik'} ] myStuff = [...mystuff,{name: 'js lovin fool']`
如果更新的是 Object ,则: let counters = {
faves: 0,forward: 20,}
// this creates a brand new copy overwriting just that key
counters = {...counters,faves: counters.faves + 1}
而不是: counters.faves = counters.faves + 1}
要避免对 Object 的 in-place editing。数组也是一样: let todos = [
{ id: 1,text: 'have lunch'}
]
todos = [...todos,{ id: 2,text: 'buy a cup of coffee'} ]
而不是: let todos = [
{ id: 1,text: 'have lunch'}
]
todos.push({ id: 2,text: 'buy a cup of coffee'});
遵循这样的方式,无需 Immutable.js 你也可以让自己的应用程序状态是 Immutable 的。 在 Redux 中, State 结构设计Redux (Flux) 都建议在保存 假设远程服务返回的数据是这样的: [{
id: 1,title: 'Some Article',author: {
id: 1,name: 'Dan'
}
},{
id: 2,title: 'Other Article',name: 'Dan'
}
}]
那么,转换成以下形式会更有效率: { result: [1,2],entities: { articles: { 1: { id: 1,author: 1 },2: { id: 2,author: 1 } },users: { 1: { id: 1,name: 'Dan' } } } }
范式化的存储让你的数据的一致性更好,上例中,如果更新了 其实传统关系数据库的设计原则就是如此,只不过随着对数据分布能力和水平扩展性的要求(放弃了一定程度的数据一致性),服务端数据的冗余越来越多。但是回到客户端,由于需要保存的数据总量不大(往往就是用户最近访问数据的缓存),也没有分布式的要求,因此范式化的数据存储就更有优势了。除了可以收获一致性,还可以减少存储空间(存储空间在客户端更加宝贵)。 除此之外,范式化的存储也利于后面讲到的 由于服务器端返回的 JSON 数据(现在常见的方式)往往是冗余而非范式的,因此,可能需要一些工具来帮助你转换,例如:https://github.com/gaearon/normalizr , 虽然很多时候自己控制会更有效一些。 Reducer下面我们以熟悉 function todoAppReducer(state = initialState,action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({},state,{
visibilityFilter: action.filter
});
case ADD_TODO:
return Object.assign({},{
todos: [...state.todos,{
text: action.text,completed: false
}]
});
default:
return state;
}
}
这个例子演示了 如果当应用程序中存在很多 import { combineReducers } from 'redux';
const todoAppReducer = combineReducers({
visibilityFilter: visibilityFilterReducer
todos: todosReducer
});
function visibilityFilterReducer(state = SHOW_ALL,action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter;
default:
return state;
}
}
{ field1: reducerForField1,field2: reducerForField2 }
使用 不过,有时我们就是需要在一个 function a(state,action) { }
function b(state,action,a) { } // depends on a's state
function something(state = {},action) {
let a = a(state.a,action);
let b = b(state.b,a); // note: b depends on a for computation
return { a,b };
}
在这个例子中,我们有两个 var reducers = reduceReducers(
combineReducers({
router: routerReducer,customers,stats,dates,filters,ui
}),// cross-cutting concerns because here `state` is the whole state tree
(state,action) => {
switch (action.type) {
case 'SOME_ACTION':
const customers = state.customers;
const filters = state.filters;
// ... do stuff
}
}
);
上面的例子里,在 一个 function createReducer(initialState,handlers) {
return function reducer(state = initialState,action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state,action);
} else {
return state;
}
}
}
export const todosReducer = createReducer([],{
[ActionTypes.ADD_TODO](state,action) {
let text = action.text.trim();
return [...state,text];
}
}
Store在 Redux 中, 因此 Store 对象有两个主要方法,一个次要方法:
一个次要方法为: 下面这个例子演示了 import { combineReducers,createStore } from 'redux';
import * as reducers from './reducers';
const todoAppReducer = combineReducers(reducers);
const store = createStore(todoAppReducer); // Line 5
store.dispatch({type: 'ADD_TODO',text: 'Build Redux app'});
我们也可以在 const store = createStore(reducers,window.STATE_FROM_SERVER);
这个例子中,初始状态来自于保存在浏览器 Action在 Redux 中,改变 {
type: 'ADD_TODO',text: 'Build Redux app'
}
Redux 要求
{
type: 'ADD_TODO',payload: {
text: 'Do something.'
},`meta: {}`
}
如果 {
type: 'ADD_TODO',payload: new Error(),error: true
}
Action Creator事实上,创建 function addTodo(text) {
return {
type: ADD_TODO,text
};
}
Action Creator 看起来很简单,但是如果结合上 Middleware 就可以变得非常灵活。 Middleware如果你用过 Express,那么就会熟悉它的 Middleware 系统。在 HTTP Request 到 Response 处理过程中,一系列的 Express Middlewares 起着不同的作用,有的 Middleware 负责记录 Log,有的负责转换内部异常为特定的 HTTP Status 返回值,有的负责将 Query String 转变到 Redux Middleware 的设计动机确实是来自于 Express 。其主要机制为,建立一个 import { createStore,combineReducers,applyMiddleware } from 'redux';
// applyMiddleware takes createStore() and returns// a function with a compatible API.
let createStoreWithMiddleware = applyMiddleware(
logger,crashReporter
)(createStore);
// Use it like you would use createStore()let todoApp = combineReducers(reducers);
let store = createStoreWithMiddleware(todoApp);
这个例子中,
`// Logs all actions and states after they are dispatched. const logger = store => next => action => { console.log('dispatching',action); let result = next(action); console.log('next state',store.getState()); return result; };`
ES6 的 Fat Arrow Function 语法( 工业化的 vanilla promiseMiddleware 还可以用来对传入的 /** * Lets you dispatch promises in addition to actions. * If the promise is resolved,its result will be dispatched as an action. * The promise is returned from `dispatch` so the caller may handle rejection. */
const vanillaPromise = store => next => action => {
if (typeof action.then !== 'function') {
return next(action);
}
// the action is a promise,we should resolve it first
return Promise.resolve(action).then(store.dispatch);
};
这个例子中,如果传入的 这种用法可能并非常用,但是从这个例子我们可以体会到,我们可以定义自己 从这个例子你可能也会发现,如果们也装载了 对 Promise 的完整支持请参见:https://github.com/acdlite/redux-promise 。 Scheduled Dispatch下面这个例子略微复杂一些,演示了如何延迟执行一个 /** * Schedules actions with { meta: { delay: N } } to be delayed by N milliseconds. * Makes `dispatch` return a function to cancel the interval in this case. */
const timeoutScheduler = store => next => action => {
if (!action.meta || !action.meta.delay) {
return next(action);
}
let intervalId = setTimeout(
() => next(action),action.meta.delay
);
return function cancel() {
clearInterval(intervalId);
};
};
这个例子中, 下面这个 Middleware 非常简单,但是却提供了非常灵活的用法。 Thunk如果不了解 Thunk 的概念,可以先阅读 http://www.ruanyifeng.com/blog/2015/05/thunk.html 。
const thunk = store => next => action =>
typeof action === 'function' ?
action(store.dispatch,store.getState) :
next(action);
下面的例子装载了 const createStoreWithMiddleware = applyMiddleware(
logger,
thunk
timeoutScheduler
)(createStore);
const store = createStoreWithMiddleware(combineReducers(reducers));
function addFave(tweetId) {
return (dispatch,getState) => {
if (getState.tweets[tweetId] && getState.tweets[tweetId].faved)
return;
dispatch({type: IS_LOADING});
// Yay,that could be sync or async dispatching
remote.addFave(tweetId).then(
(res) => { dispatch({type: ADD_FAVE_SUCCEED}) },(err) => { dispatch({type: ADD_FAVE_FAILED,err: err}) },};
}
store.dispatch(addFave());
这个例子演示了 “收藏” 一条微博的相关的 当 在 Thunk 函数中,首先会判断当前应用的 如果需要调用远程方法的话,那么首先发出 远程方法如果调用成功,就会 当 Thunk Middleware 处理了 Thunk 函数类型的 例如:我们的 Middlewares 配置为 applyMiddleware拼装 Middlewares 的工具函数是 function applyMiddleware(store,middlewares) {
middlewares = middlewares.slice();
middlewares.reverse();
let next = store.dispatch;
middlewares.forEach(middleware =>
next = middleware(store)(next)
);
return Object.assign({},store,{ dispatch: next });
}
结合 Middleware 的写法: const logger = store => next => action => {
console.log('dispatching',action);
let result = next(action);
console.log('next state',store.getState());
return result;
};
我们可以看到,给 Middleware 传入 每一个 Middleware 可以得到:
以 需要注意一点, Middleware 可以有很多玩法的,下面文档列出了 Middleware 的原理和七种Middlewares:http://rackt.github.io/redux/docs/advanced/Middleware.html 。
Higher-Order StoreMiddleware 是对 这个概念和 React 的 Higher-Order Component 概念是类似的。https://github.com/gaearon/redux/blob/cdaa3e81ffdf49e25ce39eeed37affc8f0c590f7/docs/higher-order-stores.md ,既提供一个函数,接受 createStore => createStore'
Redux 建议大家在 Middleware 不能满足扩展要求的前提下再使用 Higher-Order Store,与 Redux 配套的 redux-devtools 就是一个例子。 Binding To React (React-Native)上面的章节介绍了 Redux 的核心组组件和数据流程,可以通过下图回味一下: ┌──────────────┐
┌─────────────┐ ┌──?│ subReducer 1 │
┌───?│Middleware 1 │ │ └──────────────┘
│ └─────────────┘ │ │
│ │ │ ▼
┌─────────────┐ │ │ ┌───────────────┐ ┌──────────┐ │ ┌──────────────┐
│ action' │────┘ ▼ ┌──?│store.dispatch │───?│ reducer │───┘ │ subReducer m │
└─────────────┘ ┌─────────────┐ │ └───────────────┘ └──────────┘ └──────────────┘
│Middleware n │ │ │
└─────────────┘ │ │
│ │ ▼
│ │ ┌──────────────┐
└──────────┘ │ state │
plain action └──────────────┘
Redux 解决的是应用程序状态存储以及如何变更的问题,至于怎么用,则依赖于其他模块。关于如何在 React 或者 React-Native 中使用 Redux ,则需要参考 react-redux。 react-redux 是 React Components 如何使用 Redux 的 Binding。下面我们来分析一个具体的例子。 import { Component } from 'react';
export default class Counter extends Component {
render() {
return (
<button onClick={this.props.onIncrement}>
{this.props.value}
</button>
);
}
}
这是一个 React Component,显示了一个按钮。按下这个按钮,就会调用 在 react-redux 中,这样的 Component 被称为 “Dumb” Component,既其本身对 Redux 完全无知,它只知道从 如何为 “Dumb” Component 准备 import { Component } from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import { increment } from '../actionsCreators';
// Which part of the Redux global state does our component want to receive as props?
function mapStateToProps(state) {
return {
value: state.counter
};
}
// Which action creators does it want to receive by props?
function mapDispatchToProps(dispatch) {
return {
onIncrement: () => dispatch(increment())
};
}
export default connect( // Line 20
mapStateToProps,mapDispatchToProps
)(Counter);
第 20 行的 如果 connect 函数省掉第二个参数, Components 的嵌套你可以在你的组件树的任何一个层次调用 Provider Component上面的例子实际上是不可执行的,因为 React.render(
<Provider store={store}>
{() => <MyRootComponent />}
</Provider>,rootEl
);
selector在上面的例子中, reselect 这个项目提供了带 cache 功能的 总结
其他参考大而全的所有 Redux 参考资料。https://github.com/xgrommx/awesome-redux Slack 讨论组加入 https://reactiflux.slack.com Team,然后选择 redux channel。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |