资讯专栏INFORMATION COLUMN

react+redux+router异步数据获取教程

Arno / 3062人阅读

摘要:写法一,返回值是一个对象。我们已经知道,中间件只关注函数的传递,而且也不关心函数的返回值,所以只需要让认识这个函数就可以了。

react的FLUX数据流一直搞不清楚,他不像Angular的双向数据绑定,做一个model获取数据,然后通过controller来管理view上的数据显示就可以了。单项数据流引入了太多的概念,stateactionreducerdispatch。就算看的懂图,也不一定能coding出来。

不过我总算先搞定了Redux

keywords

store

reducer

action

dispatch

connect

router

middleware

thunk

Basic Usage 1st 实现action方法
export const addDeck = name => ({ type: "ADD_DECK", data: name });
2nd 根据action方法创建reducer方法
export const showBack = (state, action) => {
  switch(action.type) {
    case "SHOW_BACK":
      return action.data || false;
    default:
      return state || false;
  }
};
3rd 根据reducer方法创建store
const store = createStore(combineReducers(reducers));

store.subscribe()方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

store.subscribe(listener);
显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。
store.subscribe方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();
4th 引入react-redux的,导入store

    {...}
5th react组件中通过connect方法绑定store和dispatch。
const mapStateToProps = (newTalks) => ({
    newTalks
});

const mapDispatchToProps = dispatch => ({
    testFunc: () => dispatch(updataTalkLists(1)),
    receiveData: () => dispatch(receiveData())
});

export default connect(mapStateToProps, mapDispatchToProps)(MainPage);
6th this.props中直接调用action方法。
this.props.receiveData
With react-router

结合router使用时需要有2步。

1st 绑定routing到reducer上
import { syncHistoryWithStore, routerReducer } from "react-router-redux";
import * as reducers from "./redux/reducer";
reducers.routing = routerReducer;

const store = createStore(combineReducers(reducers));
2nd 使用syncHistoryWithStore绑定store和browserHistory
const history = syncHistoryWithStore(browserHistory, store);

        
           
               {routes}
           
        
Async

类似 Express 或 Koa 框架中的中间件。它提供的是位于 action 被发起之后,到达 reducer 之前的扩展。
中间件的设计使用了非常多的函数式编程的思想,包括:高阶函数,复合函数,柯里化和ES6语法,源码仅仅20行左右。
项目中主要使用了三个中间件,分别解决不同的问题。

thunkMiddleware:处理异步Action

apiMiddleware:统一处理API请求。一般情况下,每个 API 请求都至少需要 dispatch 三个不同的 action(请求前、请求成功、请求失败),通过这个中间件可以很方便处理。

loggerMiddleware:开发环境调试使用,控制台输出应用state日志

实现action异步操作,必须要引入middleware。我这里用了applyMiddleware(thunkMiddleware)组件,也可以用其他的。

1st 创建store是引入Middleware
import thunkMiddleware from "redux-thunk";
import { createStore, combineReducers, applyMiddleware } from "redux";

const store = createStore(combineReducers(reducers), applyMiddleware(thunkMiddleware));
2nd 创建一个可以执行dispacth的action

这也是中间件的作用所在。

export const receiveData = data => ({ type: "RECEIVE_DATA", data: data });

export const fetchData = () => {
  return dispatch => {
    fetch("/api/data")
      .then(res => res.json())
      .then(json => dispatch(receiveData(json)));
  };
};
3rd 组件中对异步的store元素有相应的判断操作。

React组件会在store值发生变化时自动调用render()方法,更新异步数据。但是我们同样也需要处理异步数据没有返回或者请求失败的情况。否则渲染会失败,页面卡住。

if(!data.newTalks) {
   return(
); }
相关知识 Store的实现

Store提供了3个方法

import { createStore } from "redux";
let { 
    subscribe, //监听store变化
    dispatch,  //调用action方法
    getState  //返回当前store
} = createStore(reducer);

下面是create方法的一个简单实现

const createStore = (reducer) => {
  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 };
};
combineReducer的简单实现
const combineReducers = reducers => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce(
      (nextState, key) => {
        nextState[key] = reducers[key](state[key], action);
        return nextState;
      },
      {} 
    );
  };
};
中间件

createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。

中间件的次序有讲究,logger就一定要放在最后,否则输出结果会不正确。

const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(thunk, promise, logger)
);

applyMiddlewares的实现,它是将所有中间件组成一个数组,依次执行

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

上面代码中,所有中间件被处理后得到一个数组保存在chain中。之后将chain传给compose,并将store.dispatch传给返回的函数。。可以看到,中间件内部(middlewareAPI)可以拿到getState和dispatch这两个方法。

那么在这里面做了什么呢?我们再看compose的实现:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  } else {
    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  }
}

compose中的核心动作就是将传进来的所有函数倒序(reduceRight)进行如下处理:

(composed, f) => f(composed)

我们知道Array.prototype.reduceRight是从右向左累计计算的,会将上一次的计算结果作为本次计算的输入。再看看applyMiddleware中的调用代码:

dispatch = compose(...chain)(store.dispatch)

compose函数最终返回的函数被作为了dispatch函数,结合官方文档和代码,不难得出,中间件的定义形式为:

function middleware({dispatch, getState}) {
    return function (next) {
        return function (action) {
            return next(action);
        }
    }
}

或  

middleware = (dispatch, getState) => next => action => {
    next(action);
}

也就是说,redux的中间件是一个函数,该函数接收dispatch和getState作为参数,返回一个以dispatch为参数的函数,这个函数的返回值是接收action为参数的函数(可以看做另一个dispatch函数)。在中间件链中,以dispatch为参数的函数的返回值将作为下一个中间件(准确的说应该是返回值)的参数,下一个中间件将它的返回值接着往下一个中间件传递,最终实现了store.dispatch在中间件间的传递。

redux-promise中间件

既然 Action Creator 可以返回函数,当然也可以返回其他值。另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象。

写法一,返回值是一个 Promise 对象。

const fetchPosts = 
  (dispatch, postTitle) => new Promise(function (resolve, reject) {
     dispatch(requestPosts(postTitle));
     return fetch(`/some/API/${postTitle}.json`)
       .then(response => {
         type: "FETCH_POSTS",
         payload: response.json()
       });
});

写法二,Action 对象的payload属性是一个 Promise 对象。这需要从redux-actions模块引入createAction方法,并且写法也要变成下面这样。

import { createAction } from "redux-actions";

class AsyncApp extends Component {
  componentDidMount() {
    const { dispatch, selectedPost } = this.props
    // 发出同步 Action
    dispatch(requestPosts(selectedPost));
    // 发出异步 Action
    dispatch(createAction(
      "FETCH_POSTS", 
      fetch(`/some/API/${postTitle}.json`)
        .then(response => response.json())
    ));
  }

上面代码中,第二个dispatch方法发出的是异步 Action,只有等到操作结束,这个 Action 才会实际发出。注意,createAction的第二个参数必须是一个 Promise 对象。

redux-promise的源码

export default function promiseMiddleware({ dispatch }) {
  return next => action => {
    if (!isFSA(action)) {
      return isPromise(action)
        ? action.then(dispatch)
        : next(action);
    }

    return isPromise(action.payload)
      ? action.payload.then(
          result => dispatch({ ...action, payload: result }),
          error => {
            dispatch({ ...action, payload: error, error: true });
            return Promise.reject(error);
          }
        )
      : next(action);
  };
}

从上面代码可以看出,如果 Action 本身是一个 Promise,它 resolve 以后的值应该是一个 Action 对象,会被dispatch方法送出(action.then(dispatch)),但 reject 以后不会有任何动作;如果 Action 对象的payload属性是一个 Promise 对象,那么无论 resolve 和 reject,dispatch方法都会发出 Action。

mapStateToProps()

mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系

mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。

使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。

connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。

// 容器组件的代码
//    
//      All
//    

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}
mapDispatchToProps()

mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。

mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。

const mapDispatchToProps = (
  dispatch,
  ownProps
) => {
  return {
    onClick: () => {
      dispatch({
        type: "SET_VISIBILITY_FILTER",
        filter: ownProps.filter
      });
    }
  };
}

