ahooks解析其一

5,109 阅读5分钟

一.什么是ahooks

ahooks是由阿里的前端团队开发,它是基于React Hooks 的工具库,致力提供常用且高质量的 Hooks,hooks的创建使得构建 React 组件时不再需要定义class类。从开发中得到证明,很早之前,React中使用的类架构,随着项目结构的庞大,类组件的维护让我们的开发变得繁琐。经常发现自己在编写难以分解的大型复杂组件,并且组件之间极度耦合,很难再去拆分成小组件,而且相关代码分布在多个生命周期方法中,使得阅读、维护和测试变得格外棘手。此外,我们还必须绑定方法以this确保可以在组件中访问到this。在处理高阶组件时会遇到过多的 prop 钻孔问题——也称为包装器地狱。由此,hooks在前端react开发中渐渐兴起,阿里的开发团队将在开发中使用的hooks进行封装,最终得到了一套完整的hooks类库,这便是ahooks的由来。

ahooks 的前身是蚂蚁开源的 @umijs/hooks,可以说 ahooks 是 umi hooks 的 2.0 版本。

二.学习文档

三.ahooks安装

3.1 下载

npm i ahooks 或者 yarn add ahooks

3.2 按需加载

可以通过以下的写法来按需加载 Hooks。

import useToggle from 'ahooks/es/useToggle';

注意:ahooks 默认支持基于 ES module 的 tree shaking,对于 js 部分,直接引入 import { useToggle } from 'ahooks' 也会有按需加载的效果。

如果你使用了 babel,那么可以使用 babel-plugin-import 来进行按需加载,加入这个插件后。你可以仍然这么写:

import { useToggle } from 'ahooks';

插件会帮你转换成 ahooks/es/useToggle 的写法。

四.基本使用

4.1 简单案例

以下是使用useToggle这个hooks对文字的切换案例

import React from 'react'
import { useToggle } from "ahooks";

const Index = (props) => {
    const [state, { toggle }] = useToggle();
    return (
        <>
         
                {
                    state?<p>显示</p>:<p>隐藏</p>
                }
                <p>
                    <button onClick={() => toggle()}>切换</button>
                </p>
        </>
    )
}


export default Index

五.常用hooks

5.1 useRequest

这个hooks使用于发送请求,使用过umi的小伙伴知道,umi中发送请求就是使用的useRequest 它具有以下特性:

  • 自动请求/手动请求
  • SWR(stale-while-revalidate)
  • 缓存/预加载
  • 屏幕聚焦重新请求
  • 轮询
  • 防抖
  • 节流
  • 并行请求
  • 依赖请求
  • loading delay
  • 分页
  • 加载更多,数据恢复 + 滚动位置恢复 在下面这个例子中, useRequest 接收了一个异步函数 getSome ,在组件初次加载时, 自动触发该函数执行。同时 useRequest 会自动管理异步请求的 loading , data , error 等状态。
import {useRequest } from 'ahooks';
const getSome = async () => {};
const { data, loading, run } = useRequest(getSome, {
debounceInterval: 500,
manual: true,
  refreshDeps: [], // manual为false,可以按被动式触发
});

5.2 缓存hooks

5.2.1 useCookieState

import { useCookieState } from 'ahooks';

const [message, setMessage] = useCookieState('cookie-key');

<input
  value={message}
  onChange={(e) => setMessage(e.target.value)}
/> 

5.2.2 useLocalStorageState

import { useLocalStorageState } from 'ahooks';

const [message, setMessage] = useLocalStorageState('store-key', 'default value');

<input
  value={message || ''}
  onChange={(e) => setMessage(e.target.value)}
/>

5.2.3 useSessionStorageState

import { useSessionStorageState } from 'ahooks';

const [message, setMessage] = useSessionStorageState('store-key', 'default-value');

<input
  value={message}
  onChange={(e) => {
    setMessage(e.target.value);
  }}
/>

5.3 性能优化hooks

5.3.1 useCreation

它是用来代替useMemo、useRef的钩子,因为useMemo不能保证一定不会被重新计算,useRef如果针对复杂对象,每次渲染都创建一次会很耗性能

import { useCreation } from 'ahooks';

const foo = useCreation(() => {number: Math.random()}, []);

5.3.2 useDebounceFn

这个钩子用于防抖

import { useDebounceFn } from 'ahooks';

const { run } = useDebounceFn(
  () => console.log('test'),
  { wait: 500 },
);

<div>
  <Button onClick={run}>
    Click fast!
  </Button>
</div>

5.3.3 useThrottleFn

这个钩子用于节流

import { useThrottleFn } from 'ahooks';

const { run } = useThrottleFn(
  () => console.log('test'),
  { wait: 500 },
);

<div>
  <Button onClick={run}>
    Click fast!
  </Button>
</div>

