来源:《React Hooks 核心原理与实战》--王沛
在 Redux 的 Store 中,我们不仅维护着业务数据,同时维护着应用程序的状态。比如对于发送请求获取数据这样一个异步的场景,我们来看看涉及到 Store 数据会有哪些变化:
1.请求发送出去时:设置 state.pending = true,用于 UI 显示加载中的状态;
2.请求发送成功时:设置 state.pending = false, state.data = result。即取消 UI 的加载状态,同时将获取的数据放到 store 中用于 UI 的显示。
3.请求发送失败时:设置 state.pending = false, state.error = error。即取消 UI 的加载状态,同时设置错误的状态,用于 UI 显示错误的内容。
任何对 Store 的修改都是由 action 完成的。那么对于一个异步请求,上面的三次数据修改显然必须要三个 action 才能完成。那么假设我们在 React 组件中去做这个发起请求的动作,代码逻辑应该类似如下:
functionDataList(){
constdispatch = useDispatch();//在组件初次加载时发起请求
useEffect(() => {//请求发送时
dispatch({ type: 'FETCH_DATA_BEGIN' }); fetch('/some-url').then(res => {//请求成功时
dispatch({ type: 'FETCH_DATA_SUCCESS', data: res });
}).catch(err => {//请求失败时
dispatch({ type: 'FETCH_DATA_FAILURE', error: err });
})
}, []);//绑定到state的变化
constdata = useSelectore(state => state.data); constpending = useSelector(state => state.pending); consterror = useSelector(state => state.error);//根据state显示不同的状态
if (error) return 'Error.';
if (pending) return 'Loading...';
return <Tabledata = { data }/>;
}
从这段代码可以看到,我们使用了三个(同步)Action 完成了这个异步请求的场景。这里我们将 Store 完全作为一个存放数据的地方,至于数据哪里来, Redux 并不关心。尽管这样做是可行的。
但是很显然,发送请求获取数据并进行错误处理这个逻辑是不可重用的。假设我们希望在另外一个组件中也能发送同样的请求,就不得不将这段代码重新实现一遍。
因此,Redux中提供了 middleware 这样一个机制,让我们可以巧妙地实现所谓异步 Action 的概念。简单来说,middleware 可以让你提供一个拦截器在 reducer 处理 action 之前被调用。在这个拦截器中,你可以自由处理获得的 action。无论是把这个 action 直接传递到reducer,或者构建新的 action 发送到 reducer,都是可以的。从下面这张图可以看到,Middleware 正是在 Action 真正到达 Reducer 之前提供的一个额外处理 Action 的机会:
我们刚才也提到了,Redux 中的 Action 不仅仅可以是一个 Object,它可以是任何东西,也可以是一个函数。
利用这个机制,Redux 提供了redux-thunk这样一个中间件,它如果发现接受到的 action 是一个函数,那么就不会传递给 Reducer,而是执行这个函数,并把 dispatch 作为参数传给这个函数,从而在这个函数中你可以自由决定何时,如何发送Action。
例如对于上面的场景,假设我们在创建 Redux Store 时指定了 redux-thunk 这个中间件:
import {
createStore,
applyMiddleware
} from 'redux'
import thunkMiddleware from 'redux - thunk'
import rootReducer from './reducer'
const composedEnhancer = applyMiddleware(thunkMiddleware)
const store = createStore(rootReducer, composedEnhancer)
function fetchData() {
return dispatch => {
dispatch({
type: 'FETCH_DATA_BEGIN'
});
fetch('/some-url').then(res
=> {
dispatch({
type: 'FETCH_DATA_SUCCESS', data: res
})
;
}).catch(err => {
dispatch({
type: 'FETCH_DATA_FAILURE', error: err
});
})
}
}
那么在我们 dispatch action 时就可以 dispatch 一个函数用于来发送请求,通常,我们会写成如下的结构:
import fetchData from './fetchData';
function DataList() {
const dispatch = useDispatch(); // dispatch 了一个函数由 redux - thunk 中间件去执行
dispatch(fetchData());
}
可以看到,通过这种方式,我们就实现了异步请求逻辑的重用。那么这一套结合redux-thunk中间件的机制,我们就称之为异步 Action。
所以说异步 Action 并不是一个具体的概念,而可以把它看作是 Redux 的一个使用模式。它通过组合使用同步 Action ,在没有引入新概念的同时,用一致的方式提供了处理异步逻辑的方案。
来源:《React Hooks 核心原理与实战》--王沛