好东西就该分享出来!!分享一些实用的React Hoooks

前言

当谈到现代前端开发和React时,React Hook 是一个不可或缺的话题。它们是函数式组件的利器,为开发者提供了更强大、更灵活的工具,让你的应用更易于维护和扩展。无论你是React的新手还是老手,优秀的React Hook都有助于你在项目中更高效地工作。本文将分享一些实用的React Hook,它们可以帮助你简化组件的逻辑,提高代码的可读性,以及增加应用的性能和响应性。

业界流行的React Hook库

首先列举下当前比较主流的React Hook库

其中ahooks算是高水准的Hooks库,而且笔者是ahooks的重度使用者,所以本次会从该库中抽取一些开发过程中常用的(带有笔者墙裂的主观色彩)Hooks进行介绍

Hooks介绍

Scene

useTextSelection

实时获取用户当前选取的文本内容及位置。

食用方法

jsx 复制代码
import React from 'react';
import { useTextSelection } from 'ahooks';


export default () => {
  const { text } = useTextSelection();
  return (
    <div>
      <p>You can select text all page.</p>
      <p>Result:{text}</p>
    </div>
  );
};

useVirtualList

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

食用方法

jsx 复制代码
import React, { useMemo, useRef } from 'react';
import { useVirtualList } from 'ahooks';

export default () => {
  const containerRef = useRef(null);
  const wrapperRef = useRef(null);

  const originalList = useMemo(() => Array.from(Array(99999).keys()), []);

  const [list] = useVirtualList(originalList, {
    containerTarget: containerRef,
    wrapperTarget: wrapperRef,
    itemHeight: 60,
    overscan: 10,
  });
  return (
    <>
      <div ref={containerRef} style={{ height: '300px', overflow: 'auto', border: '1px solid' }}>
        <div ref={wrapperRef}>
          {list.map((ele) => (
            <div
              style={{
                height: 52,
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                border: '1px solid #e8e8e8',
                marginBottom: 8,
              }}
              key={ele.index}
            >
              Row: {ele.data}
            </div>
          ))}
        </div>
      </div>
    </>
  );
};

笔者注: 实际上就是通过容器的高度、元素的高度和当前滚动条位置来计算当前应该显示哪一批数据。

LifeCycle

useMount

只在组件初始化时执行的 Hook

食用方法

jsx 复制代码
import { useMount, useBoolean } from 'ahooks';
import { message } from 'antd';
import React from 'react';

const MyComponent = () => {
  useMount(() => {
    message.info('mount');
  });

  return <div>Hello World</div>;
};

export default () => {
  const [state, { toggle }] = useBoolean(false);

  return (
    <>
      <button type="button" onClick={toggle}>
        {state ? 'unmount' : 'mount'}
      </button>
      {state && <MyComponent />}
    </>
  );
};

笔者注: 相当于空依赖的useEffect

jsx 复制代码
import React from 'react'

export default function component() {

  useEffect(() => {
    // do something
  }, [])

  return (
    <div>component</div>
  )
}

useUnmount

在组件卸载(unmount)时执行的 Hook。

食用方法

jsx 复制代码
import { useBoolean, useUnmount } from 'ahooks';
import { message } from 'antd';
import React from 'react';

const MyComponent = () => {
  useUnmount(() => {
    message.info('unmount');
  });

  return <p>Hello World!</p>;
};

export default () => {
  const [state, { toggle }] = useBoolean(true);

  return (
    <>
      <button type="button" onClick={toggle}>
        {state ? 'unmount' : 'mount'}
      </button>
      {state && <MyComponent />}
    </>
  );
};

笔者注: 相当于空依赖的useEffect的返回函数

jsx 复制代码
import React from 'react'

export default function component() {

  useEffect(() => {
    return () => {
      // do something
    }
  }, [])

  return (
    <div>component</div>
  )
}

State

useDebounce

用来处理防抖值的 Hook。

食用方法

jsx 复制代码
import React, { useState } from 'react';
import { useDebounce } from 'ahooks';

export default () => {
  const [value, setValue] = useState<string>();
  const debouncedValue = useDebounce(value, { wait: 500 });

  return (
    <div>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Typed value"
        style={{ width: 280 }}
      />
      <p style={{ marginTop: 16 }}>DebouncedValue: {debouncedValue}</p>
    </div>
  );
};

