CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 源码浅析

  • 工具方法

    • React常用自定义Hook汇总
    • React笔记
    • 工具方法
    CD
    2023-01-01
    目录

    React常用自定义Hook汇总

    # UseSetState

    UseSetState: 用于方便更新 JSON 数据

    import { useCallback, useState } from "react";
    export type SetState<S extends Record<string, any>> = <K extends keyof S>(
      state: Pick<S, K> | null | ((prevState: Readonly<S>) => Pick<S, K> | S | null)
    ) => void;
    
    const useSetState = <S extends Record<string, any>>(
      initialState: S | (() => S)
    ): [S, SetState<S>] => {
      const [state, setState] = useState<S>(initialState);
    
      const setMergeState = useCallback((patch) => {
        setState((prevState) => {
          const newState = typeof patch === "function" ? patch(prevState) : patch;
    
          return newState ? { ...prevState, ...newState } : prevState;
        });
      }, []);
    
      return [state, setMergeState];
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    # useUpdateEffect

    useUpdateEffect: 用于跳过组件第一次初始化时的 effect,只关注后续的 update 变化

    import React, { useEffect, useRef } from "react";
    
    const useUpdateEffect = (effect: Function, deps: any[]) => {
      const isMounted = useRef<boolean>(false);
    
      useEffect(() => {
        return () => {
          isMounted.current = false;
        };
      }, []);
    
      useEffect(() => {
        if (!isMounted.current) {
          isMounted.current = true;
        } else {
          return effect();
        }
      }, [deps]);
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # 控制监听 DOM 元素

    export const LogContext = createContext({});
    
    export const useLog = () => {
      /* 定义一些公共参数 */
      const message = useContext(LogContext);
      const listenDOM = useRef(null);
    
      /* 分清依赖关系 */
      const reportMessage = useCallback(
        function (data, type) {
          if (type === "pv") {
            // 页面浏览量上报
            console.log("组件 pv 上报", message);
          } else if (type === "click") {
            // 点击上报
            console.log("组件 click 上报", message, data);
          }
        },
        [message]
      );
    
      useEffect(() => {
        const handleClick = function (e) {
          reportMessage(e.target, "click");
        };
        if (listenDOM.current) {
          listenDOM.current.addEventListener("click", handleClick);
        }
    
        return function () {
          listenDOM.current &&
            listenDOM.current.removeEventListener("click", handleClick);
        };
      }, [reportMessage]);
    
      return [listenDOM, reportMessage];
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37

    使用

    const Home = () => {
      const [dom, reportMessage] = useLog();
      return (
        <div>
          {/* 监听内部点击 */}
          <div ref={dom}>
            <button> 按钮 1 (内部点击) </button>
            <button> 按钮 2 (内部点击) </button>
            <button> 按钮 3 (内部点击) </button>
          </div>
          {/* 外部点击 */}
          <button
            onClick={() => {
              console.log(reportMessage);
            }}
          >
            外部点击
          </button>
        </div>
      );
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # 离开页面前确认

    import { useCallback, useEffect, useRef } from 'react'
    import { useHistory } from 'react-router-dom'
    
    export function usePromt(
        when?: boolean | (() => boolean),
        msg = '修改内容未保存,确定要关闭页面吗?'
    ) {
      const history = useHistory()
    
      const curStatus = useRef(when)
      const curMsg = useRef(msg)
    
      useEffect(() => {
        curStatus.current = when
      }, [when])
    
      useEffect(() => {
        curMsg.current = msg
      }, [msg])
    
      const onBeforeUnload = useCallback((event: any) => {
        if (typeof curStatus.current === 'function' && curStatus.current()) {
          event.preventDefault()
          event.returnValue = curMsg.current
        } else if (typeof curStatus.current === 'boolean' && curStatus.current) {
          event.preventDefault()
          event.returnValue = curMsg.current
        }
      }, [])
    
      useEffect(() => {
        const unblock = history.block(() => {
          if (
            (typeof curStatus.current === 'function' && curStatus.current()) ||
            (typeof curStatus.current === 'boolean' && curStatus.current)
          ) {
            return curMsg.current
          }
        })
    
        return () => unblock()
      }, [])
    
      useEffect(() => {
        window.addEventListener('beforeunload', onBeforeUnload)
        return () => {
          window.removeEventListener(
            'beforeunload',
            onbeforeunload as EventListenerOrEventListenerObject
          )
        }
      }, [])
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53

    使用

    usePromt(true);
    
    1

    # 请求轮询

    import { useState, useRef, useEffect } from "react";
    import axios from "axios";
    
    /**
     * React Hook: usePolling
     *
     * This hook allows for making repeated API calls at a set interval (in ms).
     * The polling can be started, stopped, and manually triggered.
     *
     * @param {string} url - API endpoint to poll data from
     * @param {number} interval - interval (in ms) to poll the API
     *
     * @return An array containing the following:
     *  - [0] boolean indicating if the polling is actively taking place
     *  - [1] function to manually trigger the poll (optional)
     *  - [2] function to stop the polling (optional)
     */
    const usePolling = (url, interval) => {
      const [polling, setPolling] = useState(false);
      const [manualTrigger, setManualTrigger] = useState(false);
      const timerRef = useRef(null);
    
      const fetchData = async () => {
        try {
          const response = await axios.get(url);
          console.log(response.data); // do something with response data
        } catch (error) {
          console.error("Error fetching data:", error);
        }
    
        if (polling || manualTrigger) {
          timerRef.current = setTimeout(fetchData, interval);
        }
      };
    
      const startPolling = () => {
        setPolling(true);
        timerRef.current = setTimeout(fetchData, interval);
      };
    
      const stopPolling = () => {
        setPolling(false);
        clearTimeout(timerRef.current);
      };
    
      useEffect(() => {
        return () => clearTimeout(timerRef.current);
      }, []);
    
      return [polling, startPolling, stopPolling];
    };
    
    export default usePolling;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53

    使用示例:

    import { useEffect } from "react";
    import usePolling from "./usePolling";
    
    const App = () => {
      const [polling, startPolling, stopPolling] = usePolling(
        "https://example.com/api/data",
        5000
      );
    
      useEffect(() => {
        // Manually trigger the poll every 10 seconds
        const manualPolling = setInterval(() => {
          startPolling();
        }, 10000);
    
        return () => clearInterval(manualPolling);
      }, [startPolling]);
    
      return (
        <div>
          <button onClick={() => startPolling()}>Start polling</button>
          <button onClick={() => stopPolling()}>Stop polling</button>
          {polling && <p>Polling in progress</p>}
        </div>
      );
    };
    
    export default App;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    # createContext

    function createContext<ContextValueType extends object | null>(
      rootComponentName: string,
      defaultContext?: ContextValueType
    ) {
      const Context = React.createContext<ContextValueType | undefined>(defaultContext);
    
      function Provider(props: ContextValueType & { children: React.ReactNode }) {
        const { children, ...context } = props;
        // Only re-memoize when prop values change
        // eslint-disable-next-line react-hooks/exhaustive-deps
        const value = React.useMemo(() => context, Object.values(context)) as ContextValueType;
        return <Context.Provider value={value}>{children}</Context.Provider>;
      }
    
      function useContext(consumerName: string) {
        const context = React.useContext(Context);
        if (context) return context;
        if (defaultContext !== undefined) return defaultContext;
        // if a defaultContext wasn't specified, it's a required context.
        throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``);
      }
    
      Provider.displayName = rootComponentName + 'Provider';
      return [Provider, useContext] as const;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    这么做的作用是什么呢? 可以利用缓存来达到无需向另外的组件传递参数实现弹窗的开关等用途,例如:

    import { createContext } from "../../utils/createContext";
    const DIALOG_NAME = "Dialog";
    import React, { useState } from "react";
    
    type DialogContextValue = {
      open: boolean;
      onOpenChange(open: boolean): void;
      onOpenToggle(): void;
      modal: boolean;
    };
    const [DialogProvider, useDialogContext] =
      createContext<DialogContextValue>(DIALOG_NAME);
    
    const Dialog: React.FC = (props: any) => {
      const {
        __scopeDialog,
        children,
        open: openProp,
        defaultOpen,
        onOpenChange,
        modal = true,
      } = props;
    
      const [open, setOpen] = useState(false);
    
      return (
        <DialogProvider
          open={open}
          onOpenChange={setOpen}
          onOpenToggle={React.useCallback(
            () => setOpen((prevOpen) => !prevOpen),
            [setOpen]
          )}
          modal={modal}
        >
          {children}
        </DialogProvider>
      );
    };
    
    Dialog.displayName = DIALOG_NAME;
    
    const TRIGGER_NAME = "DialogTrigger";
    const DialogTrigger = React.forwardRef((props: any, forwardedRef) => {
      const { ...triggerProps } = props;
      const context = useDialogContext(TRIGGER_NAME);
      const handleClick = () => {
        console.log({ context });
      };
      return (
        <button
          type="button"
          aria-haspopup="dialog"
          aria-expanded={context.open}
          {...triggerProps}
          onClick={handleClick}
        />
      );
    });
    
    DialogTrigger.displayName = TRIGGER_NAME;
    
    export { Dialog, DialogTrigger };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63

    # 右键菜单位置

    let vw = document.documentElement.clientWidth;
    let vh = document.documentElement.clientHeight;
    
    window.addEventListener('resize', () => {
     vw = document.documentElement.clientWidth;
     vh = document.documentElement.clientHeight;
    })
    
    1
    2
    3
    4
    5
    6
    7
    编辑 (opens new window)
    #JavaScript#React
    上次更新: 2023/06/05, 16:50:36
    react-router 源码及简单实现

    ← react-router 源码及简单实现

    最近更新
    01
    gsap动画库学习笔记 - 持续~
    06-05
    02
    远程组件加载方案笔记
    05-03
    03
    小程序使用笔记
    03-29
    更多文章>
    Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式