美丽的公主和它的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>
  );
}
相关文章
|
3月前
|
自然语言处理 前端开发 JavaScript
说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?
说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?
27 0
|
3月前
|
自然语言处理 前端开发 JavaScript
美丽的公主和它的27个React 自定义 Hook(四)
美丽的公主和它的27个React 自定义 Hook(四)
|
3月前
|
存储 JSON 前端开发
美丽的公主和它的27个React 自定义 Hook(二)
美丽的公主和它的27个React 自定义 Hook(二)
|
3月前
|
前端开发 JavaScript API
美丽的公主和它的27个React 自定义 Hook(一)
美丽的公主和它的27个React 自定义 Hook(一)
|
3月前
|
前端开发
react hook 生命周期
react hook 生命周期
26 0
|
前端开发
React Native自定义导航栏
之前我们学习了可触摸组件和页面导航的使用的使用: 从零学React Native之09可触摸组件 … 从零学React Native之03页面导航 … 经过之前的学习, 我们可以完成一个自定义导航栏了, 效果如下: 我们需要创建一个 NaviBar.js 用来显示顶部的导航栏, 还需要四个界面(Page1.js,Page2.js,Page3.js,Page4.js
1526 0
|
2月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
52 0
|
2月前
|
存储 前端开发 JavaScript
【第34期】一文学会React组件传值
【第34期】一文学会React组件传值
21 0
|
2月前
|
前端开发
【第31期】一文学会用React Hooks组件编写组件
【第31期】一文学会用React Hooks组件编写组件
24 0
|
2月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
24 0