NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

React的思考(八)- Redux的Middleware(下)异步的世界

| Comments

有序而独立的同步世界

没有异步的情况下,Redux配合React很容易理解的(Action->Reducer->CombineReducers->React-Redux->Component),简单回顾下:

1.在组件里面dispatch(发出)一个action对象(带上类型和数据)
2.action对象被传递到reducer的入口,reducer根据类型给到不同的switch分支,然后根据带入的数据操作state,返回新的state
3.redux发现有新的state,配合React-Redux,通知所有component,告诉组件请注意你自己要不要更新,然后各自判断各自更新

一个被传递到组件里的disptach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const mapDispatchToProps = dispatch => {
  return {
    onAddTodo: text => {
      dispatch(addTodo(text))
    }
  }
}
const addTodo = (text) => {
  type: 'ADD_TODO',
  text
}

//在组件里面点击时触发
handleClickAdd() {
  this.props.onAddTodo(this.state.todoText)
}

action被传递到reducer的入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function todoApp(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO://命中这个switch case
      return Object.assign({}, state, {//返回新的state
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}

组件被通知请查看你是否需要更新,connect发现todos变了,所以要更新这个connect嵌入的组件

1
2
3
4
5
const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

没有Redux的异步世界

异步世界其实没什么可怕的(又不是异世界),看下面一个React里面用fetch实现的数据异步加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class ExampleComponent extends React.Component {
  constructor(props){
    super(props);
    this.state = { name: '', loading: false, errorMessage: '' };
    this._getRandomName = this.getRandomName.bind(this);
  }

  render() {
    const { name, errorMessage } = this.state;
    return(
      <div>
        <h1>{name}</h1>
        <button onClick={this._getRandomName}>PRESS ME!</button>
        { errorMessage && <div>{errorMessage}</div> }
      </div>
    );
  }

  getRandomName() { // 重点是这么一段代码
    this.setState({loading: true})
    fetch('https://randomuser.me/api/')
      .then(response => response.json())
      .then(data => {
        const person = data.results[0];
        this.setState({ name: `${person.name.first} ${person.name.last}`, loading: false })
      })
      .catch(reason => {
        this.setState({errorMessage:'get name failed', loading: false})
      });
  }
}

过程很简单,我们需要考虑三种页面状态:请求开始和进行中,请求成功,请求失败,然后分别设置组件的state。

如果没有Redux异步中间件

按照“没有Redux的异步世界”的思想,在Redux里面,我们仍然可以依葫芦画瓢的进行异步的Redux的操作。

首先,一个异步请求都需要dispatch至少三种action,对应至少三个不同的状态:

1.通知reducer请求开始
2.通知reducer请求成功
3.通知reducer请求失败

1
2
3
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { name: 'Redux'} }

如果没有Redux异步中间件,那么你的做法和没有Redux时是类似的,你需要在mapDispatchToProps那传入三个disptach,将异步的fetch逻辑放在组件里面实现,Redux本身仍然是处理同步的state操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ExampleComponent extends React.Component {
  ...
  getRandomName() {
    this.props.fetchRequest(true)
    fetch('https://randomuser.me/api/')
      .then(response => response.json())
      .then(data => {
        const person = data.results[0];
        this.props.fetchSuccess(`${person.name.first} ${person.name.last}`, false)
      })
      .catch(reason => {
        this.props.fetchFailure('get name failed', false)
      });
  }
}

使用Redux-Thunk的不同点在哪?

因为Redux官网推荐,我们就以Redux-Thunk为例。

Redux-Thunk刚刚引入的时候,往往容易让使用者有些感到混乱,一个原因是函数式编程的嵌套写法,第二个是和Redux之前dispatch函数做的事情不一样了。

其实,没有Redux-Thunk我们已经可以处理异步请求,只不过异步逻辑不在Redux里面,而是在组件里面,如果我们加入Redux-Thunk会有什么不同呢?

我在上面篇文章讲过,中间件的作用是在dispatch的附近做一些额外的操作,让Redux拥有不同的能力,Redux-Thunk中间件的能力,可以让action creater,不用返回一个action对象,而是一个函数,这个action创建的函数就成为一个thunk。

(关于Thunk函数的含义:编译器的”传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做Thunk函数。)

这个函数并不需要保持纯净,它还可以带有副作用,包括执行异步API请求。这个函数还可以dispatch action。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//actions.js
export function fetchRandmonName() {
  return function (dispatch) {
    dispatch(fetchRequest(true))
    return fetch('https://randomuser.me/api/')
      .then(response => response.json())
      .then(data =>{
        const person = data.results[0];
        dispatch(fetchSuccess(`${person.name.first} ${person.name.last}`, false))
      })
      .catch(reason => {
        dispatch(fetchFailure('get name failed', false))
      })
  }
}

const mapDispatchToProps = dispatch => {
  return {
    fetchRandmonName: () => {
      dispatch(fetchRandmonName())
    }
  }
}

//ExampleComponent.js
getRandomName() {
  this.props.fetchRandmonName();
}

你看其实代码差不多,只不过,因为Redux-Thunk,你可以将异步的处理逻辑,从组件里面拿出来,将它放在一个和Redux其他代码更加的内聚的位置,也许是action的存放位置actions.js,而组件里面只需要dispatch一个thunk。

总结

关于Redux的异步世界,暂时更新到这里,Redux里面处理异步的中间件有好多,我就不一个个分析了,你肯定很关心这个,《Redux异步方案选型》

React的思考(七)- Redux的Middleware(上)- 中间件的概念

| Comments

Redux的Middleware(中间件)是Redux中相对比较神秘的部分。

怎么理解middleware这个概念呢?

middle这个词很重要,它是指,这个件(ware),被放在(穿插)于某一个已存在操作的过程当中(middle)。

我这么平淡直白无水准的解释,你应该能get到吧,如果你熟悉Java Web开发,你可能第一时间会想到Java Servlet Filters,当然也许你比较年轻,你对Spring熟悉,你可能会立刻想到AOP(面向切面编程),它们可以完成日志,审计,authentication, authorization等。

那么,再往后面走,如果你使用过Express或者Koa等服务端框架,在这类框架中,middleware是指可以被嵌入在框架“接收请求到产生响应过程之中”的代码。例如,Express或者Koa的middleware可以完成添加CORS headers、记录日志、内容压缩等工作。

Redux的中间件是做什么呢?

回过头来,看Redux的中间件,同样,它肯定是指穿插在Redux的某一个过程当中,那么问题来了,这个ware可以在哪里穿插,或者哪里有穿插操作的需要呢?我们来逐一分析,以下内容,参考阮一峰的文章:

(1)Reducer:纯函数,只承担计算State的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。

(2)View:与State一一对应,可以看作State的视觉层,也不合适承担其他功能。

(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

其实,和其他服务端框架类似,它被嵌入到Redux“接收请求到产生响应过程之中”,位于action被发起之后,到达reducer之前,也就是store.dispatch()附近。

举个例子:如何在dispatch的时候log state和action

我在看官方文档的时候,看到一个非常有趣的概念,猴子补丁(monkey patching),大概的意思是指在运行时动态修改模块、类或函数,通常是添加功能或修正缺陷。

通过这种方式,可以在代码的运行过程中,给store.dispatch打补丁,增加额外的功能,比如log state和action,代码如下:

1
2
3
4
5
6
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}

通过上面这样一段代码,我们就在Redux原来的store.dispatch附近添加了我们自己代码,当我们再次调用store.dispatch,它就会打印log了,不过monkey patching本质上是一种hack,“将任意的方法替换成你想要的”。

如果,我们想给dispatch加另一个补丁,那就在它的前面,或者后面,在加上一段类似的代码呗。

Redux applyMiddlewares()

真实情况下,我们肯定不用这样写代码,那Redux提供了applyMiddlewares的方式,所以就不需要我们像上面那样写,那它是怎么做的呢?

1
2
3
4
const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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和dispatch都传入了middleware
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));//注意这里:这些middleware都是函数
    dispatch = compose(...chain)(store.dispatch);

    return {...store, dispatch}
  }
}

所有中间件被放进了一个数组chain,通过compose,将多个中间件合并,从右到左执行。中间件可以拿到getState和dispatch这两个方法。

注:compose: 将多个函数合并成一个函数,从右到左执行。例如:

1
R.compose(Math.abs, R.add(1), R.multiply(2))(-4) // 7

-4 * 2 + 1 再求绝对值

总结

仔细看下来之后,中间件就没有那么神秘了,下一篇文章,我们来介绍下Redux的异步中间件,比如:Redux-Thunk,看懂这一个,其他的都差不多。