美丽的公主和它的27个React 自定义 Hook(三)

简介: 美丽的公主和它的27个React 自定义 Hook(三)

3.13 useRenderCount

import { useEffect, useRef } from "react";
export default function useRenderCount(): number {
  const count = useRef(1);
  useEffect(() => {
    count.current++;
  });
  return count.current;
}

useRenderCount钩子利用了React的useEffectuseRef钩子来计算渲染次数。每次渲染都会增加计数,为我们提供关于组件渲染频率的实时反馈。

它提供了一种清晰而简洁的方式来监视渲染行为,这对性能优化和调试非常重要。

使用场景

这个多功能的钩子可以应用在各种场景中。例如,当我们开发一个展现出意外渲染模式的复杂组件时,useRenderCount可以通过显示准确的渲染次数来帮助我们定位问题。它还对于衡量某些优化或重构技巧的影响非常有用。

import useRenderCount from "@hooks/useRenderCount";
import useToggle from "@hooks/useToggle";
export default function RenderCountComponent() {
  const [boolean, toggle] = useToggle(false);
  const renderCount = useRenderCount();
  return (
    <>
      <div>{boolean.toString()}</div>
      <div>组件渲染次数:{renderCount}</div>
      <button onClick={() => toggle(!boolean)}>状态切换</button>
    </>
  );
}


3.14 useDebugInformation

import { useEffect, useRef } from "react";
import useRenderCount from "@hooks/useRenderCount";
type ChangedProps = Record<string, { previous: unknown; current: unknown }>;
type DebugInformationResult = {
  count: number;
  changedProps: ChangedProps;
  timeSinceLastRender: number;
  lastRenderTimestamp: number;
};
export default function useDebugInformation(
  componentName: string,
  props: Record<string, unknown>
): DebugInformationResult {
  const count = useRenderCount();
  const changedProps = useRef<ChangedProps>({});
  const previousProps = useRef(props);
  const lastRenderTimestamp = useRef(Date.now());
  const propKeys = Object.keys({ ...props, ...previousProps.current });
  changedProps.current = propKeys.reduce((obj, key) => {
    if (props[key] === previousProps.current[key]) return obj;
    return {
      ...obj,
      [key]: { previous: previousProps.current[key], current: props[key] },
    };
  }, {});
  const info: DebugInformationResult = {
    count,
    changedProps: changedProps.current,
    timeSinceLastRender: Date.now() - lastRenderTimestamp.current,
    lastRenderTimestamp: lastRenderTimestamp.current,
  };
  useEffect(() => {
    previousProps.current = props;
    lastRenderTimestamp.current = Date.now();
    console.log("[debug-info]", componentName, info);
  });
  return info;
}

调试React组件时,获取有关渲染和属性更改的详细信息可以非常有用。此时useDebugInformation自定义钩子派上用场的地方。这个钩子为开发人员提供了有关其组件行为的宝贵见解,并有助于识别性能瓶颈或意外的渲染模式。

useDebugInformation让我们可以获得大量的调试数据。该钩子跟踪渲染次数更改的属性自上次渲染以来的时间以及上次渲染的时间戳。这些全面的信息使我们能够更有效地分析组件行为,并在优化应用程序时做出明智的决策。

使用场景

useDebugInformation钩子可以应用在各种情境中。例如,我们正在开发一个复杂的表单组件,其中某些属性会触发更新或影响渲染。通过使用useDebugInformation,我们可以轻松地监视这些属性对组件性能的影响以及是否发生不必要的重新渲染。此外,当调查特定组件为什么没有如预期般更新或在性能关键的应用程序中微调优化时,这个钩子也可能非常有价值。

通过将组件名称属性传递给钩子,我们可以获得一个包含所有相关调试数据的info对象。然后,可以将该对象显示或记录以进行进一步分析。

