在本文中,我想向你們介紹 React Hooks
在列表頁中的實踐,主要是經過 useReducer
和 useEffect
來實現的。以及表達一下我對 React Hooks
的理解與思考。javascript
若是你還不知道 React
的這個新特性 React Hooks
,那麼點擊 Hooks 簡介 ; 若是你想看直接查看最後的實現效果,請點擊 倉庫 。html
在平常的開發中,咱們經常會碰到一些列表頁的開發需求。java
一個列表頁的最基本的功能就是列表的展現,而列表的展現就須要不少 state
去管理,好比:列表數據、數據總數、當前頁、展現條數等等。而這些 state
絕大部分是全部列表頁的都通用的即有共同的邏輯。react
在以往的方案中尚未特別好的方案,能夠共用這些邏輯代碼。高階組件 HOC
能夠,但會引入組件層級過深的問題。(如讀者有興趣,可自行去了解高階組件的用途,本文不深刻討論)。ios
好消息是,React Hooks
就能幫助咱們完成這個心願。git
咱們要是現實一個自定義 Hook useTable
,簡單說明一下功能github
- 接收一個
url
,向外暴露{ onSearch, bind: { loading, dataSource, pagination: { current, pageSize, total }, onChange } }
。 這個是基於antd
開發的,因此bind
內的東西是綁定在antd
的Table
組件上的。- 在頁面初始化時,自動請求數據
- 在頁面卸載時,取消異步請求的後續操做
onChange
和onSearch
時自動請求數據- 在 loading 中,不會觸發新的異步請求
好了話很少說,直接上代碼。redux
// useTable.js
import { useReducer, useEffect } from 'react';
import axios from 'axios';
// action type
const DATA_CHANGE = 'DATA_CHANGE';
const STATE_CHANGE = 'STATE_CHANGE';
const DEFAULT_STATE = {
loading: false,
current: 1,
pageSize: 10,
total: 0,
order: false,
field: '',
dataSource: [],
params: {},
}
// 用做 useReducer 中的 reducer
const reducer = (state, action) => {
const { type, data: { dataSource, ...nextState } } = action;
switch (type) {
case STATE_CHANGE:
return {...state, ...nextState};
case DATA_CHANGE:
return {...state, dataSource, ...nextState};
default:
return state;
}
}
export default (url, initState = {}) => {
/** * useReducer 的概念和 redux 很像 * 會返回一個 dispatch 函數,調用的時候傳給它一個 action * 相應的會有一個 reducer 函數,用於數據處理 */
const [{
loading, // 加載態
current, // 當前頁
pageSize, // 一頁多少條
total, // 總共多少條
order, // 排序方向
field, // 排序字段
dataSource, // 數據
params, // 額外搜索項
}, dispatch] = useReducer(reducer, {
...DEFAULT_STATE,
...initState,
});
// 獲取數據的 hooks
useEffect(() => {
let cancel = false;
dispatch({ type: STATE_CHANGE, data: { loading: true } });
axios.post(
url,
{ current, pageSize, order, field, ...params },
).then(({ data, status }) => {
if (status === 200) return data;
}).then(({ data = [], total }) => {
!cancel && dispatch({ type: DATA_CHANGE, data: { dataSource: data, total }});
}).finally(() => dispatch({ type: STATE_CHANGE, data: { loading: false } }));
// 返回值時頁面卸載以後調用的函數
return () => cancel = true;
}, [url, current, pageSize, order, field, params]); // 當這幾個狀態改變時自動調用函數
// 搜索事件
function onSearch(nextParams) {
// 點擊搜索按鈕 跳到第一頁
!loading && dispatch({ type: STATE_CHANGE, data: { params: nextParams, current: 1 } });
}
// 變動事件
function onChange({ current, pageSize }, filters, { order = false, field = ''}) {
!loading && dispatch({ type: STATE_CHANGE, data: { current, pageSize, order, field }});
}
return {
onSearch,
bind: {
loading,
dataSource,
pagination: { current, pageSize, total },
onChange,
}
};
}
// UseHooksTable.js
import React, { Fragment } from 'react';
import { Table } from 'antd';
import SearchForm from './SearchForm';
import useTable from './useTable';
const url = 'https://www.easy-mock.com/mock/5cf8ead34758621a19eef994/getData';
function UseHooksTable () {
// 使用自定義 hook
const { onSearch, bind } = useTable(url);
const columns = [
{ title: '編號', dataIndex: 'id' },
{ title: '姓名', dataIndex: 'name' },
{ title: '年齡', dataIndex: 'age' },
{ title: '郵箱', dataIndex: 'email' },
{ title: '主頁', dataIndex: 'url' },
{ title: '城市', dataIndex: 'city' },
];
return (
<Fragment>
<SearchForm onSearch={onSearch}/>
<Table
rowKey={'id'}
columns={columns}
{...bind}
/>
</Fragment>
);
}
export default UseHooksTable;
複製代碼
在代碼中的註釋簡單解釋了一下代碼,應該也沒有什麼難點。axios
到此爲止,咱們應該思考 React Hooks
能夠給咱們帶來些什麼。 爲此,我額外的寫了一個使用 class
方式實現的列表頁,下面上代碼antd
import React, { Component, Fragment } from 'react';
import { Table } from 'antd';
import axios from 'axios';
import SearchForm from './SearchForm';
const url = 'https://www.easy-mock.com/mock/5cf8ead34758621a19eef994/getData';
class UseClassTable extends Component {
state = {
loading: false,
current: 1,
pageSize: 10,
total: 0,
order: 0,
field: '',
params: {
name: '',
},
dataSource: [],
}
cancel = false;
columns = [
{ title: '編號', dataIndex: 'id', sorter: true },
{ title: '姓名', dataIndex: 'name', sorter: true },
{ title: '年齡', dataIndex: 'age', sorter: true },
{ title: '郵箱', dataIndex: 'email', sorter: true },
{ title: '主頁', dataIndex: 'url', sorter: true },
{ title: '城市', dataIndex: 'city', sorter: true },
];
componentDidMount() {
this.getData();
}
componentWillUnmount() {
this.cancel = true;
}
// 搜索事件
handleSearch = (nextParams) => {
// 點擊搜索按鈕 跳到第一頁
!this.state.loading && this.setState({ params: nextParams, current: 1 }, this.getData);
}
// 變動事件
handleTableChange = ({ current, pageSize }, filters, { order = false, field = ''}) => {
!this.state.loading && this.setState({ current, pageSize, order, field }, this.getData);
}
getData() {
const { current, pageSize, order, field, params } = this.state;
this.setState({ loading: true }, () => {
axios.post(
url,
{ current, pageSize, order, field, ...params },
).then(({ data, status }) => {
if (status === 200) return data;
}).then(({ data = [], total }) => {
!this.cancel && this.setState({ dataSource: data, total });
}).finally(() => this.setState({ loading: false }));
});
}
render() {
const {
loading, // 加載態
current, // 當前頁
pageSize, // 一頁多少條
total, // 總共多少條
dataSource, // 數據
} = this.state;
return (
<Fragment>
<SearchForm onSearch={this.handleSearch}/>
<Table
rowKey={'id'}
loading={loading}
columns={this.columns}
pagination={{ current, pageSize, total }}
dataSource={dataSource}
onChange={this.handleTableChange}
/>
</Fragment>
)
}
}
export default UseClassTable;
複製代碼
咱們能夠看到使用 Hooks
的方式,咱們能夠把共有的邏輯封裝到 Hooks
中,在全部有共有邏輯的頁面都使用這樣的 hook
,代碼行數能夠從原先的80行減小到30行,代碼變得簡單易懂,使用起來也很簡單,提升的代碼的複用性。
這應該就是 Hooks
的魅力。
咱們能夠想象到,之後的社區會提供給咱們有趣的 Hooks
。
在咱們本身的開發中,也可使用 Hooks
來封裝咱們的共有邏輯,效率能夠大大提升。
並且最重要的是,Hooks
可讓咱們的代碼變得美觀。
嗯,這很重要,哈哈哈哈哈哈。