React Hooks 状态的分层设计、自定义 hook
react-hooks 是 react16.8以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。react-hooks思想和初衷,也是把组件,颗粒化,单元化,形成独立的渲染环境,减少渲染次数,优化性能。
React Hooks是React框架内的逻辑复用形式,因其轻量、易编写的形态,必然会逐渐成为一种主流。但在实际的开发中,我依然觉得大部分的开发者对hook的使用过于粗暴,缺乏设计感和复用性。
在运用状态的过程中,颗粒化太细了,比如:table 组件
const [ loading, setLoading ] = useState(false);
const [ query, setQuery ] = useState({});
const [ dataSource, setDataSource ] = useState([]);
const [ pageOptions, setPageOptions ] = useState({
page: 1,
pageSize: 10,
totalCount: 0
});
这样一但组件状态过多,就很难读懂组件的状态对应了,其实 React Hooks 的初终并不希望这样,弥补了无状态组件,由于用法过于粗暴,增加代码可读性。而是提高代码的可复用性、逻辑性,减少渲染次数,优化性能。
笔者认为在项目过程中应该用 Hooks Api 封装符合自己项目,自己业务的各类 Hooks。不是适用,像上面的用法,虽然达到的某种目的,相对 class 类组件,并没有多大改进,代码的可读性反而变差了。所以 React Hooks 正确运用姿势,是运用基状态的分层设计,创造属性自己轮子。废话不多说,下面我们一起来就 table 组件,封装自己的自定义 hooks,轮子。
自定义useTable
设计思路
- 我们需要
state
来保存列表数据,总页码数,当前页面,loading 等信息 - 需要暴露一个方法用于,改变分页数据,从新请求数据
- useTable
import { useState, useEffect, useMemo } from 'react';
import { Table, Pagination } from 'antd';
function useTable(query, callback) {
const [ loading, setLoading ] = useState(false)
const firstRequest = useRef(false);
const [ pageOptions, setPageOptions ] = useState({
page: 1,
pageSize: 10
});
const [ tableData, setTableData ] = useState([]);
const getList = useMemo(() => {
return async payload => {
if (!callback) return;
setLoading(true);
const data = await callback(payload || {...query, ...pageOptions})
if (data.code === 0) {
setTableData(data.data);
firstRequest.current = true;
}
setLoading(false);
}
}, []);
useEffect(() => {
firstRequest.current && getList({
...query,
...pageOptions
})
}, [pageOptions]);
useEffect(() => {
getList({
...query,
...pageOptions,
page: 1
})
}, [query]);
const handerChange = useMemo(() => (options) => setPageOptions({...options}), []);
return [loading, tableData, handerChange, getList]
}
export default useTable;
使用方法:
import { useState } from 'react';
import useTable from './useTable';
import { Table, Pagination } from 'antd';
function CustomHook() {
const jsonToString = (obj) => {
let arr = [];
for(let key in obj){
arr.push(key + '=' + obj[key]);
}
return arr.join('&');
}
const getTableData = (payload) => {
const query = jsonToString(payload);
return fetch('http://127.0.0.1:3001/getList?' + query).then(res => res.json());
}
const [ query, setQuery ] = useState({});
const [ loading, tableData, handerChange ] = useTable(query, getTableData);
const { page, pageSize, totalCount, dataSource } = tableData;
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '住址',
dataIndex: 'address',
key: 'address',
},
];
return (
<>
<Table
dataSource={dataSource}
columns={columns}
rowKey="id"
loading={loading}
pagination={false} />
<Pagination
defaultCurrent={page}
total={totalCount}
style={{float: 'right', marginTop: '20px'}}
onChange={(page, pageSize) => handerChange({ page, pageSize })}
onShowSizeChange={(current, size) => handerChange({ page: current, pageSize: size }) } />
</>
)
}
export default CustomHook;
- useThrottle 节流
import { useState, useRef, useEffect } from 'react';
/**
* @desc 节流
* @param {*} fn 回调函数
* @param {*} ms 时间
* @param {*} deps 副作用,设置更新触发
*/
const useThrottle = (fn, ms = 30, deps = []) => {
const previous = useRef(0);
const [ time, setTime ] = useState(ms);
useEffect(() => {
const now = Date.now();
if (now - previous.current > time) {
fn();
previous.current = now;
}
}, deps);
const cancel = () => {
setTime(0);
}
return [cancel];
}
export default useThrottle;
运用场景
import React, { useState } from 'react';
import useThrottle from '../useHooks/useThrottle';
const CustomInput = () => {
const [ value, setValue ] = useState('');
const handlerChange = (e) => {
console.log(1, value)
}
const [ cancel ] = useThrottle(handlerChange, 50, [value])
return (
<input type="text" value={value} onChange={(e) => setValue(e.target.value)}/>
)
}
export default CustomInput;
- useDebounce 防抖
import { useRef, useEffect } from 'react';
/**
* 防抖
* @param {*} fn 回调函数
* @param {*} ms 时间
* @param {*} deps 副作用,设置更新触发
* @returns
*/
const useDebounce = (fn, ms = 30, deps = []) => {
const timeout = useRef(0);
useEffect(() => {
if (timeout.current) {
clearTimeout(timeout.current);
}
timeout.current = setTimeout(() => {
fn();
}, ms);
}, deps)
const cancel = () => {
if (timeout.current) {
clearTimeout(timeout.current);
}
}
return [cancel]
}
export default useDebounce;
运用场景
import React, { useState } from 'react';
import useDebounce from '../useHooks/useDebounce';
const CustomInput = () => {
const [ value, setValue ] = useState('');
const handlerChange = (e) => {
console.log(1, value)
}
const [ cancel ] = useDebounce(handlerChange, 50, [value])
return (
<input type="text" value={value} onChange={(e) => setValue(e.target.value)}/>
)
}
export default CustomInput;
更新中…
原文:https://www.ifrontend.net/2021/06/react-hooks/