import useDebugInformation from "@hooks/useDebugInformation";
import useToggle from "@hooks/useToggle";
import { useState } from "react";
export default function DebugInformationComponent() {
  const [boolean, toggle] = useToggle(false);
  const [count, setCount] = useState(0);
  return (
    <>
      <ChildComponent boolean={boolean} count={count} />
      <button onClick={() => toggle(!boolean)}>切换状态</button>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>
        数字+1
      </button>
    </>
  );
}
function ChildComponent(props) {
  const info = useDebugInformation("ChildComponent", props);
  return (
    <>
      <div>{props.boolean.toString()}</div>
      <div>{props.count}</div>
      <div>{JSON.stringify(info, null, 2)}</div>
    </>
  );
}

3.15 useGeolocation

import { useState, useEffect } from "react";
type GeolocationOptions = PositionOptions;
type GeolocationHookReturn = {
  loading: boolean;
  error: GeolocationPositionError | null;
  data: GeolocationCoordinates;
};
export default function useGeolocation(
  options?: GeolocationOptions
): GeolocationHookReturn {
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<GeolocationPositionError | null>(null);
  const [data, setData] = useState<GeolocationCoordinates>(
    {} as GeolocationCoordinates
  );
  useEffect(() => {
    const successHandler = (e: GeolocationPosition) => {
      setLoading(false);
      setError(null);
      setData(e.coords);
    };
    const errorHandler = (e: GeolocationPositionError) => {
      setError(e);
      setLoading(false);
    };
    navigator.geolocation.getCurrentPosition(
      successHandler,
      errorHandler,
      options
    );
    const id = navigator.geolocation.watchPosition(
      successHandler,
      errorHandler,
      options
    );
    return () => navigator.geolocation.clearWatch(id);
  }, [options]);
  return { loading, error, data };
}

useGeolocation钩子利用了React的useStateuseEffect钩子来管理加载、错误和地理位置数据的状态。它接受一个可选的options参数,以自定义地理位置行为,允许我们根据特定需求微调准确性和其他设置。

该钩子自动处理加载状态,当获取地理位置数据时更新它,并在过程中出现任何问题时设置错误状态。

useGeolocation钩子还包含了Geolocation API的watchPosition方法,它可以连续监视用户的位置。这在需要实时更新用户位置的情况下很有用,比如在跟踪应用程序或交互地图中。

使用场景

数据对象包含纬度经度值,允许我们轻松地在UI上显示用户的位置。加载变量通知我们地理位置检索的当前状态,错误变量在适用时提供任何错误消息。

import useGeolocation from "@hooks/useGeolocation"
export default function GeolocationComponent() {
    const {
        loading,
        error,
        data: { latitude, longitude },
    } = useGeolocation()
    return (
        <>
            <div>加载状态: {loading.toString()}</div>
            <div>加载是否失败: {error?.message}</div>
            <div>
                纬度:{latitude} x 经度:{longitude}
            </div>
        </>
    )
}

3.16 useHover

import { useState, RefObject } from "react";
import useEventListener from "@hooks/useEventListener";
export default function useHover(ref: RefObject<HTMLElement>): boolean {
  const [hovered, setHovered] = useState<boolean>(false);
  useEventListener("mouseover", () => setHovered(true), ref);
  useEventListener("mouseout", () => setHovered(false), ref);
  return hovered;
}

这个钩子利用了React的useStateuseEventListener钩子,用于跟踪鼠标悬停状态。通过简单地将一个ref传递给useHover钩子,我们可以开始接收准确的鼠标悬停事件。该钩子监听mouseovermouseout事件,并相应地更新悬停状态。

使用场景

useHover可以在各种情况下使用。无论我们需要在悬停时突出显示元素、触发其他操作或动态更改样式,这个自定义钩子都能胜任。

import { useRef } from "react";
import useHover from "@hooks/useHover";
export default function HoverComponent() {
  const elementRef = useRef<HTMLDivElement>(null);
  const hovered = useHover(elementRef);
  return (
    <section>
      <div
        ref={elementRef}
        style={{
          backgroundColor: hovered ? "blue" : "red",
          width: "100px",
          height: "100px",
          position: "absolute",
          top: "calc(50% - 50px)",
          left: "calc(50% - 50px)",
        }}
      >
        {hovered ? "我处于hover状态" : "正常状态"}
      </div>
    </section>
  );
}

