共计 4662 个字符,预计需要花费 12 分钟才能阅读完成。
本文是学习了 2018 年新鲜出炉的 React Hooks 提案之后,针对异步请求数据写的一个案例。注意,本文假设了:1. 你已经初步了解 hooks 的含义了,如果不了解还请移步官方文档。(其实有过翻译的想法,不过印记中文一直在翻译,就是比较慢啦)2. 你使用 Redux 实现过异步 Action(非必需,只是本文不涉及该部分知识而直接使用)3. 你听说过 axios 或者 fetch(如果没有,那么想象一下原生 js 的 promise 实现异步请求,或者去学习下这俩库)全部代码参见仓库:github | Marckon 选择 hooks-onlineShop 分支以及 master 分支查看
❗ 本文并非最佳实践,如有更好的方法或发现文中纰漏,欢迎指正!
前序方案(不想看可以直接跳过)
不考虑引入 Redux
通过学习 React 生命周期,我们知道适合进行异步请求的地方是 componentDidMount 钩子函数内。因此,当你不需要考虑状态管理时,以往的方法很简单:
class App extends React.Component{
componentDidMount(){
axios.get(‘/your/api’)
.then(res=>/*…*/)
}
}
引入 Redux 进行状态管理
当你决定使用 Redux 进行状态管理时,比如将异步获取到的数据储存在 store 中,事情就开始复杂起来了。根据 Redux 的官方文档案例来看,为了实现异步 action,你还得需要一个类似于 redux-thunk 的第三方库来解析你的异步 action。
requestAction.js:定义异步请求 action 的地方
// 这是一个异步 action,分发了两个同步 action,redux-thunk 能够理解它
const fetchGoodsList = url => dispatch => {
dispatch(requestGoodsList());
axios.get(url)
.then(res=>{
dispatch(receiveGoodsList(res.data))
})
};
requestReducer.js: 处理同步 action
const requestReducer=(state=initialState,action)=>{
switch (action.type) {
case REQUEST_GOODSLIST:
return Object.assign({},state,{
isFetching: true
});
case RECEIVE_GOODSLIST:
return Object.assign({},state,{
isFetching:false,
goodsList:action.goodsList
});
default:
return state;
}
};
App Component:你引入 redux store 和 redux-thunk 中间件的地方
import {Provider} from ‘react-redux’;
import thunkMiddleWare from ‘redux-thunk’;
import {createStore,applyMiddleware} from ‘redux’;
//other imports
let store=createStore(
rootReducer,
// 这里要使用中间件,才能够完成异步请求
applyMiddleware(
thunkMiddleWare,
myMiddleWare,
)
);
class App extends React.Component{
render(){
return (
<Provider store={store}>
<RootComponent/>
</Provider>
)
}
}
GoodsList Component:需要进行异步请求的组件
class GoodsList extends React.Component{
//…
componentDidMount(){
this.props.fetchGoodsList(‘your/url’);
}
//…
}
const mapDispatchToProps={
fetchGoodsList
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(GoodsList);
完整代码:branch:master-onlineShop
使用 Hooks-useReducer() 和 useContext()
总之使用 Redux 很累,当然,你可以不使用 Redux,直接通过 props 层层传递,或者使用 context 都可以。只不过本文我们学过了 useReducer,使用到了 Redux 的思想,总要试着用一下。
这里你不需要引入别的任何第三方库了,简简单单地使用 React@16.7.0-alpha.2 版本就好啦
很重要的一点就是——函数式组件,现在 React 推荐我们这么做,可以基本上代替 class 写法。
函数签名
useReducer(reducer,initialState)
useContext(ctxObj)
useEffect(effectFunction,[dependencyValues])
概览 - 你需要编写什么
action.js:
我们还使用 redux 的思想,编写 action
reducer.js:
处理 action,不同于 redux 的 reducer,这里我们可以不用提供初始状态
根组件:
Provider 提供给子组件 context
useReducer 定义的位置,引入一个 reducer 并且提供初始状态 initialState
子组件:
useContext 定义的位置,获取祖先组件提供的 context
useEffect 用于进行异步请求
实现
1.action.js:我们使用 action 创建函数
const REQUEST_GOODSLIST = “REQUEST_GOODSLIST”;
const RECEIVE_GOODSLIST = “RECEIVE_GOODSLIST”;
// 开始请求
const requestGoodsList = () => ({
type: REQUEST_GOODSLIST
});
// 接收到数据
const receiveGoodsList = json => ({
type: RECEIVE_GOODSLIST,
goodsList: json.goodsList,
receivedAt: Date.now()
});
export {
RECEIVE_GOODSLIST,
REQUEST_GOODSLIST,
receiveGoodsList,
requestGoodsList,
}
2.reducer.js:判断 action 的类型并进行相应处理,更新 state
import {
RECEIVE_GOODSLIST,
REQUEST_GOODSLIST,
} from “../..”;
export const fetchReducer=(state,action)=>{
switch (action.type) {
case REQUEST_GOODSLIST:
return Object.assign({},state,{
isFetching: true
});
case RECEIVE_GOODSLIST:
return Object.assign({},state,{
isFetching:false,
goodsList:state.goodsList.concat(action.goodsList)
});
default:
return state;
}
};
3. 根组件:引入 reducer.js
import React,{useReducer} from ‘react’;
import {fetchReducer} from ‘..’;
// 创建并 export 上下文
export const FetchesContext = React.createContext(null);
function RootComponent() {
// 第二个参数为 state 的初始状态
const [fetchesState, fetchDispatch] = useReducer(fetchReducer, {
isFetching: false,
goodsList: []
});
return (
// 将 dispatch 方法和状态都作为 context 传递给子组件
<FetchesContext.Provider value={{fetchesState,dispatch:fetchDispatch}}>
//…
// 用到 context 的一个子组件
<ComponentToUseContext/>
</FetchesContext.Provider>
)
}
4. 子组件:引入 FetchesContext
import {FetchesContext} from “../RootComponent”;
import React, {useContext, useEffect,useState} from ‘react’;
import axios from ‘axios’;
function GoodsList() {
// 获取上下文
const ctx = useContext(FetchesContext);
// 一个判断是否重新获取的 state 变量
const [reFetch,setReFetch]=useState(false);
// 具有异步调用副作用的 useEffect
useEffect(() => {
// 首先分发一个开始异步获取数据的 action
ctx.dispatch(requestGoodsList());
axios.get(proxyGoodsListAPI())
.then(res=>{
// 获取到数据后分发一个 action,通知 reducer 更新状态
ctx.dispatch(receiveGoodsList(res.data))
})
// 第二个参数 reFetch 指的是只有当 reFetch 变量值改变才重新渲染
},[reFetch]);
return (
<div onScroll={handleScroll}>
{
//children
}
</div>
)
}
完整代码参见:branch:hooks-onlineShop
目录结构
我的目录结构大概这样:
src
|- actions
|- fetchAction.js
|- components
|-…
|- reducers
|- fetchReducer.js
|- index.js
注意点
使用 useContext() 时候我们不需要使用 Consumer 了。但不要忘记 export 和 import 上下文对象
useEffect() 可以看做是 class 写法的 componentDidMount、componentDidUpdate 以及 componentWillUnMount 三个钩子函数的组合。
当返回了一个函数的时候,这个函数就在 compnentWillUnMount 生命周期调用
默认地,传给 useEffect 的第一个参数会在每次(包含第一次)数据更新时重新调用
当给 useEffect() 传入了第二个参数(数组类型)的时候,effect 函数会在第一次渲染时调用,其余仅当数组中的任一元素发生改变时才会调用。这相当于我们控制了组件的 update 生命周期
useEffect() 第二个数组为空则意味着仅在 componentDidMount 周期执行一次
代码仓库里使用了 Mock.js 拦截 api 请求以及 ant-design 第三 UI 方库。目前代码比较简陋。