react hook -- 自定义Hook

2022-12-29  本文已影响0人  廖雪青

当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。而组件和Hook都是函数,所以也同样适用这种方式。

认识自定义Hook

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

在自定义Hook的顶层可以无条件地调用其他Hook(useState, useEffect)。
我们可以自由决定自定义Hook的参数返回值
自定义Hook必须以use开头,这样方便判断该函数内部是否调用了内部Hook,以及React能自动检查Hook是否违反了Hook的规则(见Hook规则部分)。
每次使用自定义Hook时,其中的state和副作用都是完全隔离的。

Hooks 和普通函数在语义上是有区别的,就在于函数中有没有用到其它 Hooks。

就是说如果你创建了一个 useXXX 的函数,但是内部并没有用任何其它 Hooks,那么这个函数就不是一个 Hook,而只是一个普通的函数。但是如果用了其它 Hooks ,那么它就是一个 Hook。

自定义Hook的特点

可重用逻辑直接写一个工具类不就行了吗?为什么一定要通过Hook进行封装呢?

因为在 Hooks 中,你可以管理当前组件的 state,从而将更多的逻辑写在可重用的 Hooks 中。但是要知道,在普通的工具类中是无法直接修改组件 state 的,那么也就无法在数据改变的时候触发组件的重新渲染。

拆分逻辑的目的不一定是为了重用,而可以是仅仅为了业务逻辑的隔离。

在这个场景下,我们不一定要把 Hooks 放到独立的文件中,而是可以和函数组件写在一个文件中。这么做的原因就在于,这些 Hooks 是和当前函数组件紧密相关的,所以写到一起,反而更容易阅读和理解。

例:

function MyComponent() {
  const [id, setId] = useState(1);
  const isOnline = useOnlineStatus(id);

  return (
    <>
    // other nodes
    </>
  )
}

useState为我们提供了id的最新值,并把它做为参数传入useOnlineStatus, 当id改变时,useOnlineStatus Hook会取消订阅前一个id,并订阅新的id

例一:自定义 Hook 处理 LocalStorage 的存取

需求:希望把一些数据存储到 localStorage 中 - 不使用自定义Hook

不使用自定义Hook

import React, { useState, useEffect } from 'react'

export default function CustomDataStoreHook() {
  const [name, setName] = useState(() => {
    return JSON.parse(window.localStorage.getItem("name"))
  });

  useEffect(() => {
    window.localStorage.setItem("name", JSON.stringify(name));
  }, [name])

  return (
    <div>
      <h2>CustomDataStoreHook: {name}</h2>
      <button onClick={e => setName("gercke")}>设置name</button>
    </div>
  )
}

定义自定义Hook - useLocalStorage

import React,{useState, useEffect} from 'react';
function useLocalStorage(key) {
  const [data, setData] = useState(() => {
    return JSON.parse(window.localStorage.getItem(key))
  });

  useEffect(() => {
    window.localStorage.setItem(key, JSON.stringify(data));
  }, [data]);

  return [data, setData];
}

export default useLocalStorage;

使用自定义Hook

import React, { useState, useEffect } from 'react';

import useLocalStorage from '../hooks/local-store-hook';

export default function CustomDataStoreHook() {
  const [name, setName] = useLocalStorage("name");

  return (
    <div>
      <h2>CustomDataStoreHook: {name}</h2>
      <button onClick={e => setName("kobe")}>设置name</button>
    </div>
  )
}

例二:自定义Hook监听浏览器状态变化

需求:当 y > 300 时,显示Back to top按钮

定义自定义Hook - useScroll

import { useState, useEffect } from 'react';

// 获取横向,纵向滚动条位置
const getPosition = () => {
  return {
    x: document.body.scrollLeft,
    y: document.body.scrollTop,
  };
};
const useScroll = () => {
  // 定一个 position 这个 state 保存滚动条位置
  const [position, setPosition] = useState(getPosition());
  useEffect(() => {
    const handler = () => {
      setPosition(getPosition(document));
    };
    // 监听 scroll 事件,更新滚动条位置
    document.addEventListener("scroll", handler);
    return () => {
      // 组件销毁时,取消事件监听
      document.removeEventListener("scroll", handler);
    };
  }, []);
  return position;
};

使用自定义Hook

import React, { useCallback } from 'react';
import useScroll from './useScroll';

function ScrollTop() {
  const { y } = useScroll();

  const goTop = useCallback(() => {
    document.body.scrollTop = 0;
  }, []);

  const style = {
    position: "fixed",
    right: "10px",
    bottom: "10px",
  };
  // 当滚动条位置纵向超过 300 时,显示返回顶部按钮
  if (y > 300) {
    return (
      <button onClick={goTop} style={style}>
        Back to Top
      </button>
    );
  }
  // 否则不 render 任何 UI
  return null;
}

Hook 规则

要确保Hook的调用顺序在每次渲染中都是相同的

上一篇下一篇

猜你喜欢

热点阅读