通过将useHover钩子应用于elementRef,div的背景颜色在悬停状态下动态变为蓝色或红色。


3.17 useLongPress

import useEventListener from "@hooks/useEventListener";
import useTimeout from "@hooks/useTimeout";
import useEffectOnce from "@hooks/useEffectOnce";
import { RefObject } from "react";
type LongPressCallback = () => void;
type LongPressOptions = { delay?: number };
export default function useLongPress(
  ref: RefObject<HTMLElement>,
  cb: LongPressCallback,
  { delay = 250 }: LongPressOptions = {}
) {
  const { reset, clear } = useTimeout(cb, delay);
  useEffectOnce(clear);
  useEventListener("mousedown", reset, ref);
  useEventListener("touchstart", reset, ref);
  useEventListener("mouseup", clear, ref);
  useEventListener("mouseleave", clear, ref);
  useEventListener("touchend", clear, ref);
}

通过利用这个钩子,开发人员可以轻松地在其React应用程序中的任何元素上定义长按操作。只需几行代码,这个钩子就会处理跟踪长按持续时间和触发相关回调函数。

使用场景

无论我们正在开发触摸敏感的用户界面、实现上下文菜单或创建自定义手势,这个钩子都证明是一个有价值的工具。从移动应用到复杂的Web界面,都有用武之地。

import { useRef } from "react";
import useLongPress from "@hooks/useLongPress";
export default function LongPressComponent() {
  const elementRef = useRef<HTMLDivElement>(null);
  useLongPress(elementRef, () => alert("触发回调"));
  return (
    <>
      <div
        ref={elementRef}
        style={{
          backgroundColor: "red",
          width: "100px",
          height: "100px",
          position: "absolute",
          top: "calc(50% - 50px)",
          left: "calc(50% - 50px)",
        }}
      >
        我是一个待测试的元素
      </div>
    </>
  );
}

3.18 useOnlineStatus

import { useState } from "react";
import useEventListener from "@hooks/useEventListener";
export default function useOnlineStatus(): boolean {
  const [online, setOnline] = useState<boolean>(navigator.onLine);
  useEventListener("online", () => setOnline(navigator.onLine));
  useEventListener("offline", () => setOnline(navigator.onLine));
  return online;
}

我们可以轻松地访问用户的在线状态。该钩子内部使用navigator.onLine属性来确定初始的在线状态,并在用户的连接状态发生变化时动态更新它。

它返回一个布尔值,指示用户当前是在线还是离线。然后,我们可以利用这些信息来为用户提供实时反馈或根据他们的在线状态做出决策。

使用场景

useOnlineStatus钩子可以在各种情境中找到应用。例如,我们可以通过在用户失去互联网连接时显示提示来提高用户体验,以便他们采取适当的行动。此外,我们可以根据用户的在线状态有条件地渲染某些组件或触发特定的行为。

import useOnlineStatus from "@hooks/useOnlineStatus"
export default function OnlineStatusComponent() {
    const online = useOnlineStatus()
    return <div>用户是否在线{online.toString()}</div>
}

3.19 useOnScreen

import { useEffect, useState, RefObject } from "react";
export default function useOnScreen(
  ref: RefObject<HTMLElement>,
  rootMargin?: string = "0px"
): boolean {
  const [isVisible, setIsVisible] = useState<boolean>(false);
  useEffect(() => {
    if (ref.current == null) return;
    const observer = new IntersectionObserver(
      ([entry]) => setIsVisible(entry.isIntersecting),
      { rootMargin }
    );
    observer.observe(ref.current);
    return () => {
      if (ref.current == null) return;
      observer.unobserve(ref.current);
    };
  }, [ref, rootMargin]);
  return isVisible;
}

useOnScreen钩子充分利用了Intersection Observer API的强大功能,只需简单地提供一个引用到我们想要监视的元素,useOnScreen会在该元素进入或离开视口时通知我们。

使用场景

