Redux 05 Thunk-发送异步action

Redux 05 Thunk-发送异步action

用Redux的话,与后端交互就优雅的使用异步Action吧

继续来看几个概念。

Thunk

在看到Redux的Thunk概念之前,我对于与服务端交互还停留在以前写Vue的时候把交互部分写在事件里的水平。看了之后发现自己有点low了,正确的方式是应该发送异步Action。

为什么要使用Redux-Thunk

这个问题,在SegmentFault:Redux异步解决方案之Redux-Thunk原理及源码解析里讲的很透彻,我只是觉得不这么用,自己包装会比较麻烦,这篇文章直接把官网的回答翻译了。看了很多东西只要到官网里都能找到答案的。

写点代码准备一下

通过create-react-app新创建一个项目,然后清空App.js,之后写一个按钮和一个展示结果的组件:

export const MyButton = (props) => (
    <button {...props}>访问API</button>
);

export const MyResult = (props) => (
    <h2>{props.value}</h2>
);

然后定义几个action,分别为开始获取API,获取失败和获取成功,以及创建对应的actionCreator:

export const actionTypes = {
    FETCH_POKEMON: "FETCH_POKEMON",
    FETCH_SUCCESS: "FETCH_SUCCESS",
    FETCH_FAILED: "FETCH_FAILED",
};

export const fetchData = () => ({
    type: actionTypes.FETCH_POKEMON
});

export const fetchSuccess = (data) => ({
    type: actionTypes.FETCH_SUCCESS,
    data,
});

export const fetchFailed = () => ({
    type: actionTypes.FETCH_FAILED,
});

之后是reducer和store。这里考虑state中有三个属性,一个表示是否正在异步通信中,一个用于保存数据,一个表示是否失败。reducer就是更改这两个属性,一并编写如下:

export const rootReducer = (state, action) => {
    switch (action.type) {
        case actionTypes.FETCH_POKEMON:
            return {isFetching: true, data: "开始获取数据", failed: false};
        case actionTypes.FETCH_SUCCESS:
            return {isFetching: false, data: action.data.next, failed: false}
        case actionTypes.FETCH_FAILED:
            return {isFetching: false, data: "获取失败", failed: true}
        default:
            return state;
    }
};

const store = createStore(rootReducer, {
    isFetching: false,
    data: "没有数据",
    failed: false
});

之后安装react-redux,使用Provider组件:

ReactDOM.render(
  <React.StrictMode>
      <Provider store={store}>
    <App />
      </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

然后在App.js里连接一下MyResult到State。

function App() {

    const myMapStateToProps = (state) => ({
        disabled: state.isFetching,
        value:state.data,
        failed:state.failed,
    });

    const WrappedResult = connect(myMapStateToProps, null)(MyResult);

  return (
      <div className="App">
        <MyButton/>
        <WrappedResult/>
      </div>
  );
}

编写按钮的事件

这里我们准备访问的API是口袋妖怪的API:https://pokeapi.co/api/v2/pokemon,这里为了方便,把按钮改成类组件:

import React, {Component} from 'react';
import {store} from "./index";
import {fetchFailed, fetchSuccess} from "./Actions";

class MyButton extends Component {

    constructor(props) {
        super(props);
    }

    fetchPokemon = () => {
        fetch("https://pokeapi.co/api/v2/pokemon").then(response => response.json()).then(data => store.dispatch(fetchSuccess(data))).catch(error => store.dispatch(fetchFailed()));

    }

    render() {
        return (
            <button {...this.props} onClick={this.fetchPokemon}>访问API</button>

        );
    }
}

export default MyButton;

这其中把点击改成fetchAPI,根据结果来派发两个不同的dispatch,但是很难再派发其他控制的东西,因为Promise链还必须一个接一个的返回东西。

这个时候就可以使用到Thunk了。

派发函数而不是action

由于我们现在要派发函数而不是直接派发对象,我们先写这个要被派发的函数:

export const fetchTodos = () => {
    return (dispatch) => {
        dispatch(fetchTodosRequest());
        return fetch("./mock/todos.json").then(
            response => {
                response.json().then(data => {
                        dispatch(fetchTodosSuccess(data))
                    }
                )
            },
            error => {
                dispatch(fetchTodosFailure(error));
                console.log("An error occured: " + error)
            }
        );
    }
}

这个函数运行之后,返回一个以dispatch为参数的函数,其中先用dispatch派发了开始访问API的动作,然后根据访问结果的不同,进行派发不同的动作。
然后我们需要派发这个函数,修改一下MyButton的代码如下:

class MyButton extends Component {

    constructor(props) {
        super(props);
    }

    handleClick = () => {
        store.dispatch(fetchPokemon());
    }

    render() {
        return (
            <button {...this.props} onClick={this.handleClick}>访问API</button>

        );
    }
}

一运行,按下按钮,发现报错如下:

Error: Actions must be plain objects. Instead, the actual type was: 'function'.

提示action只能是纯对象,结果我们派发了一个函数,自然这也是我们预料之中的结果。

使用redux-thunk

这个时候就要使用redux-thunk了,先用npm安装:

npm install redux-thunk

之后需要在创建store的时候使用中间件,代码如下:

import thunk from "redux-thunk";

export const store = createStore(rootReducer, {
    isFetching: false,
    data: "没有数据",
    failed: false
}, applyMiddleware(thunk));

redux-thunk就是用来让dispatch可以派发一个函数,这个函数的形态上边已经说了,就是返回一个以dispatch为参数的函数,来根据异步访问API的结果实际派发不同的action。

有了这种操作之后,可以把这个特定的函数就看成一个action,当成dispatch的参数进行使用,不仅仅可以使用在点击按钮这些事件交互中,也可以直接在组件挂载的时候就去派发这个函数,根据访问结果更新state,这种在Redux下使用异步action的方法非常优雅。

LICENSED UNDER CC BY-NC-SA 4.0
Comment