共计 10301 个字符,预计需要花费 26 分钟才能阅读完成。
学习React-Redux,必须学习Redux,个人觉得Redux比Flux复杂多了。Flux一切都由dispatcher去处理,缺点很多事情都要自己去做,监听等。redux呢,虽然也是单项流呢,但是所用的方法函数就多了很多,帮助你做了很多事情,缺点就是要花时间去弄明白这些函数是做什么,数据是怎样流动的。
一、Redux三大部分
Redux包含Store,action,reducers三大部分;这里解释一下,这个store跟react组件上的state是两个东西。他们没有半毛钱关系(也不全没关系,还是有一点点)。而对应着三大部分,又有一下相应的函数。
1、action
简单的说action是一个返回纯对象的函数。 在Action 内使用一个字符串类型的 type
字段来表示将要执行的动作。
说白了,就是告诉应用有什么可以做的。比如看书,打游戏。就是任务类型,而真正去看书和打游戏的人呢,并不是Action。
2、Reducer
Reducer 是一个普通的回调函数。当它被Redux调用的时候会为他传递两个参数State
和 Action
。Reducer会根据 Action
的type来对旧的 State
进行操作。返回新的State。根据上面说的,它就是去看书,打游戏的那个人了,是执行者。记住啊,Reducer,里面一定会调用具体执行任务的那个函数(这个函数并不是Reducer),Reducer是执行者,调用。
一定要谨记, reducer 一定要保持纯净。只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
3、Store
store上有4个方法,分别是
getState()获取当前store
dispatch(action)修改store
subscribe(listener)添加监听
replaceReducer(nextReducer)替换 store 当前用来计算 state 的 reducer。
Redux 应用只有一个单一的 store。它的作用就是保存当前应用的状态
二、Redux数据流向
首先,你先告诉应用你有什么事情可以做,也就是创建Action;
接着,你需要编写Reducer,有事具体怎么做。当编写多个Reducer时,可以使用combineReducers函数包裹成一个RootReducer。combineReducers还有一个作用就是帮忙检测state是否标准
然后,按需求决定是否用compose来增强store。createStore通过传入RootReducer参数创建store。接着把Action和oldstore传给dispatch生成新的newstore。
具体流程如下图:
三、Redux的API
如图所示了。Redux的全局方法有5个。
createStore(reducer, [initialState],enhancer)
创建一个 Redux store 来以存放应用中所有的 state。应用中应有且仅有一个 store。combineReducers(...reducers)
combineReducers
辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用c
reateStore
。合并后的 reducer 可以调用各个子 reducer,并把它们的结果合并成一个 state 对象。state 对象的结构由传入的多个 reducer 的 key 决定。applyMiddleware(...middlewares)
使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的dispatch
方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。applyMiddleware来自redux可以包装 store 的 dispatch()参数
...middlewares
(arguments): 遵循 Redux middleware API 的函数。每个 middleware 接受Store
的dispatch
和getState
函数作为命名参数,并返回一个函数。该函数会被传入 被称为next
的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用next(action)
,或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的dispatch
方法作为next
参数,并借此结束调用链。所以,middleware 的函数签名是({ getState, dispatch }) => next => action
。返回值
(Function) 一个应用了 middleware 后的 store enhancer。这个 store enhancer 就是一个函数,并且需要应用到
createStore
。它会返回一个应用了 middleware 的新的createStore
。bindActionCreators(actionCreators,dispatch)
把
action creators 转成拥有同名 keys 的对象,但使用
dispatch
把每个 action creator 包围起来,这样可以直接调用它们。惟一使用bindActionCreators
的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 Redux store 或dispatch
传给它。参数
actionCreators
(Function or Object): 一个 action creator,或者键值是 action creators 的对象。dispatch
(Function): 一个dispatch
函数,由Store
实例提供。
返回值
(Function or Object): 一个与原对象类似的对象,只不过这个对象中的的每个函数值都可以直接 dispatch action。如果传入的是一个函数,返回的也是一个函数。
compose(...functions)
当需要把多个
store 增强器 依次执行的时候,需要用到它。compose在应用常见两种用法,例如
//用法1 var buildStore = compose( applyMiddleware(thunk) )(createStore) //用法2 var initStore = compose( applyMiddleware(thunk) )
参数
- (arguments): 需要合成的多个函数。每个函数都接收一个函数作为参数,然后返回一个函数。
返回值
(Function): 从右到左把接收到的函数合成后的最终函数。
四、Redux数据异步处理
1.异步Action
当调用异步 API 时,有两个非常关键的时刻:发起请求的时刻,和接收到响应的时刻 (也可能是超时)。
这两个时刻都可能会更改应用的 state;为此,你需要 dispatch 普通的同步 action。一般情况下,每个 API 请求都需要 dispatch 至少三种 action:
- 一种通知 reducer 请求开始的 action。对于这种 action,reducer 可能会切换一下 state 中的
isFetching
标记。以此来告诉 UI 来显示进度条。 - 一种通知 reducer 请求成功结束的 action。对于这种 action,reducer 可能会把接收到的新数据合并到 state 中,并重置
isFetching
。UI 则会隐藏进度条,并显示接收到的数据。 - 一种通知 reducer 请求失败的 action。对于这种 action,reducer 可能会重置
isFetching
。或者,有些 reducer 会保存这些失败信息,并在 UI 里显示出来。
为了区分这三种 action,可能在 action 里添加一个专门的 status
字段作为标记位:
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
又或者为它们定义不同的 type:
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }
设计 state 结构
根据Action设计响应的 reducer,而 reducer是设计就决定了state的结构,通常是拆分 reducer然后合并成一个RootReducer。异步 Action Creator一般通过Thunk middleware来处理。thunk作用使action创建函数可以返回一个function代替一个action对象
2.异步数据流
默认情况下,createStore()
所创建的 Redux store 没有使用 middleware,所以只支持 同步数据流。
你可以使用 applyMiddleware()
来增强 createStore()
。虽然这不是必须的,但是它可以帮助你用简便的方式来描述异步的 action。
你可以使用任意多异步的 middleware 去做你想做的事情,但是需要使用普通对象作为最后一个被 dispatch 的 action ,来将处理流程带回同步方式
3.Middleware
middleware 是指可以被嵌入在框架接收请求到产生响应过程之中的代码;它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。每一个 middleware 都可以操作(或者直接调用)前一个 middleware 包装过的 store.dispatch;
Middleware 接收了一个 next()
的 dispatch 函数,并返回一个 dispatch 函数,返回的函数会被作为下一个 middleware 的 next()
,以此类推。由于 store 中类似 getState()
的方法依旧非常有用,我们将 store
作为顶层的参数,使得它可以在所有 middleware 中被使用。
我们可以在 Redux 内部提供一个可以将实际的 monkeypatching 应用到 store.dispatch
中的辅助方法:
function applyMiddlewareByMonkeypatching(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
// 在每一个 middleware 中变换 dispatch 方法。
middlewares.forEach(middleware =>
store.dispatch = middleware(store)
)
}
然后像这样应用多个 middleware:
applyMiddlewareByMonkeypatching(store, [ logger, crashReporter ])
尽管我们做了很多,实现方式依旧是 monkeypatching。
我们可以写一个 applyMiddleware()
方法替换掉原来的 applyMiddlewareByMonkeypatching()
。在新的applyMiddleware()
中,我们取得最终完整的被包装过的 dispatch()
函数,并返回一个 store 的副本:
// 警告:这只是一种“单纯”的实现方式!
// 这 *并不是* Redux 的 API.
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
let dispatch = store.dispatch
middlewares.forEach(middleware =>
dispatch = middleware(store)(dispatch)
)
return Object.assign({}, store, { dispatch })
}
这与 Redux 中 applyMiddleware()
的实现已经很接近了,但是有三个重要的不同之处:
- 它只暴露一个 store API 的子集给 middleware:
dispatch(action)
和getState()
。 - 它用了一个非常巧妙的方式来保证你的 middleware 调用的是
store.dispatch(action)
而不是next(action)
,从而使这个 action 会在包括当前 middleware 在内的整个 middleware 链中被正确的传递。这对异步的 middleware 非常有用 - 为了保证你只能应用 middleware 一次,它作用在
createStore()
上而不是store
本身。因此它的签名不是(store, middlewares) => store
, 而是(...middlewares) => (createStore) =>createStore
。
五、Redux 官方实例async
首先先从Action看起。
import fetch from 'isomorphic-fetch' //类似于XHR
//定义action的type
export const REQUEST_POSTS = 'REQUEST_POSTS'
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
export const SELECT_REDDIT = 'SELECT_REDDIT'
export const INVALIDATE_REDDIT = 'INVALIDATE_REDDIT'
//选择新闻类型action -
export function selectReddit(reddit) {
return {
type: SELECT_REDDIT,
reddit
}
}
//废弃新闻类型action
export function invalidateReddit(reddit) {
return {
type: INVALIDATE_REDDIT,
reddit
}
}
//开始获取新闻action
function requestPosts(reddit) {
return {
type: REQUEST_POSTS,
reddit
}
}
//获取新闻成功的action
function receivePosts(reddit, json) {
return {
type: RECEIVE_POSTS,
reddit,
posts: json.data.children.map(child => child.data),
receivedAt: Date.now()
}
}
// 来看一下我们写的第一个 thunk action creator!
// 虽然内部操作不同,你可以像其它 action creator 一样使用它:
// store.dispatch(fetchPosts('reactjs'))
//获取文章,先触发requestPosts开始获取action,
//完成后触发receivePosts获取成功的action
function fetchPosts(reddit) {
// Thunk middleware 知道如何处理函数。
// 这里把 dispatch 方法通过参数的形式传给函数,
// 以此来让它自己也能 dispatch action。
return dispatch => {
// 首次 dispatch:更新应用的 state 来通知
// API 请求发起了。
dispatch(requestPosts(reddit))
// thunk middleware 调用的函数可以有返回值,
// 它会被当作 dispatch 方法的返回值传递。
// 这个案例中,我们返回一个等待处理的 promise。
// 这并不是 redux middleware 所必须的,但这对于我们而言很方便。
return fetch(`https://www.reddit.com/r/${reddit}.json`)
.then(response => response.json())
.then(json =>
// 可以多次 dispatch!
// 这里,使用 API 请求结果来更新应用的 state。
dispatch(receivePosts(reddit, json))
)
}
}
// 在实际应用中,还需要
// 捕获网络请求的异常。
//是否需要获取文章
function shouldFetchPosts(state, reddit) {
const posts = state.postsByReddit[reddit]
if (!posts) {
return true
}
if (posts.isFetching) {
return false
}
return posts.didInvalidate
}
//注意这个函数也接收了 getState() 方法
//它让你选择接下来 dispatch 什么。
//当缓存的值是可用时,
//减少网络请求很有用。
//如果需要则开始获取文章
export function fetchPostsIfNeeded(reddit) {
return (dispatch, getState) => {
if (shouldFetchPosts(getState(), reddit)) {
// 在 thunk 里 dispatch 另一个 thunk!
return dispatch(fetchPosts(reddit))
}else {
// 告诉调用代码不需要再等待。
return Promise.resolve()
}
}
}
接着看Reducer
import { combineReducers } from 'redux'
import {
SELECT_REDDIT, INVALIDATE_REDDIT,
REQUEST_POSTS, RECEIVE_POSTS
} from '../actions'
//选择新闻后,将state.selectedReddit设为所选选项
function selectedReddit(state = 'reactjs', action) {
switch (action.type) {
case SELECT_REDDIT:
return action.reddit
default:
return state
}
}
function posts(state = {
isFetching: false,//是否正在获取最新
didInvalidate: false, //是否废弃
items: []//内容
}, action) {
switch (action.type) {
case INVALIDATE_REDDIT:
return Object.assign({}, state, {
didInvalidate: true
})
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
})
default:
return state
}
}
//废弃、接收到、开始接受新闻后,将state.postsByReddit设为相关参数
function postsByReddit(state = { }, action) {
switch (action.type) {
case INVALIDATE_REDDIT:
case RECEIVE_POSTS:
case REQUEST_POSTS:
return Object.assign({}, state, {
[action.reddit]: posts(state[action.reddit], action)
})
// return {
// state,
// [action.reddit]: posts(state[action.reddit], action)
// }
default:
return state
}
}
//将两个reducer合并成一个reducer,也就将全局的state加上postsByReddit,selectedReddit两个属性,
//每个属性都有自己的state
const rootReducer = combineReducers({
postsByReddit,
selectedReddit
})
/*
postsByReddit节点下面就是postsByReddit返回的state,
也就是[action.reddit]: posts(state[action.reddit], action)。
posts()就是{
isFetching: false,
didInvalidate: false,
items: []
}
*/
export default rootReducer
接着Stroe的configureStore
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
import rootReducer from '../reducers'
//注册Store
export default function configureStore(preloadedState) {
const store = createStore(
rootReducer,
preloadedState,
applyMiddleware(
thunkMiddleware,// 允许我们 dispatch() 函数
createLogger()// 一个很便捷的 middleware,用来打印 action 日志
)
)
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers').default
store.replaceReducer(nextRootReducer)
})
}
return store
}
最后看Component的app.js
//初始化渲染后触发
componentDidMount() {
console.log('执行componentDidMount');
const { dispatch, selectedReddit } = this.props
dispatch(fetchPostsIfNeeded(selectedReddit))
}
//每次接受新的props触发
componentWillReceiveProps(nextProps,nextState) {
console.log('执行componentWillReceiveProps',nextProps);
if (nextProps.selectedReddit !== this.props.selectedReddit) {
const { dispatch, selectedReddit } = nextProps
dispatch(fetchPostsIfNeeded(selectedReddit))
}
/*
首先,执行了componentDidMount,也就是渲染了组件。然后执行request_post的action,
这个action改变了state,state和props就是部分绑定关系,所以触发了componentWillReceiveProps。
然后那个[HMR]是热替换的意思
接下来又执行了componentWillReceiveProps,为什么呢?因为获取新闻数据成功了,state改变了,
被绑定的props也变了,所以执行了componentWillReceiveProps。
我们可以看到posts里面已经有值了,这时触发了receive_posts的action。
*/
}
总结一下,Redux的通过创建Action决定事件类型,也就是应用在某个阶段的执行的顺序等等;Reducer根据Action创建的类型,结合该类型执行的动作,拿到(oldStore,Action)=>newStore;多个Reducer一般合并成一个RootReducer;到这里一般也就对Stroe树的结构大致有了一个样子了。最后通过cresteStroe接受到的参数,RootReducer和[initialStroe][enhaner]来生成真正的Stroe。但在实际应用中往往不是同步操作,需要结合thunk等异步函数,来增强stroe,这样applyMiddleware(…middleware)就起作用了。
参考链接:
react+redux教程
redux官方
Redux 中文文档
深入浅出 – Redux
基于Redux架构的单页应用开发总结