Loading...

# react 自定义 hook

# 1. 什么是自定义 hook

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。

函数名 :以 “use” 开头,比如 useState。为什么要以 “use” 开头。这是因为,如果不以 “use” 开头的话,无法判断这个函数是否包含对其内部 Hook 的调用,React 将无法自动检查这个自定义 Hook 是否违反了 Hook 的规则。

参数 :并没有特殊要求,只是 React 官网中有提到过,可以接收另一个 Hook 的返回值作为参数,实现在多个 Hook 之间传递信息的功能。

函数内部 :在函数内部可以调用其他的 Hook,但是要遵循两个规则:

  • 只在最顶层使用 Hook ,不要在循环,条件或嵌套函数中调用 Hook,这是因为 React 是靠 Hook 调用的顺序来知道哪个 state 对于哪个 useState 的。

  • 自定义 hooks 由 React 内置的 hook 或其他的自定义 hook 组成。因此一个自定义 hook 本质是一个或多个 hook 构成的新组合。如果一个自定义 hook 没有使用任何的内部 hook,那它就不是一个自定义 hook,也不应该以 "use" 为前缀。

# 2. 使用场景

让组件的通用逻辑复用更简单,可以把包含状态和事件处理的所有实现细节,移动到这个自定义 hook 中。另外,这个自定义 hook 返回一个数组,包含状态和一些更新状态函数。

# 3. 如何使用

自定义 hook 是定义在一个 js 或 ts 文件中,最后用 export 导出,故用 import 引入使用:

React 提供了多个内部 Hook,在自定义 Hook 的内部,一般都是利用内部 Hook,进行组装和扩展,来自定义各种功能的 Hook。
其中比较常见使用的 hooks 是
useState , useRef , useEffect/useLayoutEffect

而从一个自定义 hook 中需要返回多个值时最佳的做法是返回一个数组,并利用数组进行解构。

# 4. 案例

实现一个检测 React 组件外部点击事件的 useOutsideClick ,其使用方式大致如下所示

function App() {
  const handleClickOutside = () => {
    //dosomething
  };
  const ref = useOutsideClick(handleClickOutside);
  const handleClick = () => {
    //dosomething
  };
  return (
    <div>
      <button ref={ref} type="button" onClick={handleClick}>
        Count: {count}
      </button>
    </div>
  );
}

从上述的使用方式上,可以首先写出如下的代码,输入 callback ,输出一个 ref

t
const useOutsideClick = (callback) => {
  const ref = React.useRef();
  // 具体代码
  return ref;
};

由于这个 hook 主要是处理点击事件的,所以首先需要添加点击事件,并且在销毁时移除添加的事件,为了能监听所有的点击事件,是在 document 上添加。

t
const useOutsideClick = (callback) => {
  const ref = React.useRef();
  React.useEffect(() => {
    const handleClick = (event) => {
      callback();
    };
    document.addEventListener('click', handleClick);
    return () => {
      document.removeEventListener('click', handleClick);
    };
  }, []);
  return ref;
};

目前版本所有点击都会触发 callback ,所以接下来就是过滤掉自身触发的 onclick 事件。

t
const useOutsideClick = (callback) => {
  const ref = React.useRef();
  React.useEffect(() => {
    const handleClick = (event) => {
      if (ref.current && !ref.current.contains(event.target)) {
        callback();
      }
    };
    document.addEventListener('click', handleClick);
    return () => {
      document.removeEventListener('click', handleClick);
    };
  }, [ref]);
  return ref;
};

上述的代码已经满足了要求,但是还有一点可以改进的,就是可能某些场景下,存在一个触发事件的边界,即外部可以通过 stopPropagation 来停止事件的传播。如下所示:

t
function App() {
  const handleClickOutside = () => {
    //dosomething
  };
  const ref = useOutsideClick(handleClickOutside);
  const handleClick = () => {
    //dosomething
  };
  const handleHeaderClick = (event) => {
    // do something
    event.stopPropagation();
  };
  return (
    <div onClick={handleHeaderClick}>
      <button ref={ref} type="button" onClick={handleClick}>
        Count: {count}
      </button>
    </div>
  );
}

目前的版本并不适用与当前情况,因为事件中途停止冒泡了,所以无法触发在 document 上的 callback

为了实现上述功能,可以将冒泡阶段的事件处理放在捕获阶段进行处理。

t
const useOutsideClick = (callback) => {
  const ref = React.useRef();
  React.useEffect(() => {
    const handleClick = (event) => {
      if (ref.current && !ref.current.contains(event.target)) {
        callback();
      }
    };
    document.addEventListener('click', handleClick, true);
    return () => {
      document.removeEventListener('click', handleClick, true);
    };
  }, [ref]);
  return ref;
};

# 5. 参考

  • React Hook: Detect click outside of Component
  • React Hook: Using the Local Storage
  • React Hook: Check if Overflow
  • React Hook: Get Scroll Direction
  • React Hook: Get Scrollbar Width
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

jluyeyu 微信支付

微信支付

jluyeyu 支付宝

支付宝