美丽的公主和它的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>
  );
}
相关文章
|
15天前
|
前端开发 JavaScript 定位技术
Docusaurus框架——react+antd+echarts自定义mdx生成图表代码解释文档
Docusaurus框架——react+antd+echarts自定义mdx生成图表代码解释文档
28 0
|
16天前
|
前端开发 JavaScript CDN
前端react 18.2整合ckeditor富文本编辑器——配置插件、自定义toolbar工具栏(一)
前端react 18.2整合ckeditor富文本编辑器——配置插件、自定义toolbar工具栏
30 0
|
29天前
|
前端开发 JavaScript
【边做边学】React Hooks (二)——useEffect Hook
【边做边学】React Hooks (二)——useEffect Hook
|
1月前
|
前端开发 JavaScript
React中useEffect Hook使用纠错
React中useEffect Hook使用纠错
17 0
|
5月前
|
自然语言处理 前端开发 JavaScript
说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?
说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?
52 0
|
5月前
|
自然语言处理 前端开发 JavaScript
美丽的公主和它的27个React 自定义 Hook(四)
美丽的公主和它的27个React 自定义 Hook(四)
|
19天前
|
前端开发 测试技术 开发工具
探索前端框架React Hooks的优势与应用
本文将深入探讨前端框架React Hooks的优势与应用。通过分析React Hooks的特性以及实际应用案例,帮助读者更好地理解和运用这一现代化的前端开发工具。
|
11天前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
【4月更文挑战第30天】对比 Flutter(Dart,强类型,Google支持,快速热重载,高性能渲染)与 React Native(JavaScript,庞大生态,热重载,依赖原生渲染),文章讨论了开发语言、生态系统、性能、开发体验、学习曲线、社区支持及项目选择因素。两者各有优势,选择取决于项目需求、团队技能和长期维护考虑。参考文献包括官方文档和性能比较文章。
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
|
11天前
|
前端开发 JavaScript 开发者
【专栏:HTML与CSS前端技术趋势篇】前端框架(React/Vue/Angular)与HTML/CSS的结合使用
【4月更文挑战第30天】前端框架React、Vue和Angular助力UI开发,通过组件化、状态管理和虚拟DOM提升效率。这些框架与HTML/CSS结合,使用模板语法、样式管理及组件化思想。未来趋势包括框架简化、Web组件标准采用和CSS在框架中角色的演变。开发者需紧跟技术发展,掌握新工具,提升开发效能。
|
12天前
|
开发框架 缓存 前端开发