Background
React在16.7-alpha版本中新增了React的一些新特性卡辰,如Hook尔崔。
在這里主要想分享一下關(guān)于Hook使用
About Hook
- useState() 讓你定義一個state變量
- useEffect() 讓你定義一個副作用
- useContext() 讓你讀取某些Context
PS: 了解上面三個 api 就可以對Hook進行基本的使用了贮乳。 類似于官方的聲明可以直接去官網(wǎng)查看果港。下面直接進入實戰(zhàn)模式。
Practice
這里我們先說明我們要做的是什么, 我們在這里最終目的是要做一個由Hook制作的一個異步Table組件费奸,Table使用的是 **Ant-design **里封裝好的組件弥激, 而cli用 **create-react-app **官方自帶吧。因為側(cè)重 hook 所以我們不會涉及到redux之類的愿阐,所以也不必要徒增不必要的代碼量微服。 then let's do it。
Mock Data
第一步缨历,首先我們要構(gòu)造后臺的數(shù)據(jù)以蕴,或者自己找一個可以進行mock的數(shù)據(jù)就可以。這個盡可能模仿后臺辛孵,我這里使用的就用 ant-design-pro 提供的分頁丛肮。https://preview.pro.ant.design/api/rule?currentPage=1&pageSize=10 可以在postman搜索即可 ,瀏覽器打開不正常魄缚。估計是做了額外的處理宝与。但是有個問題就是跨域問題解決不了,我這邊基于easy-mock又做了一個 接口冶匹, 理論上兩個行為是一致的习劫。
主要后臺和前端配合下 前端傳 當(dāng)前頁碼 (currentPage) 和 展示數(shù)量 (pageSize) 即可。 后臺返回需要有 總頁數(shù)嚼隘。 因為我們要做如下這個情況就必須得要有總頁數(shù)诽里。
Like this. ↓
?
?
anyway. 數(shù)據(jù)有了就可以下一步了。
Font-end
構(gòu)建目錄
create-react-app 構(gòu)建出基本的react項目 這個可以看官方文檔
對package.json進行修改飞蛹,把 react , react-dom 兩個修改成 v16.8.0-alpha.1谤狡, 然后重新安裝
這里還需要把 antd 加上 畢竟我們也要用到他們的Table組件
還有一個請求這里直接用原生的fetch就可以了
貼一下目前的package.json
?
代碼塊
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"antd": "^3.13.0",
"react": "v16.8.0-alpha.1",
"react-dom": "v16.8.0-alpha.1",
"react-scripts": "2.1.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
?
接下來我們刪除目前不需要用到的頁面,并且把 server-worker 去掉卧檐, 最后簡化到如下即可墓懂。 代碼可在github上預(yù)覽。
接下來第一次嘗試
異步獲取數(shù)據(jù)并且渲染到頁面
首先這里有一個關(guān)鍵的問題 泄隔, **異步 **在一開始拒贱,我們就得明確hook中使用帶有副作用的都得使用 useEffect() 進行定義, 異步也是副作用的一種佛嬉。因此逻澳,我們獲取數(shù)據(jù)的時候應(yīng)該是使用 useEffect() 代碼走一個
yap,我們可以得到我們想要的數(shù)據(jù)了暖呕!
?
?
解釋一下這里斜做, useEffect() 這個非常強大, 哈哈哈湾揽。 不愧是在React發(fā)布會上Ryan特別點了一下瓤逼,真好用笼吟。哈哈
這里主要是容納了 componentDidMount 和 componentDidUpdate return 的時候就是 組件 umount的時機, 因此 subscribe在useEffect中霸旗,但return的時候就應(yīng)該把他們給 unsubscribe贷帮。
Ok. just fine.
下一步。
我們已經(jīng)知道如何得到數(shù)據(jù) 诱告, 那么我們把它塞到我們的Table組件中撵枢。試一下
失敗
?
?
這里錯誤的原因很簡單,因為數(shù)據(jù)在更新之后并沒有處罰需要 re-render 的條件精居,因此我們加上 useState()
?
?
數(shù)據(jù)請求成功锄禽。并且渲染成功! but靴姿,這里有一個問題沃但。為什么我們會有這么多條請求?如果不報錯甚至?xí)霈F(xiàn)死循環(huán)佛吓? 因為 調(diào)用的位置 出現(xiàn)問題了宵晚! 想想我們以前寫的react, 只有一次的觸發(fā)時機辈毯。 那就是 componentWillMount 或者 componentDidMount 那么 坝疼,這個位置在哪里呢搜贤?
useEffect() 就因此而來谆沃。
?
?
沒有任何問題! 但是仪芒,這里我多做了一步唁影,就是 給useEffect添加了第二個參數(shù)
useEffect() 的第二個參數(shù)就是專門做數(shù)據(jù)對比,它的入?yún)⑹且粋€變量掂名。只有在變量指向的數(shù)據(jù)進行修改后据沈,進行對比發(fā)現(xiàn)不一致時,才會重新進行渲染饺蔑。
好的锌介,現(xiàn)在才是正題啊猾警!
封裝異步Table組件
定義入?yún)⒎椒?/h5>
展示的columns, 請求后臺api, 傳后臺參數(shù), 后臺返回的數(shù)組的字鍵名稱
function asyncTableList(columns, queryAction, params, cgiKey) {
// do something
}
整套代碼如下
?
代碼塊
/**
* 異步table
* 組件使用方法
*
* import renderAsyncTable from './this.file'
*
* const asyncTable = renderAsyncTable(展示的columns, 請求后臺api, 傳后臺參數(shù), 后臺返回的數(shù)組的字鍵名稱)
*
* function App() {
* const [query] = useState()
* const columns = [...]
* const asyncTable = renderAsyncTable(columns, api, query, listName )
* return (
* <div>
* {asyncTable}
* </div>
* )
* }
*
*/
import React, { useEffect, useReducer } from 'react'
import Table from 'antd/lib/table';
function useAsyncTable (columns, queryAction, params, listName) {
const paginationInitial = {
current: 1,
pageSize: 10
}
const [state, dispatch] = useReducer(
(state, action) => {
const { payload } = action
switch (action.type) {
case 'TOGGLE_LOADING':
return { ...state, loading: !state.loading }
case 'SET_QUERY':
return {
...state,
query: payload.params,
pagination: paginationInitial
}
case 'SET_PAGINATION':
return { ...state, pagination: payload.pagination }
case 'SET_DATA_SOURCE':
return { ...state, dataSource: payload.dataSource }
default:
return state
}
},
{
loading: false,
query: null,
pagination: paginationInitial,
dataSource: []
}
)
function handleTableChange (event) {
if (event) {
const { current } = event
dispatch({
type: 'SET_PAGINATION',
payload: {
pagination: {
...state.pagination,
current
}
}
})
}
}
function doQuery () {
dispatch({
type: 'TOGGLE_LOADING'
})
const { current, pageSize } = state.pagination
const pagination = {
current,
pageSize
}
queryAction({
...state.query,
...pagination
})
.catch(err => {
dispatch({
type: 'TOGGLE_LOADING'
})
return {}
})
.then((payload) => {
if(payload.pagination && payload.list) {
const { pagination: { total } } = payload
console.log('total', total)
dispatch({
type: 'TOGGLE_LOADING'
})
if (!state.pagination.total) {
dispatch({
type: 'SET_PAGINATION',
payload: {
pagination: { ...state.pagination, total }
}
})
}
dispatch({
type: 'SET_DATA_SOURCE',
payload: {
dataSource: payload[listName]
}
})
}
})
}
// cDM cDU
useEffect(
() => {
if (params && JSON.stringify(params) !== JSON.stringify(state.query)) {
dispatch({
type: 'SET_QUERY',
payload: {
params
}
})
} else {
doQuery()
}
},
[params, state.pagination.current, state.query]
)
return (
<Table
columns={columns}
rowKey={record => record.key}
pagination={state.pagination}
dataSource={state.dataSource}
loading={state.loading}
onChange={handleTableChange}
/>
)
}
export default useAsyncTable
?
useReducer
這里解釋一下:
在React官網(wǎng)的Hook中也有用到useReducer這種方法孔祸! 怎么說呢? 用hook的方式寫一個redux吧发皿。 用法和redux一樣崔慧。state只能被dispatch的數(shù)據(jù)進行修改。 dispatch用type來區(qū)分修改state里的哪個數(shù)據(jù)穴墅,然后 我這邊用payload 帶數(shù)據(jù)進去惶室。修改 state 內(nèi)部數(shù)據(jù)温自。
每次做diff的之后 只拿 透傳過來的 params 和 存在reducer里的 query做對比,如果有修改就進行修改皇钞。
沒有的話就是調(diào)用請求悼泌。
這里調(diào)用請求可以理解為 :
- 只有在 current 頁面不一致的時候調(diào)用
- 只有在query更改過后進行修改
而params在這里只是作為一個觸發(fā)修改query的操作,并不會直接進行請求夹界。
調(diào)用頁面
調(diào)用相對簡單券躁。 我們需要傳入的參數(shù)只有4個
- columns的展示
- 異步拉取的接口
- 其他query, 這個query可能需要解析一下掉盅, 因為不是所有的異步拉取只有 pageSize 和 current 兩個參數(shù)也拜。 它們還有可能是 cityId 為 10000 或者 name是小明 也有可能是集合 如 城市A區(qū),名字小白趾痘。這樣慢哈,我們異步在使用異步拉取的接口 就可以不單單的延申為 pageSize 和 current這么單調(diào)。 它也可以combine參數(shù)后得到更好的拓展永票。
- listName卵贱。 這個主要是為了不同的后臺,有可能它是比較泛的名稱如 lists 或者 beanList 等等侣集,但也有可能是 cities键俱,topics。 因此也用透傳的方式世分。 更加強壯编振。
?
代碼塊
import React, {useState} from 'react';
import renderAsyncTable from './asyncTable'
import getAjax from './utils/ajax'
function App() {
const [query] = useState()
const columns = [
{
title: '規(guī)則名稱',
dataIndex: 'name',
},
{
title: '描述',
dataIndex: 'desc',
},
{
title: '服務(wù)調(diào)用次數(shù)',
dataIndex: 'callNo',
render: val => `${val} 萬`,
// mark to display a total number
needTotal: true,
},
{
title: '上次調(diào)度時間',
dataIndex: 'updatedAt',
},
];
const asyncTable = renderAsyncTable(columns, (query) => getAjax('o2po/data', query), query, 'list' )
return (
<div style={{width: 600, margin: '30px auto'}}>
{asyncTable}
</div>
)
}
export default App;
?
整套流程就可以了。
?
?github:
https://github.com/panmouren/asyncTableList
歡迎start臭埋,如果有什么問題歡迎指正踪央。! 指正比start更重要瓢阴。 語雀https://www.yuque.com/zibuyu/fed/ognwv0#Background