【React】自定义hook

154 阅读2分钟

简介

React 16.8 版本引入了 Hooks,使得函数组件能够使用状态和其他React特性,React 中内置了一些 Hook,比如 useStateuseContextuseEffect。有时我们需要一个用途更特殊的 Hook,这时我们就可以根据应用需求创建自己的 Hook。

优势

  • 可以聚合功能代码使代码可读性更强,有利于维护
  • 可以在多个组件之间重用状态逻辑,提高代码的可重用性

限制

  • 没有使用hook的函数不要加 use
  • 使用了hook的自定义hook名称必须以 use 开头,然后紧跟一个大写字母

实现一个自定义hook

借助官方的示例实现一个网络状态同步hook,常规写法如下:

import React, { useEffect, useState } from 'react';
function App() {
  const [isOnline, setIsOnline] = useState(true);
  
  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      // 组件释放取消事件监听
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  },[]);
  
  return (
    <div className="App">
      <h3>{isOnline ? '✅ 在线' : '❌ 离线'}</h3>
    </div>
  );
}

export default App;

从上面示例中可以看出我们只关注网络状态,不需要关心网络监听、移除等操作,该功能可以看作是一个独立的功能与当前页面没有关系 ,我们可以将其独立为 useOnlineStatus 的hook,让其与 useStateuseEffect 相似。

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

// 自定义hook
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    return () => {
      // 组件释放取消事件监听
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    }
  }, []);
  return isOnline;
}

function App() {
  // 使用hook
  const isOnline = useOnlineStatus();
  return (
    <div className="App">
      <h3>{isOnline ? '✅ 在线' : '❌ 离线'}</h3>
    </div>
  );
}

export default App;

image.png

实现一个输入hook

自定义 Hook 共享的只是状态逻辑而不是状态本身,每个调用都完全独立于对同一个 Hook 的其他调用。

import React, { useEffect, useState } from 'react';
import './App.css';

function useFormInput(initialValue: string) {
  const [value, setValue] = useState(initialValue);
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };
  return {
    value,
    onChange: handleChange,
  };
}

function App() {
  // 使用hook
  const javaScriptProps = useFormInput('JavaScript');
  const typeScriptProps = useFormInput('TypeScript');
  return (
    <div className="App">
      <h2>{javaScriptProps.value}-{typeScriptProps.value}</h2>
      <input type="text" {...javaScriptProps} />
      <input type="text" {...typeScriptProps} />
    </div>
  );
}

export default App;

实现一个跳过首次触发的Effect

/**
 *  useEffect跳过首次渲染
 * @param effect 回调
 * @param dependencies 依赖项,依赖项改变时才执行回调
 */
function useSkipFirstEffect(effect: () => void, dependencies: any[]) {
  // 记录回调函数是否是第一次渲染
  const isFirstRender = useRef(true);
  
  useEffect(() => {
    if (isFirstRender.current) { // 第一次渲染不执行
      isFirstRender.current = false;
    } else { // 第二次及以后执行回调
      effect();
    }
  }, dependencies)
}

实现一个剪切板hook

import { useCallback, useState } from "react";

const useClipboard = () => {
  const [isCopied, setIsCopied] = useState(false);
  const copy = useCallback(async (textstring) => {
    // 判断是否支持剪贴板
    if (!navigator.clipboard) {
      // 打印警告
      console.warn("当前浏览器不支持剪贴板");
      return;
    }
    // try catch
    try {
      navigator.clipboard.writeText(text);
      setIsCopied(true);
      // 延迟1.5秒后清除状态
      setTimeout(() => {
        setIsCopied(false);
      }, 1500);
    } catch (error) {
      console.warn("复制失败", error);
      setIsCopied(false);
    }
  }, []);
  
  return { isCopied, copy }
}

export default useClipboard;

参考

友情提示

见原文:【React】自定义hook)

本文同步自微信公众号 "程序员小溪" ,这里只是同步,想看及时消息请移步我的公众号,不定时更新我的学习经验。