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

155 阅读4分钟
关于useRef和useState 传递函数作为初始值
  • useState 如果传递一个函数,则这个函数获取对应的初始值只会执行一次。
  • useRef 可以用来存储函数,这样子函数地址可以不发生改变。

如下所示DEMO,说明引用地址的变化与没有变化

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, useEfffect, 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()方法返回的width,height,是受transform: scale,scaleX,scaleY影响的,offsetWidth 和offsetHeight 却不会。

  • 相同点:

这二者都包含padding,border,实际的宽度和高度,不包含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,scale对一些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;