赞
踩
ahooks
一套高质量可靠的 React Hooks 库 //引入
npm install --save ahooks
或 yarn add ahooks
//使用
import { useRequest } from 'ahooks'
useRequest
是一个强大的异步数据管理
的 Hooks,React 项目中的网络请求场景
使用 useRequest就够了
useRequest
通过插件式组织代码,核心代码极其简单,并且可以很方便的扩展出更高级的功能。目前已有能力包括:
useRequest
的第一个参数是一个异步函数,在组件初次加载
时,会自动触发该函数执行
。同时自动管理该异步函数的 loading
, data
, error
等状态。
useRequest
的第二个参数可以为 options.manual = true
,若设置了这个参数,则 useRequest不会默认触发,需要通过run触发
data:service返回的数据
error:service抛出的异常
loading:service是否在执行
run:手动触发useRequest执行,参数会传递给service
params : 当次执行的 service 的参数数组。比如你触发了 run(1, 2, 3,则 params 等于 [1, 2, 3]
//第一个参数service是异步函数
const { data, error, loading,run } = useRequest(service, {
manual: true//若设置了这个参数,则不会默认触发,需要通过run触发
defaulParams:[]//首次默认执行时,传递给 service 的参数
});
useRequest
提供了以下几个生命周期配置项,供你在异步函数的不同阶段做一些处理。
onBefore
:请求之前触发onSuccess
:请求成功触发onError
:请求失败触发onFinally
:请求完成触发案例
//按需引入antd组件库 import { message } from 'antd'; import React, { useState } from 'react'; import { useRequest } from 'ahooks'; //异步函数 function editUsername(username: string): Promise<void> { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { resolve(); } else { reject(new Error('Failed to modify username')); } }, 1000); }); } export default () => { //添加输入框的内容状态 const [state, setState] = useState(''); const { loading, run } = useRequest(editUsername, { //手动触发 manual: true, //四个声明周期函数 onBefore: (params) => { message.info(`Start Request: ${params[0]}`); }, onSuccess: (result, params) => { setState(''); message.success(`The username was changed to "${params[0]}" !`); }, onError: (error) => { message.error(error.message); }, onFinally: (params, result, error) => { message.info(`Request finish`); }, }); return ( <div> <input //输入框发生改变时,将状态state设置为e.target.value onChange={(e) => setState(e.target.value)} //输入框的内容设置为state value={state} placeholder="Please enter username" style={{ width: 240, marginRight: 16 }} /> //手动触发useRequest <button disabled={loading} type="button" onClick={() => run(state)}> {loading ? 'Loading' : 'Edit'} </button> </div> ); };
API
const { //service是否正在执行 loading: boolean, //service返回的数据 data?: TData, //service抛出的异常 error?: Error, //当次执行的service的参数数组。比如你触发了run(1, 2, 3),则params等于[1, 2, 3] params: TParams || [], //手动触发service执行,参数会传递给service异常自动处理,通过onError反馈 run: (...params: TParams) => void, //与run用法一致,但返回的是Promise,需要自行处理异常。 runAsync: (...params: TParams) => Promise<TData>, //使用上一次的 params,重新调用 run refresh: () => void, //使用上一次的params,重新调用runAsync refreshAsync: () => Promise<TData>, //直接修改data mutate: (data?: TData | ((oldData?: TData) => (TData | undefined))) => void, //取消当前正在进行的请求 cancel: () => void, } = useRequest<TData, TParams>( //第一个参数为异步函数 service: (...args: TParams) => Promise<TData>, { //第二个参数 //如果设置为 true,则需要手动调用 run 或 runAsync 触发执行。 manual?: boolean, //首次默认执行时,传递给service的参数 defaultParams?: TParams, //service执行前触发 onBefore?: (params: TParams) => void, //service resolve时触发 onSuccess?: (data: TData, params: TParams) => void, //service reject时触发 onError?: (e: Error, params: TParams) => void, //service 执行完成时触发 onFinally?: (params: TParams, data?: TData, e?: Error) => void, } );
useRequest还有很多功能具体请参照官网
useAntdTable
: 基于 useRequest
实现,封装了 Ant Design UI组件库中Table与Form联动逻辑。
const { tableProps, search, loading } = useAntdTable(
async ({ current: page, pageSize }, formData) => {
const data = await getRoleList({ page, pageSize, ...formData })
return {
//data包含表格数据data和分页数据(page(当前页) pageSize(每页条数) toatl(数据总数) totalPage(页数总数))
...data,
list: data?.data
}
},
{
form
}
)
// tableProps包含 dataSource: data?.data 和 pagination: {current: 1, pageSize: 10, total: }
第一个参数为异步函数
,它接收两个参数 :分页数据{ current, pageSize, sorter, filters }
,其中sorter,ilters用于表格分页、分类筛选等功能和表单数据(formData自定义)
函数自动触发前会自动收集表单框中的数据,带入到接口入参中,格式{key:value} ; 返回的数据结构为 { total: number, list: Item[] }
。第二个参数一般为form(挂载Form实例)
,也可以增加默认参数或refreshDeps(参数格式为数组[ ]) ,refreshDeps 变化,会重置 current 到第一页,并重新发起请求。tableProps
和 search
字段,管理表格和表单。loading
:异步函数(获取数据的过程)是否在执行,执行中为true,执行结束为falseTable 管理
useAntdTable
会自动管理 Table
分页数据,你只需要把返回的 tableProps
传递给 Table
组件就可以了
<Table columns={columns} rowKey="email" {...tableProps} />
useAntdTable 如何和表单形成关联关系?
const form =Form.useForm( ) 创建 Form 实例,用于管理所有数据状态
//创建的form实例要给所需Form表单用
<Form form={form} ></Form>
useAntdTable的第二个参数挂载form实例
Form 与 Table 联动
useAntdTable
接收 form
实例后,会返回 search 对象,用来处理表单相关事件。
search.type
支持 simple
和 advance
两个表单切换search.changeType
,切换表单类型search.submit
提交表单行为search.reset
重置当前表单初始化数据
useAntdTable
通过 defaultParams
设置初始化值,defaultParams
是一个数组,第一项为分页相关参数,第二项为表单相关数据。如果有第二个值,我们会帮您初始化表单!
表单验证
表单提交之前,我们会调用 form.validateFields
来校验表单数据,如果验证不通过,则不会发起请求。
API
useRequest
所有参数和返回结果均适用于 useAntdTable
,此处不再赘述。
type Data = { total: number; list: any[] }; type Params = [{ current: number; pageSize: number, filter?: any, sorter?: any }, { [key: string]: any }]; //useAntdTable会返回tableProps和search字段,用于管理表格和表单 const { ..., //tableProp管理表格 Table组件需要的数据,直接透传给Table组件即可 tableProps: { //数据源 dataSource: any[]; //service是否在执行 loading: boolean; //表格发生改变执行操作 onChange: ( pagination: any, filters?: any, sorter?: any, ) => void; //分页数据 pagination: { //当前页 current: number; //每页显示的数据 pageSize: number; //总数据 total: number; }; }; //search管理表单 search: { //当前表单类型 type: 'simple' | 'advance'; //切换表单类型 changeType: () => void; //提交表单 submit: () => void; //重置当前表单 reset: () => void; }; } = useAntdTable<TData extends Data, TParams extends Params>( //异步函数 //service接收两个参数,第一个参数为分页数据 { current, pageSize, sorter, filters },第二个参数为表单数据。 //service 返回的数据结构为 { total: number, list: Item[] }。 service: (...args: TParams) => Promise<TData>, { ..., //接受的第二个参数 //Form实例 form?: any; //默认表单类型 defaultType?: 'simple' | 'advance'; //默认参数,第一项为分页数据,第二项为表单数据 // defaultParams: [ // { current: 2, pageSize: 5 }, // { name: 'hello', email: 'abc@gmail.com', gender: 'female' } // ], defaultParams?: TParams, //默认分页数量 defaultPageSize?: numbe; //refreshDeps 变化,会重置 current 到第一页,并重新发起请求。 refreshDeps?: any[]; } );
useBoolean
: 优雅的管理 boolean 状态的 Hook。
//state状态值和actions操作集合
//Actions toggle为切换state,set设置state,setTrue设置为true,setFals设置为false
const [ state, { toggle, set, setTrue, setFalse }] = useBoolean(
//传入的参数可选项,传入默认的状态值
defaultValue?: boolean
);
持久化 function 的 Hook,理论上,可以使用 useMemoizedFn
完全代替 useCallback。
const memoizedFn = useMemoizedFn(fn);
参数
:fn回调函数,引用地址永远不会变化的回调函数返回值
:memoizedFn缓存版本的回调函数const [name, setName] = useState('xiaoMing')
const [age, seAge] = useState(20)
// useCallback
// 在 state 变化时,memoFn 地址会变化
const memoFn = useCallback(() => {
console.log('name', name, 'age', age)
}, [name, age])
// useMemoizedFn
// memoFn 地址永远不会变化
const memoFn = useMemoizedFn(() => {
console.log('name', name, 'age', age)
})
useMemoizedFn的实现
const callbackFn = useCallback(() => {
console.log(`Current count is ${count}`);
}, [count]);
<ExpensiveTree showCount={callbackFn} />
在上面的代码中,callbackFn 的 dep 必须包含 count
,保证它被调用时能输出正确的 count,而不是错误的闭包值。但是这样的话,每次 count 发生变化时,callbackFn 本身的引用会变化,会触发依赖 callbackFn 的 ExpensiveTree 组件 重新render。在 ExpensiveTree 角度来看,其实这是一次多余的 render。
实际上,如果我们找到一种方法解决上面所说的问题,就实现了 useMemoizedFn
这个 hook,我们来看看需要解决的问题有哪些
接下来开始解决这些问题,如下:
function useMemoizedFn(fn) {
// 这里可以拿到每次最新的 fn,并把它更新到 ref 中,这可以保证此 ref 能够持有最新的 fn 引用
const latestFn = useRef(fn);
latestFn.current = fn;
// 我们通过这个只初始化一次的 useRef 来构建一个函数调用外壳,保证这个外壳函数的引用不会发生变化
// 并且通过在内部持有最新函数的引用,来保证调用准确性
const memoizedFn = useRef((...args) => {
latestFn.current?.(...args);
});
return memoizedFn.current;
}
到这里,我们已经实现了 useMemoizedFn
的所有功能,简单来说,这个 hook
做的事情就是实时的维护函数的最新引用,并在适当的时候通过一个包装函数来调用它。
通过 url query 来管理 state 的 Hook。
npm i @ahooksjs/use-url-state -S
该 Hooks 基于 react-router 的 useLocation & useHistory & useNavigate 进行 query 管理,一般获取路由参数使用该hook更加简便
使用该 Hooks 之前,你需要保证
你项目正在使用 react-router 5.x 或 6.x 版本来管理路由
独立安装了 @ahooksjs/use-url-state
import useUrlState from '@ahooksjs/use-url-state';
const [state, setState] = useUrlState(initialState, options);
举个例子:
向路由组件传递search参数:
路由链接(携带参数):<Link to=‘/demo/test?name=tom&age=18’}>详情
接收参数:
const {search,setSearch} =useSearchParams()
const name =search.get('name')
const id =search.get('id')
使用useUrlState
const [query, setQuery] = useUrlState(props, { navigateMode: ‘replace’ });
query就直接可以取出name和id
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。