理解 React 中的 Redux-Thunk

1,061 阅读4分钟

pexels-cliff-booth-4057839.jpg

Thunk 是一个逻辑编程概念。你可以用来处理推迟任何事件的计算或者评估的函数,并且 React-Thunk 可以有效地充当应用程序的单独线程。

Redux Thunk 是一个中间件,它允许 Redux 返回函数而不是 actions。这就允许你在延迟处理 actions 的时候结合 promises 使用。

该中间件的主要应用包括处理潜在的异步 actions 操作,例如使用 Axios 发送一个 GET 请求。

借助 Redux Thunk,我们可以异步 dispatch 这些操作并返回 promise 处理的结果。

下面我们来实操下:

设置工作环境

假设你已经通过 create-react-app 生成了一个 redux 项目,参考 React Js 中创建和使用 Redux Store。通过 npm install redux-thunk --save 或者 yarn add redux-thunk 进行安装。

然后,我们可以使用 applyMiddleware() 开启:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

为什么是 Redux Thunk?

如果你熟悉 Redux,你将会了解相关重要概念:actions, actions creators, reducersmiddleware

Redux store 只允许同步 dispatch actions,并且一个 Redux store 中不会有任何异步逻辑。它只会明白怎么同步dispatch 事件并更新 state

请注意,Reducer 是一个纯函数;因此它不能用于处理 API 调用。它不应该造成副作用,也不应该直接改变 state

React 中,你不应该直接更改 state。而是,使用 setState 去更新一个对象的 state 状态。

Redux 使用 actionsreducers 去更新你应用的 state。使用这两个可以让人们轻松了解数据如何流动以及 state 何时发生变化。

Redux 首先复制 state,然后重写你想更改 state 的值。

return {
  ...state,
  zip: MOR0O0
}

为了让事情简单,Redux-thunk 是一个中间件,使用户能够使用异步函数代替 API 调用。

action 对象应该被返回,因为 action 是一个对象。Redux-thunk 允许一个 action creator 返回一个函数!现在,我们可以做任何异步工作。

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;

Thunk 中间件到底是什么

Redux 中间件允许你拦截每个发送到 reduceraction,并更改或者取消 action

中间件可以帮助你进行日志记录、错误报告、异步请求等。

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';

const reducer = (state = 0, action) => {
  switch(action.type) {
    case "INCREMENT": 
      return state + action.payload;
    case "DECREMENT": 
      return state - action.payload;
    default;
      return state;
  }
};

const store = createStore(reducer);

store.subscribe(() => {
  console.log("current state", store.getState());
});

store.dispatch({
  type: "INCREMENT",
  payload: 1
});

store.dispatch({
  type: "INCREMENT",
  payload: 5
});

store.dispatch({
  type: "DECREMENT",
  payload: 2
});

createStore 函数包含三个参数:

  • 第一个参数 reducer - 必填
  • 第二个参数是 state 初始值 - 可选
  • 第三个参数是中间件 - 可选

由于嵌套函数的特定语法,createStore 函数会根据参数的类型自动确定传递的参数是中间件。

LoggerMiddleware 如下:

const loggerMiddleware = (store) => (next) => (action) => {
  console.log("action", action);
  next(action);
}

在控制台上,你将会看到下面的输出:

logger-middleware.png

如上所示,中间件会在 actiondispatch 前调用

怎么使用 Redux Thunk: 构建一个购物车

shopping-cart.png

在本教程中,我们将使用 Redux Thunk 开发一个简单的购物车功能,更好地明白 Thunk 怎么工作。

为了连接 Redux store,我们在 products.json 文件中模拟些数据:

// product.json

[ 
  {"id": 1, "title": "Strawberry ice-cream", "price": 100.01, "inventory": 2}, 
  {"id": 2, "title": "Gucci T-Shirt Blue", "price": 15.99, "inventory": 10}, 
  {"id": 3, "title": "Vulton XCX - Sucker ", "price": 29.99, "inventory": 5} 
]

一旦 Redux Thunk 被安装并引入到项目 applyMiddleware(thunk),你就可以异步派发 actions 了。

我们创建购物车的 actions

import shop from '../api/shop';
import * as types from '../constants/ActionTypes';

const receiveProducts = products => ({
  type: types.RECEIVE_PRODUCTS, products
})

export const getAllProducts = () => dispatch => {
  shop.getProducts(products => {
    dispatch(receiveProducts(products))
  })
}

const addToCartUnsafe = productId => ({
  type: types.ADD_TO_CART, productid
})

export const addToCart = productId => (dispatch, getState) => {
  if(getState().products.byId[productId].inventory > 0) {
    dispatch(addToCartUnsafe(productId))
  }
}

export const checkout = products => (dispatch, getState) => {
  const { cart } = getState()
  
  dispatch({
    type: types.CHECKOUT_REQUEST
  })
  
  shop.buyProducts(products, () => {
    dispatch.buyProducts(products, () => {
      dispatch({
        type: types.CHECKOUT_SUCCESS, cart
      })
    })
  })
}

而且因为一直手动编写这些对象有些烦人(更不用说容易出错)。

Reduxaction types/creators 的概念来消除这些事情:

export const ADD_TO_CART = 'ADD_TO_CART' 
export const CHECKOUT_REQUEST = 'CHECKOUT_REQUEST' 
export const CHECKOUT_SUCCESS = 'CHECKOUT_SUCCESS' 
export const CHECKOUT_FAILURE = 'CHECKOUT_FAILURE' 
export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS'

接下来,我们首先导入每个 action 和钩子。我们派发 actions,然后访问 store 中的数据:

import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import { createLogger } from 'redux-logger';
import thunk from 'redux-thunk';
import reducer from './reducers';
import { getAllProducts } from './actions';
import App from './containers/App';
const middleware = [ thunk ];

if(process.env.NODE_ENV !== 'production') {
  middleware.push(createLogger());
}

const store = createStore(
  reducer,
  applyMiddleware(...middleware)
)

store.dispatch(getAllProducts());

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

确保你已经将 thunk 放在 applyMiddleware 中,否则它不起效。

Redux-Thunk 幕后怎么工作

关于 redux-thunk 的全部代码如下:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    // This gets called for every action you dispatch
    // If it's a function, call it
    if(typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    
    // Otherwise, just continue processing this action as usual
    return next(action);
  }
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

安装并应用了 redux-thunk 中间件后,你派发的所有 actions 都会经过上面代码流程。

当一个 action 是一个函数,它会被调用,否则它会被传递给下一个中间件或者 Redux 本身。

参考文件