当前位置:   article > 正文

【React】React全家桶(十一)ahooks

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'
  • 1
  • 2
  • 3
  • 4
  • 5

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 的参数
});
  • 1
  • 2
  • 3
  • 4
  • 5

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>
  );
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

在这里插入图片描述

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,
    }
  );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

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: }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • useAntdTable第一个参数为异步函数,它接收两个参数 :分页数据{ current, pageSize, sorter, filters },其中sorter,ilters用于表格分页、分类筛选等功能和表单数据(formData自定义)函数自动触发前会自动收集表单框中的数据,带入到接口入参中,格式{key:value} ; 返回的数据结构为 { total: number, list: Item[] }
  • useAntdTable的第二个参数一般为form(挂载Form实例),也可以增加默认参数或refreshDeps(参数格式为数组[ ]) ,refreshDeps 变化,会重置 current 到第一页,并重新发起请求。
  • useAntdTable 返回tablePropssearch 字段,管理表格和表单。loading:异步函数(获取数据的过程)是否在执行,执行中为true,执行结束为false

Table 管理

useAntdTable 会自动管理 Table 分页数据,你只需要把返回的 tableProps 传递给 Table 组件就可以了

<Table columns={columns} rowKey="email" {...tableProps} />
  • 1

useAntdTable 如何和表单形成关联关系?

  • const form =Form.useForm( ) 创建 Form 实例,用于管理所有数据状态

    //创建的form实例要给所需Form表单用 
    <Form form={form} ></Form>
    
    • 1
    • 2
  • useAntdTable的第二个参数挂载form实例

Form 与 Table 联动

useAntdTable 接收 form 实例后,会返回 search 对象,用来处理表单相关事件。

  • search.type 支持 simpleadvance 两个表单切换
  • 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[];
  }
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

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
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2.4 useMemoizedFn

持久化 function 的 Hook,理论上,可以使用 useMemoizedFn 完全代替 useCallback

const memoizedFn = useMemoizedFn(fn);
  • 1
  • 参数: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)
})

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 在某些场景中,我们需要使用 useCallback 来记住一个函数,但是在第二个参数 deps 变化时,会重新生成函数,导致函数地址变化
  • 使用 useMemoizedFn,可以省略第二个参数 deps,同时保证函数地址永远不会变化,可以用于性能优化。
  • useMemoizedFn解决了由 hook 中的 deps 引起的闭包问题,同时保证了函数调用的准确性,实时性,保证函数调用可以拿到最新的值。

useMemoizedFn的实现

const callbackFn = useCallback(() => {
  console.log(`Current count is ${count}`);
}, [count]);

<ExpensiveTree showCount={callbackFn} />
  • 1
  • 2
  • 3
  • 4
  • 5

在上面的代码中,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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

到这里,我们已经实现了 useMemoizedFn 的所有功能,简单来说,这个 hook 做的事情就是实时的维护函数的最新引用,并在适当的时候通过一个包装函数来调用它。

2.5 useUrlState

通过 url query 来管理 state 的 Hook。

npm i @ahooksjs/use-url-state -S
  • 1

该 Hooks 基于 react-router 的 useLocation & useHistory & useNavigate 进行 query 管理,一般获取路由参数使用该hook更加简便
使用该 Hooks 之前,你需要保证

  1. 你项目正在使用 react-router 5.x 或 6.x 版本来管理路由

  2. 独立安装了 @ahooksjs/use-url-state

import useUrlState from '@ahooksjs/use-url-state';
const [state, setState] = useUrlState(initialState, options);
  • 1
  • 2
  • 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')
  • 1
  • 2
  • 3

使用useUrlState
const [query, setQuery] = useUrlState(props, { navigateMode: ‘replace’ });
query就直接可以取出name和id

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/275135?site
推荐阅读
相关标签
  

闽ICP备14008679号