技术创作与交流平台
前端

看react-use hooks库和ahooks之后的一些收获

1

1272086709@163.com

2026年5月23日66 次阅读0 条评论

关于 useRefuseState 传递函数作为初始值

  • 在使用 useState 时,如果传递一个函数,则该函数获取对应的初始值只会执行一次。
  • useRef 可以用来存储函数,这样子函数的地址就不会发生改变。

下面示例展示了引用地址的变化与没有变化:

import { useState, useRef } from 'react';
import { useClickAway } from 'ahooks';

class A extends Object {
  constructor() {
    super();
    console.log('A...constructor');
  }
}

const map = new Map();

function logMap(ref: any, category: string) {
  const aRefCnt = map.get(ref);
  if ([null, undefined].includes(aRefCnt)) {
    map.set(ref, 0);
  } else {
    map.set(ref, parseInt(aRefCnt) + 1);
  }
  console.log('cate:', category, 'map', aRefCnt);
}

export default () => {
  const [counter, setCounter] = useState(0);
  const ref = useRef<HTMLButtonElement>(null);
  
  // 使用 useRef 传递一个函数,函数会在初始化时执行一次
  const aRef = useRef((name: string) => {
    console.log('name', name);
    new A();
  });

  // 这里传递的是一个对象实例,因此当 state 变化时,A 的构造函数每次都会重新执行
  const bRef = useRef(new A());
  console.log('bRef:', bRef.current);

  const cnt = useState(0)[0];

  // 可以看出,useState 解构出来的函数,当 state 发生变化后,它的地址也是固定的
  logMap(setCounter, 'setCounter');
  logMap(cnt, 'cnt');
  logMap(bRef.current, 'bRef.current');
  logMap(aRef.current, 'aRef.current');

  useClickAway(() => {
    // logMap(aRef.current);
    setCounter((s) => s + 1);
  }, ref);

  return (
    <div>
      <button ref={ref} type="button">
        box
      </button>
      <p>counter: {counter}</p>
    </div>
  );
};

获取 query 参数的方法

下面示例可以用于获取 URL 中的某些参数:

const getValue = (search: string, param: string) => new URLSearchParams(search).get(param);

关于闭包陷阱问题

  • 存在闭包陷阱的 Hooks 包括:useCallback, useEffect, useMemo 等,只要依赖元素为空数组,就可能存在闭包陷阱。

下面例子展示了:window.location.hash 并不会取原始值,而是会取最新的值。因为它是一个全局变量,在 React 组件外部定义的:

const onHashChange = useCallback(() => {
    setHash(window.location.hash);
  }, []);

  useLifecycles(
    () => {
      on(window, 'hashchange', onHashChange);
    },
    () => {
      off(window, 'hashchange', onHashChange);
    }
  );
  • 存在闭包陷阱的例子:
const [count, setCount] = useState(0);
const [info, setInfo] = useState({
  age: 0,
  sex: '男',
});
const setObj = useCallback(() => {
  console.log('info', info);
  console.log('count', count);
  console.log('global:', globalUserInfo.name);
}, []);
  • useSafeState(来自 ahooks)

类似于 useState,区别在于它可以防止内存泄漏。

主要场景:在一个组件开始挂载时,会执行一些副作用的异步操作,并设置 state,但这个组件可能存在卸载的情况。当组件卸载时,调用 setState 是没有意义的,会导致内存泄漏。

import React, { useState } from 'react';
import useSafeState from '../index';

const Child = () => {
  const [value, setValue] = useSafeState<string>();
  React.useEffect(() => {
    setTimeout(() => {
      setValue('data loaded from server');
    }, 5000);
  }, []);
  const text = value || 'Loading...';
  return (
    <div>{text}</div>
  );
};

export default () => {
  const [visible, setVisible] = useSafeState(true);
  return (
    <div>
      <button onClick={() => setVisible(false)}>Unmount</button>
      {visible && <Child />}
    </div>
  );
};

getBoundingClientRect()offsetWidth/offsetHeight 的区别

getBoundingClientRect() 方法返回的 widthheight,是受 transform: scalescaleXscaleY 影响的,而 offsetWidthoffsetHeight 却不会。

  • 相同点:

这两者都包含 paddingborder,实际的宽度和高度,但不包含 border

下面代码可以用于判断当前节点与父节点之间的真实距离:

export const getRectDiff = (node: HTMLElement, parentNode: HTMLElement) => {
  const nodeRect = node.getBoundingClientRect();
  const parentRect = parentNode.getBoundingClientRect();
  const scaleX = parentNode.offsetWidth / parentRect.width;
  const scaleY = parentNode.offsetHeight / parentRect.height;

  return {
    left: (nodeRect.left - parentRect.left) * scaleX,
    top: (nodeRect.top - parentRect.top) * scaleY,
    right: (nodeRect.right - parentRect.right) * scaleX,
    bottom: (nodeRect.bottom - parentRect.bottom) * scaleY,
  };
};
import { useEffect, useRef } from 'react';

// 测试一下
function Demo11() {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const { width, height } = ref.current?.getBoundingClientRect() || {};
    // 打印出来 210, 210, 会显示真实的宽度和高度
    console.log('width', width);
    console.log('height', height);
    // 不能显示出来真实的宽度和高度,显示原来的宽度和高度
    // 通过 offset 的方式
    console.log('offsetWidth', ref.current?.offsetWidth);
    console.log('offsetHeight', ref.current?.offsetHeight);
  }, []);
  return (
    <div>
      <h2>测试一下 scaleX, scaleY 对一些 JS 的影响</h2>
      <div ref={ref} style={{ 
          width: '300px', 
          height: '300px', 
          border: '1px solid red',
          padding: '10px',
          transform: 'scale(0.7)',
          overflowX: 'auto',
          overflowY: 'auto',
      }}>
        <p style={{
            // whiteSpace: 'nowrap',
            maxWidth: '100%',
        }}>开心每一天开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天,
        开心每一天开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天,
        开心每一天开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天,
        开心每一天开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天,
        开心每一天开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天,
        开心每一天开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天,
        开心每一天开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天,
        开心每一天开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天, 开心每一天开心每一天,
        </p>
      </div>
    </div>
  );
}

export default Demo11;
加载评论中...