主要是下记录一下 redux-thunk的学习,毕竟刚接触React以及React社区相关的产品
- 看了一下
redux-thunk的源码的实现,也非常的简单易懂
import type { Action, AnyAction } from 'redux'
import type { ThunkMiddleware } from './types'
export type {
ThunkAction,
ThunkDispatch,
ThunkActionDispatch,
ThunkMiddleware
} from './types'
/** A function that accepts a potential "extra argument" value to be injected later,
* and returns an instance of the thunk middleware that uses that value
*/
function createThunkMiddleware<
State = any,
BasicAction extends Action = AnyAction,
ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
// Standard Redux middleware definition pattern:
// See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
return middleware
}
const thunk = createThunkMiddleware() as ThunkMiddleware & {
withExtraArgument<
ExtraThunkArg,
State = any,
BasicAction extends Action = AnyAction
>(
extraArgument: ExtraThunkArg
): ThunkMiddleware<State, BasicAction, ExtraThunkArg>
}
// Attach the factory function so users can create a customized version
// with whatever "extra arg" they want to inject into their thunks
thunk.withExtraArgument = createThunkMiddleware
export default thunk
- 去掉
TypeScript类型操作之后,其核心源码也更清晰了
const middleware = ({ dispatch, getState }) => next => action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
return middleware
- 就是判断一下你传来的
action是不是函数,是函数的话那就把这个函数执行了,所以这样我们就可以更优雅的写异步操作 - 没有
redux-thunk之前你写异步可能是这样写的
const dispatch = useDispatch()
const currentSong = useSelector(store => store.currentSong)
axios.get(api, {
data: {
id: currentSong.id
}
}).then((response) => {
dispatch(playMusic(response.id))
})
- 有了
redux-thunk之后你可以这样写
const dispatch = useDispatch()
const currentSong = useSelector(store => store.currentSong)
const asyncPlayMusic = (id) => (dispatch) => {
return axios.get(api, {
data: {
id
}
}).then((response) => {
dispatch(playMusic(response.id))
})
}
function play() {
dispatch(asyncPlayMusic(currentSong.id))
}
- 下面是一个
codesandbox里redux-thunk写法的例子,可以自己在线操作感受一下,不得不说codesandbox很方便也很强大
import { useDispatch, useSelector } from 'react-redux'
import { addUser } from './store/test.slice'
import './styles.css'
export default function App() {
const dispatch = useDispatch()
const users = useSelector((store) => store.user)
const aysncAddUser = (user) => async (dispatch) => {
setTimeout(() => {
return dispatch(addUser(user))
}, 2000)
}
function add() {
dispatch(aysncAddUser({ name: 'My', id: '7' }))
}
return (
<div className="App">
<ul>
{users.map((user) => <li key={user.id}>{user.name}</li>}
</ul>
<button onClick={add}>Add</button>
</div>
)
}
题外话Thunk的含义
Thunk函数早在上个世纪60年代就诞生了。
那时,编程语言刚刚起步,计算机学家还在研究,编译器怎么写比较好。一个争论的焦点是"求值策略",即函数的参数到底应该何时求值。
var x = 1;
function f(m){
return m * 2;
}
f(x + 5)
上面代码先定义函数 f,然后向它传入表达式 x + 5 。请问,这个表达式应该何时求值?
一种意见是"传值调用"(call by value),即在进入函数体之前,就计算 x + 5 的值(等于6),再将这个值传入函数 f 。C语言就采用这种策略。
let y = x + 5
f(y)
另一种意见是"传名调用"(call by name),即直接将表达式 x + 5 传入函数体,只在用到它的时候求值。Hskell语言采用这种策略。
f(x + 5)
传值调用和传名调用,哪一种比较好?回答是各有利弊。 传值调用比较简单,但是对参数求值的时候,实际上还没用到这个参数,有可能造成性能损失。
function f(a, b){
return b;
}
f(3 * x * x - 2 * x - 1, x);
上面代码中,函数 f 的第一个参数是一个复杂的表达式,但是函数体内根本没用到。对这个参数求值,实际上是不必要的。
因此,有一些计算机学家倾向于"传名调用",即只在执行时求值。
二、Thunk 函数的含义
编译器的"传名调用"实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
function f(m){
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk){
return thunk() * 2;
}
大致我们可以定义 Thunk 的具体含义如下:
Thunk 是一类函数的别名,主要特征是对另外一个函数添加了一些额外的操作,类似装饰器。其主要用途为延迟函数执行(惰性求值)或者给一个函数执行前后添加一些额外的操作。
// ThunkActionCreator
function fetchUser(id) {
// 返回的这个函数既是一个 Thunk, 或者叫 ThunkAction
return async (dispatch) => {
// 额外的异步API调用
const user = await api.getUser(id);
// 此时才真正dispatch action
dispatch({ type: 'UPDATE_USER', payload: user });
}
}
给大家一个思考题fetchUser返回的异步函数里面的dispatch参数是怎么拿到的?也就是
async (dispatch) => {}
里的dispatch参数是怎么拿到的?可以看一下上面的redux-thunk源码
再来分析一下redux-thunk的核心源码
const middleware = ({ dispatch, getState }) => next => action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
return middleware
当我执行
function fetchUser(id) {
return async function thunk(dispatch) => {
setTimeout(() => {
dispatch(updateUser(id))
}, 2000)
}
}
dispatch(fetchUser(id))
redux-thunk中间件会分析到
typeof action === 'function'// === true
即把action看作thunk这个函数
typeof thunk === 'function' // === true
而后redux-thunk到了下面这个逻辑
return action(dispatch, getState, extraArgument)
等价于
thunk(dispatch, getState, extraArgument)
最后thunk函数里面的dispatch参数来源的问题就解决了,然后我们就能这么写了
function fetchUser(id) {
return async function thunk(dispatch) => {
setTimeout(() => {
dispatch(updateUser(id))
}, 2000)
}
}
dispatch(fetchUser(id))