笔者注: 对useState的value做防抖处理,并导出一个新的变量。这样就不会因为value频繁变化导致页面频繁渲染。

useThrottle

用来处理节流值的 Hook。

食用方法

jsx 复制代码
import React, { useState } from 'react';
import { useThrottle } from 'ahooks';

export default () => {
  const [value, setValue] = useState<string>();
  const throttledValue = useThrottle(value, { wait: 500 });

  return (
    <div>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Typed value"
        style={{ width: 280 }}
      />
      <p style={{ marginTop: 16 }}>throttledValue: {throttledValue}</p>
    </div>
  );
};

笔者注: 道理同上,只是做了节流处理并导出一个新的变量。

useMap

管理 Map 类型状态的 Hook。

食用方法

jsx 复制代码
import React from 'react';
import { useMap } from 'ahooks';

export default () => {
  const [map, { set, setAll, remove, reset, get }] = useMap<string | number, string>([
    ['msg', 'hello world'],
    [123, 'number type'],
  ]);

  return (
    <div>
      <button type="button" onClick={() => set(String(Date.now()), new Date().toJSON())}>
        Add
      </button>
      <button
        type="button"
        onClick={() => setAll([['text', 'this is a new Map']])}
        style={{ margin: '0 8px' }}
      >
        Set new Map
      </button>
      <button type="button" onClick={() => remove('msg')} disabled={!get('msg')}>
        Remove 'msg'
      </button>
      <button type="button" onClick={() => reset()} style={{ margin: '0 8px' }}>
        Reset
      </button>
      <div style={{ marginTop: 16 }}>
        <pre>{JSON.stringify(Array.from(map), null, 2)}</pre>
      </div>
    </div>
  );
};

笔者注: 笔者用的最频繁的Hook之一。尤其来管理不定状态(数量不定、key不定)有奇效。

usePrevious

保存上一次状态的 Hook。

食用方法

jsx 复制代码
import { usePrevious } from 'ahooks';
import React, { useState } from 'react';

export default () => {
  const [count, setCount] = useState(0);
  const previous = usePrevious(count);
  return (
    <>
      <div>counter current value: {count}</div>
      <div style={{ marginBottom: 8 }}>counter previous value: {previous}</div>
      <button type="button" onClick={() => setCount((c) => c + 1)}>
        increase
      </button>
      <button type="button" style={{ marginLeft: 8 }} onClick={() => setCount((c) => c - 1)}>
        decrease
      </button>
    </>
  );
};

笔者注: 没啥好说的,就是记录当前状态的前一次数据。如果有对比新老数据的需求,有奇效。

useRafState

只在 requestAnimationFrame callback 时更新 state,一般用于性能优化。

食用方法

jsx 复制代码
import { useRafState } from 'ahooks';
import React, { useEffect } from 'react';

export default () => {
  const [state, setState] = useRafState({
    width: 0,
    height: 0,
  });

  useEffect(() => {
    const onResize = () => {
      setState({
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight,
      });
    };
    onResize();

    window.addEventListener('resize', onResize);

    return () => {
      window.removeEventListener('resize', onResize);
    };
  }, []);

  return (
    <div>
      <p>Try to resize the window </p>
      current: {JSON.stringify(state)}
    </div>
  );
};

useSafeState

用法与 React.useState 完全一样,但是在组件卸载后异步回调内的 setState 不再执行,避免因组件卸载后更新状态而导致的内存泄漏。

食用方法

jsx 复制代码
import { useSafeState } from 'ahooks';
import React, { useEffect, useState } from 'react';

const Child = () => {
  const [value, setValue] = useSafeState<string>();

  useEffect(() => {
    setTimeout(() => {
      setValue('data loaded from server');
    }, 5000);
  }, []);

  const text = value || 'Loading...';

  return <div>{text}</div>;
};

export default () => {
  const [visible, setVisible] = useState(true);

  return (
    <div>
      <button onClick={() => setVisible(false)}>Unmount</button>
      {visible && <Child />}
    </div>
  );
};

笔者注: 笔者最喜欢的Hook,没有之一。主要用来处理异步setState前就已经把组件卸载导致内存泄露的问题。如果你的控制台出现以下警告,那么这个Hook包治各种不服!

