1.使用场景
redux虽然好,也并不是什么情况下都要使用,如果在项目中遇到一下场景,你会自发的寻找一个工具来解决问题,这时就是redux隆重登场的时候了:
业务层面:
用户的使用方式复杂
不同身份的用户有不同的使用方式(比如普通用户和管理员)
多个用户之间可以协作
与服务器大量交互,或者使用了WebSocket
View要从多个来源获取数据
组件层面:
某个组件的状态,需要共享
某个状态需要在任何地方都可以拿到
一个组件需要改变全局状态
一个组件需要改变另一个组件的状态
如果通过上面的考虑,你还是不知道是否需要使用Rduex,那很有可能就是你不需要使用
2.设计思想
两句话概括:
1. Web 应用是一个状态机,视图与状态是一一对应的。
2. 所有的状态,保存在一个对象里面。
结合这个图对原理做一下解释,也许有些名字还是不懂,但是可以先有个印象,接下来我们会做进一步的介绍。
用户会在view上进行一系列操作,而每一个操作都对应一个action。Store对象通过调用dispatch方法,传入上次的状态(previousState)和操作(action),经过Reducers针对不同的action,改变previousState的值,然后返回一个newState,然后Stroe对象可以通过getState()方法,获取这个新状态。最后进行相应的视图更新。web应用所有的状态都保存在state中,每一个state都对应一个视图。
3. 核心概念
state:
整个Redux都是围绕状态进行的操作,所有的 state 都被保存在一个单一对象中。建议在写代码前先想一下这个对象的结构。如何才能以最简的形式把应用的 state 用对象描述出来。如果应用较复杂在创建store对象时,可以这些意见:http://cn.redux.js.org/docs/recipes/reducers/NormalizingStateShape.html。应用的初始状态可以在创建Store时传入,也可以根据reducer中state的默认值自动生成。
action:
每一个action都有个对应的操作,所以action中有这个操作的名字(类型)和需要的数据。action 是把数据从应用传到 store 的有效载荷,它是Store数据的唯一来源。一般来说你会通过 store. dispatch() 将 action 传到 store;它本质上是JavaScripte的普通对象:
{
type: “ADD_TODO”,
text: 'Build my first Redux app'
}
对象中type字段一般是必须的,用来描述操作的类型。其他的参数都可以根据操作的需要,进行自定义扩充。
这个例子action是像toDolist中添加一项,text字段是这个todo的内容。很明显我们不能每一个不同的内容都有个action,这个需要action creator:
let todo = function (text) {
return {
type: "ADD_TODO",
text
}
}
reducer:
action只是描述了一个操作,而没有描述如何针对这个action进行状态更改。reducer则描述了如何根据传入的action,对状态做相应的改变。
在编写reducer函数是要保证它的纯净性,不要进行以下操作:
不要修改传入的参数,尤其是在处理state时,要返回新的对象
执行有副作用的操作,如 API 请求和路由跳转
调用非纯函数,如 Date.now() 或 Math.random()
要保证只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算
Store:
Store 就是把state、action、 reducers联系到一起的对象。Store 有以下职责
维持应用的 state
提供 getState() 方法获取 state
提供 dispatch(action) 方法,然后通过reducer更新 state
通过 subscribe(listener) 注册监听器
通过subscribe(listener) 返回的函数注销监听器
Redux应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合而不是创建多个 store。
// createStore的简单实现
const createStore = (reducer) => { // 源码支持传入三个参数,reducer, preloadedState, enhancer
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
}
};
dispatch({}); //获得初始状态
return { getState, dispatch, subscribe };
};
数据流:
严格的单向数据流是 Redux 架构的设计核心:
用户行为引起调用store.dispatch(actioin)
调用reduser函数:两个参数,当前的state和action
返回计算过的state
触发所有订阅store.subscribe(listener)
4. 简单实例
下面是一个toDoList的简单实例
http://jsbin.com/noxalaw/edit?html,js,console,output
5.高级用法
异步action:
上面讲到的action触发的动作都是同步的,如果有异步的action该如何处理呢?同步时只需触发一个action,当异步时要要触发三个action,分别为:
发起:操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
成功: 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染
失败: 请求失败时发出action,触发state更新,view显示错误状态
这三个action对应的描述如下:
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
给出一个异步action的示例:
const fetchPosts = subreddit => dispatch => {
dispatch(requestPosts(subreddit)) //开始action
return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(subreddit, json))) //成功action
}
Actions creator现在返回了一个函数,而在同步时返回一个普通对像。在默认情况下dispatch的参数只能是一个对象,不能是一个函数。这个时候就需要引入中间件: redux-thunk,让dispatch参数可以传入函数。
所以,异步操作的一种解决方案就是,
一个返回函数的 Action Creator,
使用redux-thunk中间件改造store.dispatch,使其能支持函数作为参数
至于中间件是如何操作的,这里就不细述了,有兴趣的同学可以参考http://cn.redux.js.org/docs/advanced/Middleware.html
// redux-thunk的源码
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
6. 异步action的完整示例
http://jsbin.com/xexax/edit?html,js,console,output
参考:
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html