React-Redux(1)前篇Redux

学习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调用的时候会为他传递两个参数StateAction。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

三、Redux的API

redux
如图所示了。Redux的全局方法有5个。

  • createStore(reducer, [initialState],enhancer)
    创建一个 Redux store 来以存放应用中所有的 state。应用中应有且仅有一个 store。
  • combineReducers(...reducers)
    combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore。合并后的 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 接受 StoredispatchgetState 函数作为命名参数,并返回一个函数。该函数会被传入 被称为 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 传给它。

    参数

    1. actionCreators (Function or Object): 一个 action creator,或者键值是 action creators 的对象。
    2. 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)
    )
    

    参数

    1. (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架构的单页应用开发总结