手写 redux-thunk

了解了redux,还需要好好了解 Redux 相关的生态。

一、redux-thunk

redux-thunk是什么?redux-thunk是redux的中间件, 利用 redux 的中间件可以让 redux 支持异步的 action。

如下:原始的Redux里面,action creator必须返回plain object,而且必须是同步的。

function increment() {
  return {
    type: 'INCREMENT'
  }
};

store.dispatch(increment());

如下:涉及到异步操作

function increment() {
  return {
    type: 'INCREMENT'
  }
};

// 异步action creator
function incrementAsync() {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(increment());
    }, 1000);
  }
}

// 使用了Redux-Thunk后dispatch不仅仅可以发出plain object,还可以发出这个异步的函数
store.dispatch(incrementAsync());

综上可以看出:在使用Redux-Thunk前我们dispatchaction必须是一个纯对象(plain object),使用了Redux-Thunk后,dispatch可以支持函数,这个函数会传入dispatch本身作为参数。

没有 redux-thunk

如果没有 redux-thunk ,异步 action有两种办法如下:

1、先创建「请求数据」的 action,然后再创建一个「更新数据」的 action:

2、直接发请求,得到数据之后创建「更新数据」的 action

setTimeout(() => {
  store.dispatch(increment());
}, 1000);

如下实现一个简单的通知,5秒后关闭:

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

这样写同样可以在1秒后发出增加的action,而且代码还更简单,那我们为什么还要用Redux-Thunk呢,他存在的意义是什么呢?stackoverflow对这个问题有一个很好的回答,而且是官方推荐的解释

翻译就是下面的意思:

**不要觉得一个库就应该规定了所有事情!**如果你想用JS处理一个延时任务,直接用setTimeout就好了,即使你使用了Redux也没啥区别。Redux确实提供了另一种处理异步任务的机制,但是你应该用它来解决你很多重复代码的问题。如果你没有太多重复代码,使用语言原生方案其实是最简单的方案。

具体说明可以看StackOverflow,也可以看翻译后的https://juejin.cn/post/6869950884231675912

redux中间件大概就长这个样子:

  • 一个中间件接收store作为参数,会返回一个函数
  • 返回的这个函数接收老的dispatch函数作为参数(也就是上面的next),会返回一个新的函数
  • 返回的新函数就是新的dispatch函数,这个函数里面可以拿到外面两层传进来的store和老dispatch函数
function logger(store) {
  return function(next) {
    return function(action) {
      console.group(action.type);
      console.info('dispatching', action);
      let result = next(action);
      console.log('next state', store.getState());
      console.groupEnd();
      return result
    }
  }
}

 redux-thunk 的源码如下:https://github.com/reduxjs/redux-thunk/blob/master/src/index.js

如上,一共就14行代码,在简化以下就如下:

const thunk = ({ dispatch, getState }) => (next) => (action) => {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }

  return next(action);
};
export default thunk


//详细如下:
function thunk(store) {
  return function (next) {
    return function (action) {
      // 从store中解构出dispatch, getState
      const { dispatch, getState } = store;

      // 如果action是函数,将它拿出来运行,参数就是dispatch和getState
      if (typeof action === 'function') {
        return action(dispatch, getState);
      }

      // 否则按照普通action处理
      let result = next(action);
      return result
    }
  }
}

这段代码的逻辑就是:

  1. 如果 action 是个函数,就调用这个函数
  2. 如果 action 不是函数,就传给下一个中间件

其实就是:发现 action 是函数就调用它
有了 redux-thunk:action 就可以是函数了

const action = function(dispatch) {
  return fetchUsers().then(
    (users) => dispatch({type:'updateUsers', payload: users}),
    (error) => dispatch({type:'updateUsersError'}),
  );
};
dispatch(action)

这个 action 会被调用,然后在将来某个时候创建 updateUsers action,所以我们可以称它为异步 action(其实就是函数),redux-thunk 就是帮你调用这个函数。

源码中:在thunk函数外又包裹了一层,其实就是传递额外的参数

const api = "http://www.example.com/sandwiches/";
const whatever = 42;

const store = createStore(
  reducer,
  applyMiddleware(thunk.withExtraArgument({ api, whatever })),
);

 

二、redux-saga

对于复杂场景,Redux-Thunk并不适用,推荐Redux-Saga来处理复杂副作用。Redux-Saga也是在实际工作中使用最多的Redux异步解决方案。Redux-SagaRedux-Thunk复杂得多,而且他整个异步流程都使用Generator来处理,Generator也是我们这篇文章的前置知识,如果你对Generator还不熟悉,可以看看这篇文章

redux-saga 是一个 redux 中间件,它具有如下特性:

  • 集中处理 redux 副作用问题。

  • 被实现为 generator 。

  • 类 redux-thunk 中间件。

  • watch/worker(监听->执行) 的工作形式。

简单案例:点击一个按钮去请求用户的信息,大概长这样

Sep-11-2020 16-31-55

import React from 'react';
import { connect } from 'react-redux';

function App(props) {
  const { dispatch, userInfo } = props;

  const getUserInfo = () => {
    dispatch({ type: 'FETCH_USER_INFO' })
  }

  return (
    <div className="App">
      <button onClick={getUserInfo}>Get User Info</button>
      <br></br>
      {userInfo && JSON.stringify(userInfo)}
    </div>
  );
}

const matStateToProps = (state) => ({
  userInfo: state.userInfo
})

export default connect(matStateToProps)(App);

store.js:

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import rootSaga from './saga';

const sagaMiddleware = createSagaMiddleware()

let store = createStore(reducer, applyMiddleware(sagaMiddleware));

// 注意这里,sagaMiddleware作为中间件放入Redux后
// 还需要手动启动他来运行rootSaga
sagaMiddleware.run(rootSaga);

export default store;

saga.js:

import { call, put, takeLatest } from 'redux-saga/effects';
import { fetchUserInfoAPI } from './api';

function* fetchUserInfo() {
  try {
    const user = yield call(fetchUserInfoAPI);
    yield put({ type: "FETCH_USER_SUCCEEDED", payload: user });
  } catch (e) {
    yield put({ type: "FETCH_USER_FAILED", payload: e.message });
  }
}

function* rootSaga() {
  yield takeEvery("FETCH_USER_INFO", fetchUserInfo);
}

export default rootSaga;
  • takeEvery:他的作用是监听每个FETCH_USER_INFO,当FETCH_USER_INFO出现的时候,就调用fetchUserInfo函数,注意这里是每个FETCH_USER_INFO。也就是说如果同时发出多个FETCH_USER_INFO,我们每个都会响应并发起请求。
  • takeLatesttakeLatest从名字都可以看出来,是响应最后一个请求,具体使用哪一个,要看具体的需求。

具体参考:https://juejin.cn/post/6885223002703822855

猜你喜欢

转载自blog.csdn.net/CamilleZJ/article/details/117117564