Hooks从诞生至今已经有了长足的进步,不得不说hooks的出现是React的一个标志性里程碑。用hooks来解决开发中的问题大大提升了我们日常工作的效率,本文是针对异步请求数据的一个案例,目前只针对useReducer、useContext、useEffect进行探讨,同时还需要使用过Redux进行过异步action添加。
微信前端核心500人群:群内不定期会有赞助商送书活动,BAT大厂资深大牛定期推送面经与源码分析,各平台大牛优秀文章推荐,更有内推跳槽咨询、视频资源共享、学习资料文章pdf面经网盘资源等等福利。加入我们一起进步。
为了解决知乎活码识别问题,下方的二维码做了持久化处理。扫描二维码添加小柠即可加入我们。
公众号:中台架构之家
群内分享每日一题:题目传送门
前端电子书大全:电子书
每日7点贝壳网P7大牛免费基础公开课开讲中
非Hooks实现方案
在React的生命周期中(初学请参考:安大虎:使用setState对数据进行同步、异步操作),我们知道componentDidMount里我们最适合去进行异步请求,因此在不需要考虑状态管理时,这种方法很简单:
class App extends React.Component{
componentDidMount(){
axios.get('/your/api')
.then(res=>/*...*/)
}
}
随后引入Redux进行状态管理
当你决定使用Redux
进行状态管理时,比如将异步获取到的数据储存在store
中,事情就开始复杂起来了。根据Redux
的官方文档案例来看,为了实现异步action
,你还得需要一个类似于redux-thunk、redux-saga的第三方库来解析你的异步action
。
(没用过redux可以参考:安大虎:React-Redux实现无关联组件props传值)
Action.js: 定义异步请求action的地方
//这是一个异步action,分发了两个同步action,redux-thunk能够理解它
const fetchGoodsList = url => dispatch => {
dispatch(requestGoodsList());
axios.get(url)
.then(res=>{
dispatch(receiveGoodsList(res.data))
})
};
Reducer.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>
)
}
}
Component :进行异步请求的组件
class *** extends React.Component{
//...
componentDidMount(){
this.props.url('your/url');
}
//...
}
const mapDispatchToProps={
fetchGoodsList
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(GoodsList);
使用Hooks
-useReducer()
和useContext()
使用Redux
很累,当然,你可以不使用Redux,直接通过props
层层传递,或者使用context
都可以。既然出了useReducer
,使用到了Redux
的思想,总要试着用一下。再麻烦也不会比redux麻烦了(手动狗头)
优点:
1、这里你不需要引入别的任何第三方库了,简简单单地使用React@16.8.0
以上的版本就好啦
2、很重要的一点就是——函数式组件,现在React推荐我们这么做,可以基本上代替class
写法。
useReducer(reducer,initialState)
useContext(ctxObj)
useEffect(effectFunction,[dependencyValues])
action.js:
-
- 我们还使用
redux
的思想,编写action
- 我们还使用
reducer.js:
-
- 处理action,不同于
redux
的reducer
,这里我们可以不用提供初始状态
- 处理action,不同于
根组件:
-
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的情况下可以这样写)
export const EditContext = React.createContext({});
// useReducer与useContext配合注入新的modal显示状态的控制信息**************************************
const initialVisiable = false;
const reducer = (visible: boolean, action: string) => {
switch (action) {
case 'show':
return Boolean(1);
case 'hide':
return Boolean(0);
default:
return visible;
}
};
之后需要在根组件的context()中传入相应参数,使得子组件可以获取到相应数据。
const DataListManage: React.FC = props => {
const dataManageList = useTestManageList();
//reducer
const [count, dispatch] = useReducer(reducer, initialVisiable);
return (
<PageHeaderLayout>
<Card>
<EditContext.Provider
value={{
visiState: count,
visiDispatch: dispatch
}}
>
<DataManageTable
{...props}
loading={dataManageList.loading}
data={dataManageList.data}
/>
</EditContext.Provider>
</Card>
</PageHeaderLayout>
);
};
4、子组件
注意只有被
<***Context.Provider>包裹住的元素才能进行值与函数的传递
const onlyForSearch = value => {
setSearchList(value);
};
<tableContext.Provider
value={{
tabledata: dataset,
setDataFunc: changeTableData,
searchProps: searchState,
controlSearchProps: throwStateToContral,
searchList: searchList,
onlyForSearchResult: onlyForSearch
}}
>
<SearchBoxinTable />
...
5、子组件的子组件
在其中进行声明
const searchDataSet = useContext(tableContext);
...
// console.log(searchDataSet);
searchDataSet.setDataFunc(searchResult.data.data.list);
searchDataSet.controlSearchProps(true);
searchDataSet.onlyForSearchResult({
需要注意的是:
1、无论在哪一层级进行context的声明,最终都可以通过provider进行向下的数据传递。
2、context可以进行函数的传递,传递的参数在接收的子元素中进行数据传递回父组件,父组件接收到数据即可进行setState动态数据变更,促使组件render
- 使用
useContext()
时候我们不需要使用Consumer
了。但不要忘记export
和import
上下文对象 useEffect()
可以看做是class
写法的componentDidMount
、componentDidUpdate
以及componentWillUnMount
三个钩子函数的组合。
当返回了一个函数的时候,这个函数就在compnentWillUnMount
生命周期调用。默认地,传给useEffect的第一个参数会在每次(包含第一次)数据更新时重新调用。- 当给
useEffect()
传入了第二个参数(数组类型)的时候,effect函数会在第一次渲染时调用,其余仅当数组中的任一元素发生改变时才会调用。这相当于我们控制了组件的update
生命周期 useEffect()
第二个数组为空则意味着仅在componentDidMount
周期执行一次
3、不要在一个文件中provider提供完数据后,马上useContext消费数据,可能会取不到值。
Happy Hacking~~