ReactReact全家桶ahooks
Posted 前端More
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReactReact全家桶ahooks相关的知识,希望对你有一定的参考价值。
文章目录
1 ahooks简介
1.1 什么是ahooks?
ahooks
一套高质量可靠的 React Hooks 库- React 虽然提供基础的 hooks 函数,但在实际场景中,我们需要在基础函数上再封装 , ahooks 就满足了这个需求, 它的功能非常强大,拥有的 hooks 已经 60+ 。
- ahooks官方文档
1.2 ahooks的特点
- 易学易用
- 支持 SSR( 服务器端渲染 )
- React Hooks 在 SSR 场景下,普遍会碰到“DOM/BOM 缺失”、“useLayoutEffect 警告”两个问题。ahooks v3.0 彻底解决了这两个问题,你可以安心的将 ahooks 使用到 SSR 场景了。
- 对输入输出函数做了特殊处理,避免闭包问题
- 所有用户输入的函数,永远使用最新的一份( 通过useRef 进行实现 )
- ahooks 所有的输出函数,引用地址都是不会变化的( 通过useMemoizedFn(ahooks 封装的)实现的,其实现也是通过 useRef实现 )
- 包含大量提炼自业务的高级 Hooks
- 包含丰富的基础 Hooks
- 使用 TypeScript 构建,提供完整的类型定义文件
1.3 ahooks如何使用?
//引入
npm install --save ahooks
或 yarn add ahooks
//使用
import useRequest from 'ahooks'
2 ahooks API
2.1 useRequest
useRequest
是一个强大的异步数据管理
的 Hooks,React 项目中的网络请求场景
使用 useRequest就够了
useRequest
通过插件式组织代码,核心代码极其简单,并且可以很方便的扩展出更高级的功能。目前已有能力包括:
- 自动请求/手动请求
- 轮询
- 防抖
- 节流
- 屏幕聚焦重新请求
- 错误重试
- loading delay
- SWR(stale-while-revalidate)
- 缓存
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还有很多功能具体请参照官网
2.2 useAntdTable
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:
- useAntdTable
第一个参数为异步函数
,它接收两个参数 :分页数据 current, pageSize, sorter, filters
,其中sorter,ilters用于表格分页、分类筛选等功能和表单数据(formData自定义)
函数自动触发前会自动收集表单框中的数据,带入到接口入参中,格式key:value ; 返回的数据结构为total: number, list: Item[]
。 - useAntdTable的
第二个参数一般为form(挂载Form实例)
,也可以增加默认参数或refreshDeps(参数格式为数组[ ]) ,refreshDeps 变化,会重置 current 到第一页,并重新发起请求。 - useAntdTable 返回
tableProps
和search
字段,管理表格和表单。loading
:异步函数(获取数据的过程)是否在执行,执行中为true,执行结束为false
Table 管理
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[];
);
2.3 useBoolean
useBoolean
: 优雅的管理 boolean 状态的 Hook。
//state状态值和actions操作集合
//Actions toggle为切换state,set设置state,setTrue设置为true,setFals设置为false
const [ state, toggle, set, setTrue, setFalse ] = useBoolean(
//传入的参数可选项,传入默认的状态值
defaultValue?: boolean
);
2.4 useMemoizedFn
持久化 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)
)
- 在某些场景中,我们需要使用 useCallback 来记住一个函数,但是在第二个参数 deps 变化时,会重新生成函数,导致函数地址变化
- 使用 useMemoizedFn,可以省略第二个参数 deps,同时保证函数地址永远不会变化,可以用于性能优化。
- useMemoizedFn解决了由 hook 中的 deps 引起的闭包问题,同时保证了函数调用的准确性,实时性,保证函数调用可以拿到最新的值。
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,我们来看看需要解决的问题有哪些
- callbackFn 的地址不能随 render 改变
- 要同时保证 count 的实时更新
- 并且 callbackFn 的引用地址不能变
- 不需要添加 dep 依赖
接下来开始解决这些问题,如下:
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
做的事情就是实时的维护函数的最新引用,并在适当的时候通过一个包装函数来调用它。
2.5 useUrlState
通过 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);
- initialState:初始状态
- options:url配置
navigateMode:状态变更时切换 history 的方法,默认push
parseOptions:query-string parse 的配置
stringifyOptions:query-string stringify 的配置 - state:url query 对象
- setState:用法同 useState,但 state 需要是 object
举个例子:
向路由组件传递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
以上是关于ReactReact全家桶ahooks的主要内容,如果未能解决你的问题,请参考以下文章