5.3.4 useInterval

这个钩子与定时器功能一致

import { useInterval } from 'ahooks';

const [count, setCount] = useState(0);

useInterval(() => {
  setCount(count + 1);
}, 1000);

<div>数字: {count}</div>

5.3.5 useVirtualList

提供虚拟化列表能力的 Hook,用于解决展示海量数据渲染时首屏渲染缓慢和滚动卡顿问题。

import { useVirtualList } from 'ahooks';

const { list, containerProps, wrapperProps } = useVirtualList(Array.from(Array(99999).keys()), {
	overscan: 30, // 视区上、下额外展示的 dom 节点数量
	itemHeight: 60, // 行高度,静态高度可以直接写入像素值,动态高度可传入函数
});

5.4 操作DOM的hooks

5.4.1 useClickAway

这个钩子可以确定一个区域,当鼠标点击区域外触发,可以做一些点击区域外的操作

import React,{useRef} from 'react'
import { useClickAway } from 'ahooks';

const Index=(props)=> {
 
const ref = useRef();
useClickAway(() => {
  console.log('点击到div外部了')
}, ref);

return(
  <div ref={ref} style={{width:'200px',height:'100px',background:'red',
  lineHeight:'100px',textAlign:'center'}}> 这是一个区域内 </div>
)
}



export default Index

5.4.2 useDocumentVisibility

可以获取页面的可见状态,并且监听document 的可见状态

import React,{useEffect} from 'react'
import { useDocumentVisibility } from 'ahooks';

const documentVisibility = useDocumentVisibility();
useEffect(() => {
  if (documentVisibility === 'visible') {
    console.log('当前页面在可见状态');
  } else {
    console.log('当前页面不在可见状态');
  }
}, [documentVisibility]);

5.4.3 useEventListener

这个钩子类似于dom操作中的addEventListener,可以给dom元素绑定事件 这个钩子可以传递三个参数 | 参数 | 说明 | 类型 | 默认值 | |-----------|------------|------------|--------| | eventName | 事件名称 | string | - | | handler | 处理函数 | Function | - | | options | 设置(可选) | Options | - | 在它的源码中是这么绑定元素的,也是利用了addEventListener

    targetElement.addEventListener(eventName, eventListener, {
      capture: options.capture,
      once: options.once,
      passive: options.passive,
    });

使用

import React, {
  useState,
  useRef
} from 'react'
import {
  useEventListener
} from 'ahooks';

const Index = (props) => {

  const [value, setValue] = useState(0);

  const clickHandler = () => {
    setValue(value + 1);
  };

  const ref = useRef();
  useEventListener('click', clickHandler, {
    target: ref
  });

  const style = {
    width: '100px',
    height: '100px',
    background: 'red',
    lineHeight: '100px',
    textAlign: 'center'
  }
  return ( 
    <>
    <p>这是一个数字:{value}</p> 
    <div ref = {ref} style = {style} > 加一 </div> 
    </>
  )
}

export default Index

5.4.4 useInViewport

这个钩子可以观测到目标元素是否在可视范围内

import { useInViewport } from 'ahooks';

const ref = useRef();
const inViewPort = useInViewport(ref);

<div ref={ref}>
  {inViewPort ? 'div在可视区' : 'div不在可视区'}
</div>

在源码中它首先使用了一个getBoundingClientRect观察目标元素得到位置,判断它的top,bottom,left,right是否在可视区域内,用IntersectionObserver去监听当目标元素的位置发生变化时,目标元素是否还在可视区域内

//这里使用了 [IntersectionObserver API],这个api可以自动"观察"元素是否可见
  const observer = new IntersectionObserver((entries) => {
      for (const entry of entries) {
        if (entry.isIntersecting) {
          setInViewport(true);
        } else {
          setInViewport(false);
        }
      }
    });

    observer.observe(el as HTMLElement);

    return () => {
      observer.disconnect();
    };
  }, [target]);

5.4.5 useResponsive

这个钩子可以监听浏览器视口的大小,可以做一些屏幕适配功能

import React from 'react'
import { configResponsive, useResponsive } from 'ahooks';
const Index=(props)=> {
configResponsive({
  small: 0,
  middle: 800,
  large: 1200,
});
const responsive = useResponsive();
    return (
        <>  
      <h1>改变浏览器视口的大小,判断对错 </h1>
      {Object.keys(responsive).map((key) => (
        <p key={key}>
          {key} {responsive[key] ? '✔' : '✘'}
        </p>
      ))}
  
        </>
    )
}

export default Index

5.4.6 useEventEmitte

这个钩子是用来实现事件订阅的

import { useEventEmitter } from 'ahooks';

// 事件队列
const focus$ = useEventEmitter<number>();

// 发送
focus$.emit(123);

// 订阅
focus$.useSubscription(value => {
  console.log(value);
});