我们可以在我们希望触发动画、延迟加载图像或在用户滚动时加载额外内容的情况下,使用这个Hook。

要使用这个钩子,首先将其导入到我们的组件文件中。然后,使用useRef钩子创建一个引用,以定位所需的元素。将引用作为useOnScreen钩子的第一个参数传递,我们还可以提供一个可选的rootMargin值来调整可见阈值。

import { useRef } from "react";
import useOnScreen from "@hooks/useOnScreen";
export default function OnScreenComponentComponent() {
  const headerTwoRef = useRef<HTMLHeadingElement>(null);
  const visible = useOnScreen(headerTwoRef, "-100px");
  return (
    <div>
      <h1>Header</h1>
      <div>
        修改此元素的高度,使页面可滚动,在滚动过程中,可查看待验证元素的可见性
      </div>
      <h1 ref={headerTwoRef}>待验证元素 {visible && "(Visible)"}</h1>
      <div>...</div>
    </div>
  );
}

3.20 usePrevious

import { useRef } from "react";
export default function usePrevious<T>(value: T): T | undefined {
  const currentRef = useRef<T | undefined>(value);
  const previousRef = useRef<T | undefined>();
  if (currentRef.current !== value) {
    previousRef.current = currentRef.current;
    currentRef.current = value;
  }
  return previousRef.current;
}

通过使用useRef,这个钩子可以高效地存储当前值和上一个值,并在值更改时更新它们。通过比较当前值和上一个值,我们可以轻松地检测和响应组件数据的变化。

例如,我们可以利用usePrevious来比较和可视化数据的变化,跟踪状态转换,或实现撤销/重做功能。此外,在处理表单、动画和任何需要访问以前值的情况下,它都可能对我们的应用程序逻辑至关重要。

使用场景

import { useState } from "react";
import usePrevious from "@hooks/usePrevious";
export default function PreviousComponent() {
  const [count, setCount] = useState(0);
  const previousCount = usePrevious(count);
  return (
    <div>
      <div>当前视图的值: {count}</div>
      <div>之前视图的值(初始化时为空):{previousCount}</div>
      <button onClick={() => setCount((currentCount) => currentCount + 1)}>
        数字+1
      </button>
    </div>
  );
}
相关文章
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
184 2
|
4月前
|
设计模式 存储 前端开发
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
React开发设计模式及原则概念问题之自定义Hooks的作用是什么,自定义Hooks设计时要遵循什么原则呢
|
3月前
|
前端开发
React给antd中TreeSelect组件左侧加自定义图标icon
本文介绍了如何在React中为Ant Design的TreeSelect组件的每个树节点添加自定义图标,并解决了因缺少key属性而导致的警告问题,展示了如何通过递归函数处理treeData数据并为每个节点添加图标。
138 2
React给antd中TreeSelect组件左侧加自定义图标icon
|
3月前
|
前端开发 Python
React技术栈-React路由插件之自定义组件标签
关于React技术栈中React路由插件自定义组件标签的教程。
59 4
React技术栈-React路由插件之自定义组件标签
|
2月前
|
前端开发 JavaScript API
自定义React Hooks综合指南
本文介绍了React Hooks及其在组件开发中的作用,重点讲解了自定义Hook的创建和使用方法。通过实例展示了如何创建`useWindowWidth`、`useFetch`和`useForm`等自定义Hook,并分享了使用自定义Hook的最佳实践。文章强调了自定义Hook在提高代码复用性和组件可维护性方面的重要性。
62 0
|
4月前
|
前端开发 JavaScript
|
4月前
|
前端开发
React 中的 Hook 概念
【8月更文挑战第31天】
37 0
|
5月前
|
前端开发
React useImperativeHandle Hook
【7月更文挑战第1天】React useImperativeHandle Hook
30 3
|
5月前
|
前端开发 JavaScript 数据格式
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
96 1
|
5月前
|
前端开发
Vue3 【仿 react 的 hook】封装 useTitle
Vue3 【仿 react 的 hook】封装 useTitle
54 0