useGetState

React.useState 增加了一个 getter 方法,以获取当前最新值。

食用方法

jsx 复制代码
import React, { useEffect } from 'react';
import { useGetState } from 'ahooks';

export default () => {
  const [count, setCount, getCount] = useGetState<number>(0);

  useEffect(() => {
    const interval = setInterval(() => {
      console.log('interval count', getCount());
    }, 3000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return <button onClick={() => setCount((count) => count + 1)}>count: {count}</button>;
};

笔者注: 在setInterval或者setTimeout等闭包场景下有奇效。

Effect

useUpdateEffect

useUpdateEffect 用法等同于 useEffect,但是会忽略首次执行,只在依赖更新时执行。

食用方法

jsx 复制代码
import React, { useEffect, useState } from 'react';
import { useUpdateEffect } from 'ahooks';

export default () => {
  const [count, setCount] = useState(0);
  const [effectCount, setEffectCount] = useState(0);
  const [updateEffectCount, setUpdateEffectCount] = useState(0);

  useEffect(() => {
    setEffectCount((c) => c + 1);
  }, [count]);

  useUpdateEffect(() => {
    setUpdateEffectCount((c) => c + 1);
    return () => {
      // do something
    };
  }, [count]); // you can include deps array if necessary

  return (
    <div>
      <p>effectCount: {effectCount}</p>
      <p>updateEffectCount: {updateEffectCount}</p>
      <p>
        <button type="button" onClick={() => setCount((c) => c + 1)}>
          reRender
        </button>
      </p>
    </div>
  );
};

笔者注: 如果你在组件中有个依赖某个变量而执行的逻辑且不希望该逻辑在组件初始化时候执行的话,那么这个Hook有奇效

useDebounceEffect

useEffect 增加防抖的能力。

食用方法

jsx 复制代码
import { useDebounceEffect } from 'ahooks';
import React, { useState } from 'react';

export default () => {
  const [value, setValue] = useState('hello');
  const [records, setRecords] = useState<string[]>([]);
  useDebounceEffect(
    () => {
      setRecords((val) => [...val, value]);
    },
    [value],
    {
      wait: 1000,
    },
  );
  return (
    <div>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Typed value"
        style={{ width: 280 }}
      />
      <p style={{ marginTop: 16 }}>
        <ul>
          {records.map((record, index) => (
            <li key={index}>{record}</li>
          ))}
        </ul>
      </p>
    </div>
  );
};

笔者注: 如果不希望依赖状态(频繁变化)来执行的逻辑不要那么频繁执行,那么就用这个吧

useDebounceFn

用来处理防抖函数的 Hook。

食用方法

jsx 复制代码
import { useDebounceFn } from 'ahooks';
import React, { useState } from 'react';

export default () => {
  const [value, setValue] = useState(0);
  const { run } = useDebounceFn(
    () => {
      setValue(value + 1);
    },
    {
      wait: 500,
    },
  );

  return (
    <div>
      <p style={{ marginTop: 16 }}> Clicked count: {value} </p>
      <button type="button" onClick={run}>
        Click fast!
      </button>
    </div>
  );
};

笔者注: 没啥可说的,就是防!抖!

useThrottleEffect

useEffect 增加节流的能力。

食用方法

jsx 复制代码
import React, { useState } from 'react';
import { useThrottleEffect } from 'ahooks';

export default () => {
  const [value, setValue] = useState('hello');
  const [records, setRecords] = useState<string[]>([]);
  useThrottleEffect(
    () => {
      setRecords((val) => [...val, value]);
    },
    [value],
    {
      wait: 1000,
    },
  );
  return (
    <div>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Typed value"
        style={{ width: 280 }}
      />
      <p style={{ marginTop: 16 }}>
        <ul>
          {records.map((record, index) => (
            <li key={index}>{record}</li>
          ))}
        </ul>
      </p>
    </div>
  );
};

笔者注: 如果不希望依赖状态(频繁变化)来执行的逻辑按某个节奏来执行,那么就用这个吧

useThrottleFn

用来处理函数节流的 Hook。

食用方法

jsx 复制代码
import React, { useState } from 'react';
import { useThrottleFn } from 'ahooks';

export default () => {
  const [value, setValue] = useState(0);
  const { run } = useThrottleFn(
    () => {
      setValue(value + 1);
    },
    { wait: 500 },
  );

  return (
    <div>
      <p style={{ marginTop: 16 }}> Clicked count: {value} </p>
      <button type="button" onClick={run}>
        Click fast!
      </button>
    </div>
  );
};

笔者注: 没啥可说的,就是节!流!

useRafInterval

requestAnimationFrame 模拟实现 setInterval,API 和 useInterval 保持一致,好处是可以在页面不渲染的时候停止执行定时器,比如页面隐藏或最小化等。
请注意,如下两种情况下很可能是不适用的,优先考虑 useInterval

  • 时间间隔小于 16ms
  • 希望页面不渲染的情况下依然执行定时器

Node 环境下 requestAnimationFrame 会自动降级到 setInterval

食用方法

jsx 复制代码
import React, { useState } from 'react';
import { useRafInterval } from 'ahooks';

export default () => {
  const [count, setCount] = useState(0);

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

  return <div>count: {count}</div>;
};

笔者注: 性能优化有他没毛病!

useUpdate

useUpdate 会返回一个函数,调用该函数会强制组件重新渲染。

食用方法

jsx 复制代码
import React from 'react';
import { useUpdate } from 'ahooks';

export default () => {
  const update = useUpdate();

  return (
    <>
      <div>Time: {Date.now()}</div>
      <button type="button" onClick={update} style={{ marginTop: 8 }}>
        update
      </button>
    </>
  );
};

笔者注: 手动触发组件重渲染,关键时候大用处。

Dom

useEventListener

优雅的使用 addEventListener。

食用方法

jsx 复制代码
import React, { useState, useRef } from 'react';
import { useEventListener } from 'ahooks';

export default () => {
  const [value, setValue] = useState(0);
  const ref = useRef(null);

  useEventListener(
    'click',
    () => {
      setValue(value + 1);
    },
    { target: ref },
  );

  return (
    <button ref={ref} type="button">
      You click {value} times
    </button>
  );
};

笔者注: 不用手动removeEventListener,好用就对了!

useSize

监听 DOM 节点尺寸变化的 Hook。

食用方法

jsx 复制代码
import React, { useRef } from 'react';
import { useSize } from 'ahooks';

export default () => {
  const ref = useRef(null);
  const size = useSize(ref);
  return (
    <div ref={ref}>
      <p>Try to resize the preview window </p>
      <p>
        width: {size?.width}px, height: {size?.height}px
      </p>
    </div>
  );
};

笔者注: 监听节点resize就用他,一行代码解决的事情就不要折腾了!

useClickAway

监听目标元素外的点击事件。

食用方法

jsx 复制代码
import React, { useState, useRef } from 'react';
import { useClickAway } from 'ahooks';

export default () => {
  const [counter, setCounter] = useState(0);
  const ref = useRef<HTMLButtonElement>(null);
  useClickAway(() => {
    setCounter((s) => s + 1);
  }, ref);

  return (
    <div>
      <button ref={ref} type="button">
        box
      </button>
      <p>counter: {counter}</p>
    </div>
  );
};

useDocumentVisibility

监听页面是否可见,参考 visibilityState API

食用方法

jsx 复制代码
import React, { useEffect } from 'react';
import { useDocumentVisibility } from 'ahooks';

export default () => {
  const documentVisibility = useDocumentVisibility();

  useEffect(() => {
    console.log(`Current document visibility state: ${documentVisibility}`);
  }, [documentVisibility]);

  return <div>Current document visibility state: {documentVisibility}</div>;
};

笔者注: 一行代码解决的事情就不要折腾了!一行代码解决的事情就不要折腾了!

useInViewport

观察元素是否在可见区域,以及元素可见比例。更多信息参考 Intersection Observer API

食用方法

jsx 复制代码
import React, { useRef } from 'react';
import { useInViewport } from 'ahooks';

export default () => {
  const ref = useRef(null);
  const [inViewport] = useInViewport(ref);
  return (
    <div>
      <div style={{ width: 300, height: 300, overflow: 'scroll', border: '1px solid' }}>
        scroll here
        <div style={{ height: 800 }}>
          <div
            ref={ref}
            style={{
              border: '1px solid',
              height: 100,
              width: 100,
              textAlign: 'center',
              marginTop: 80,
            }}
          >
            observer dom
          </div>
        </div>
      </div>
      <div style={{ marginTop: 16, color: inViewport ? '#87d068' : '#f50' }}>
        inViewport: {inViewport ? 'visible' : 'hidden'}
      </div>
    </div>
  );
};

笔者注: 一行代码解决的事情就不要折腾了!一行代码解决的事情就不要折腾了!一行代码解决的事情就不要折腾了!

Advanced

useLatest

返回当前最新值的 Hook,可以避免闭包问题。

食用方法

jsx 复制代码
import React, { useState, useEffect } from 'react';
import { useLatest } from 'ahooks';

export default () => {
  const [count, setCount] = useState(0);

  const latestCountRef = useLatest(count);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount(latestCountRef.current + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <>
      <p>count: {count}</p>
    </>
  );
};

笔者注:useGetState 有异曲同工之妙

useMemoizedFn

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

在某些场景中,我们需要使用 useCallback 来记住一个函数,但是在第二个参数 deps 变化时,会重新生成函数,导致函数地址变化。

jsx 复制代码
const [state, setState] = useState('');

// 在 state 变化时,func 地址会变化
const func = useCallback(() => {
  console.log(state);
}, [state]);

使用 useMemoizedFn,可以省略第二个参数 deps,同时保证函数地址永远不会变化。

食用方法

jsx 复制代码
const [state, setState] = useState('');

// func 地址永远不会变化
const func = useMemoizedFn(() => {
  console.log(state);
});

笔者注: 将一个回调函数通过props传入子组件且不希望因为函数地址发生变化导致组件无效渲染,那么就用他吧。

Dev

useTrackedEffect

追踪是哪个依赖变化触发了 useEffect 的执行。

食用方法

jsx 复制代码
import React, { useState } from 'react';
import { useTrackedEffect } from 'ahooks';

export default () => {
  const [count, setCount] = useState(0);
  const [count2, setCount2] = useState(0);

  useTrackedEffect(
    (changes) => {
      console.log('Index of changed dependencies: ', changes);
    },
    [count, count2],
  );

  return (
    <div>
      <p>Please open the browser console to view the output!</p>
      <div>
        <p>Count: {count}</p>
        <button onClick={() => setCount((c) => c + 1)}>count + 1</button>
      </div>
      <div style={{ marginTop: 16 }}>
        <p>Count2: {count2}</p>
        <button onClick={() => setCount2((c) => c + 1)}>count + 1</button>
      </div>
    </div>
  );
};

笔者注: 调试利器!

useWhyDidYouUpdate

帮助开发者排查是哪个属性改变导致了组件的 rerender。

食用方法

jsx 复制代码
import { useWhyDidYouUpdate } from 'ahooks';
import React, { useState } from 'react';

const Demo: React.FC<{ count: number }> = (props) => {
  const [randomNum, setRandomNum] = useState(Math.random());

  useWhyDidYouUpdate('useWhyDidYouUpdateComponent', { ...props, randomNum });

  return (
    <div>
      <div>
        <span>number: {props.count}</span>
      </div>
      <div>
        randomNum: {randomNum}
        <button onClick={() => setRandomNum(Math.random)} style={{ marginLeft: 8 }}>
          🎲
        </button>
      </div>
    </div>
  );
};

export default () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <Demo count={count} />
      <div>
        <button onClick={() => setCount((prevCount) => prevCount - 1)}>count -</button>
        <button onClick={() => setCount((prevCount) => prevCount + 1)} style={{ marginLeft: 8 }}>
          count +
        </button>
      </div>
      <p style={{ marginTop: 8 }}>Please open the browser console to view the output!</p>
    </div>
  );
};

笔者注: 想知道组件为啥频繁渲染?用这个Hook试下?

相关推荐
四喜花露水20 分钟前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy29 分钟前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie1 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
工业甲酰苯胺1 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
程序员爱技术5 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
悦涵仙子6 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
兔老大的胡萝卜6 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
cs_dn_Jie9 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic10 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿10 小时前
webWorker基本用法
前端·javascript·vue.js