如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。

const mapDispatchToProps = {
  onClick: (filter) => {
    type: "SET_VISIBILITY_FILTER",
    filter: filter
  };
}
组件

React-Redux 提供Provider组件,可以让容器组件拿到state,它的原理是React组件的context属性,请看源码。

class Provider extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: React.PropTypes.object
}

上面代码中,store放在了上下文对象context上面。然后,子组件就可以从context拿到store,代码大致如下。

class VisibleTodoList extends Component {
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    );
  }

  render() {
    const props = this.props;
    const { store } = this.context;
    const state = store.getState();
    // ...
  }
}

VisibleTodoList.contextTypes = {
  store: React.PropTypes.object
}
redux-thunk

我们知道,异步调用什么时候返回前端是无法控制的。对于redux这条严密的数据流来说,如何才能做到异步呢。redux-thunk的基本思想就是通过函数来封装异步请求,也就是说在actionCreater中返回一个函数,在这个函数中进行异步调用。我们已经知道,redux中间件只关注dispatch函数的传递,而且redux也不关心dispatch函数的返回值,所以只需要让redux认识这个函数就可以了。
看了一下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;
export default thunk;

这段代码跟上面我们看到的中间件没有太大的差别,唯一一点就是对action做了一下如下判断:

if (typeof action === "function") {
   return action(dispatch, getState, extraArgument);
}

也就是说,如果发现actionCreater传过来的action是一个函数的话,会执行一下这个函数,并以这个函数的返回值作为返回值。前面已经说过,redux对dispatch函数的返回值不是很关心,因此此处也就无所谓了。

这样的话,在我们的actionCreater中,我们就可以做任何的异步调用了,并且返回任何值也无所谓,所以我们可以使用promise了:

function actionCreate() {
    return function (dispatch, getState) {
        // 返回的函数体内自由实现。。。
        Ajax.fetch({xxx}).then(function (json) {
            dispatch(json);
        })
    }
}

最后还需要注意一点,由于中间件只关心dispatch的传递,并不限制你做其他的事情,因此我们最好将redux-thunk放到中间件列表的首位,防止其他中间件中返回异步请求。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81032.html

相关文章

  • dva框架使用详解及Demo教程

    摘要:框架的使用详解及教程在前段时间,我们也学习讲解过框架的基本使用,但是有很多同学在交流群里给我的反馈信息说,框架理解上有难度,看了之后还是一脸懵逼不知道如何下手,很多同学就转向选择使用框架。 dva框架的使用详解及Demo教程 在前段时间,我们也学习讲解过Redux框架的基本使用,但是有很多同学在交流群里给我的反馈信息说,redux框架理解上有难度,看了之后还是一脸懵逼不知道如何下手,很...

    bergwhite 评论0 收藏0
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目

    摘要:利用中间件实现异步请求,实现两个用户角色实时通信。目前还未深入了解的一些概念。往后会写更多的前后台联通的项目。删除分组会连同组内的所有图片一起删除。算是对自己上次用写后台的一个强化,项目文章在这里。后来一直没动,前些日子才把后续的完善。 欢迎访问我的个人网站:http://www.neroht.com/ 刚学vue和react时,利用业余时间写的关于这两个框架的训练,都相对简单,有的...

    tangr206 评论0 收藏0
  • 前端面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)

    摘要:并总结经典面试题集各种算法和插件前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快速搭建项目。 本文是关注微信小程序的开发和面试问题,由基础到困难循序渐进,适合面试和开发小程序。并总结vue React html css js 经典面试题 集各种算法和插件、前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快...

    pumpkin9 评论0 收藏0
  • 前端面试题总结(js、html、小程序、React、ES6、Vue、算法、全栈热门视频资源)

    摘要:并总结经典面试题集各种算法和插件前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快速搭建项目。 本文是关注微信小程序的开发和面试问题,由基础到困难循序渐进,适合面试和开发小程序。并总结vue React html css js 经典面试题 集各种算法和插件、前端视频源码资源于一身的文档,优化项目,在浏览器端的层面上提升速度,帮助初中级前端工程师快...

    Carson 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<