本文是學習了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
,這裡我們可以不用提供初始狀態
- 處理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
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方庫。目前程式碼比較簡陋。