关于前端:解读-ahooks-源码系列Dev篇useTrackedEffect-和-useWhyDidYouUpdate

3次阅读

共计 3943 个字符,预计需要花费 10 分钟才能阅读完成。

前言

本文是 ahooks 源码(v3.7.4)系列的第六篇——Dev 篇,该篇次要是帮助开发调优的 Hook,只有两个

往期文章:

  • 【解读 ahooks 源码系列】(开篇)如何获取和监听 DOM 元素:useEffectWithTarget
  • 【解读 ahooks 源码系列】DOM 篇(一):useEventListener、useClickAway、useDocumentVisibility、useDrop、useDrag
  • 【解读 ahooks 源码系列】DOM 篇(二):useEventTarget、useExternal、useTitle、useFavicon、useFullscreen、useHover
  • 【解读 ahooks 源码系列】DOM 篇(三):useMutationObserver、useInViewport、useKeyPress、useLongPress
  • 【解读 ahooks 源码系列】DOM 篇(四):useMouse、useResponsive、useScroll、useSize、useFocusWithin

本文次要解读 useTrackedEffectuseWhyDidYouUpdate 的源码实现

useTrackedEffect

追踪是哪个依赖变动触发了 useEffect 的执行。

官网文档

根本用法

查看每次 effect 执行时发生变化的依赖项

官网在线 Demo

import React, {useState} from 'react';
import {useTrackedEffect} from 'ahooks';

export default () => {const [count, setCount] = useState(0);
  const [count2, setCount2] = useState(0);

  useTrackedEffect((changes) => {console.log('Index of changed dependencies:', changes);
    },
    [count, count2],
  );

  return (
    <div>
      <p>Please open the browser console to view the output!</p>
      <div>
        <p>Count: {count}</p>
        <button onClick={() => setCount((c) => c + 1)}>count + 1</button>
      </div>
      <div style={{marginTop: 16}}>
        <p>Count2: {count2}</p>
        <button onClick={() => setCount2((c) => c + 1)}>count + 1</button>
      </div>
    </div>
  );
};

外围实现

实现原理:通过 uesRef 记录上一次依赖的值,在以后执行的时候,判断以后依赖值和上次依赖值之间有无变动

  • changes:变动的依赖 index 数组
  • previousDeps:上一个依赖
  • currentDeps:以后依赖
useTrackedEffect(effect: (changes: [], previousDeps: [], currentDeps: []) => (void | (() => void | undefined)),
  deps?: deps,
)

源码实现

const useTrackedEffect = (effect: Effect, deps?: DependencyList) => {const previousDepsRef = useRef<DependencyList>(); // 记录上次依赖

  useEffect(() => {
    // 判断依赖前后的 changes
    const changes = diffTwoDeps(previousDepsRef.current, deps);
    const previousDeps = previousDepsRef.current; // 赋值上次依赖
    previousDepsRef.current = deps;
    return effect(changes, previousDeps, deps);
  }, deps);
};

diffTwoDeps 办法实现:

  1. 对前后两个 deps 依赖项列表应用 Object.is 进行严格相等性查看
  2. 如果定义了 deps1,则遍历 deps1 并将每个元素与来自 deps2 的对应索引元素进行比拟(因为这个函数只在这个钩子中应用,所以假如两个 deps 列表的长度总是雷同的)

    • 相等返回 -1
    • 不相等返回索引值
    • 过滤小于 0 的值(即校验后果相等的).filter((ele) => ele >= 0),最终只返回变动的数组索引值
const diffTwoDeps = (deps1?: DependencyList, deps2?: DependencyList) => {
  // 对前后两个 deps 依赖项列表应用 Object.is 进行严格相等性查看
  return deps1
    ? deps1
        .map((_ele, idx) => (!Object.is(deps1[idx], deps2?.[idx]) ? idx : -1))
        .filter((ele) => ele >= 0) // 过滤相等值
    : deps2
    ? deps2.map((_ele, idx) => idx)
    : [];};

残缺源码

useWhyDidYouUpdate

帮忙开发者排查是那个属性扭转导致了组件的 rerender。

官网文档

根本用法

官网在线 Demo

关上控制台,能够看到扭转的属性。

import {useWhyDidYouUpdate} from 'ahooks';
import React, {useState} from 'react';

const Demo: React.FC<{count: number}> = (props) => {const [randomNum, setRandomNum] = useState(Math.random());

  useWhyDidYouUpdate('useWhyDidYouUpdateComponent', { ...props, randomNum});

  return (
    <div>
      <div>
        <span>number: {props.count}</span>
      </div>
      <div>
        randomNum: {randomNum}
        <button onClick={() => setRandomNum(Math.random)} style={{marginLeft: 8}}>
          🎲
        </button>
      </div>
    </div>
  );
};

export default () => {const [count, setCount] = useState(0);

  return (
    <div>
      <Demo count={count} />
      <div>
        <button onClick={() => setCount((prevCount) => prevCount - 1)}>count -</button>
        <button onClick={() => setCount((prevCount) => prevCount + 1)} style={{marginLeft: 8}}>
          count +
        </button>
      </div>
      <p style={{marginTop: 8}}>Please open the browser console to view the output!</p>
    </div>
  );
};

应用场景

  • 查看哪些 props 产生扭转
  • 帮助找出有效渲染:useWhyDidYouUpdate 会通知咱们监听数据中所有变动的数据,不论它是不是有效的更新,但还须要咱们本人来辨别辨认哪些是有效更新的属性,从而进行优化。

实现思路

  1. 应用 useRef 申明 prevProps 变量(确保拿到最新值),用来保留上一次的 props
  2. 每次 useEffect 更新都置空 changedProps 对象,并将新旧 props 对象的属性提取进去,生成属性数组 allKeys
  3. 遍历 allKeys 数组,去比照新旧属性值。如果不同,则记录到 changedProps 对象中
  4. 如果 changedProps 有长度,则输入扭转的内容,并更新 prevProps

外围实现

实现原理:通过 useEffect 拿到上一次 props 值 和以后 props 值 进行遍历比拟,如果值发送扭转则输入

// componentName:观测组件的名称
// props:须要观测的数据(以后组件 state 或者传入的 props 等可能导致 rerender 的数据)export default function useWhyDidYouUpdate(componentName: string, props: IProps) {const prevProps = useRef<IProps>({});

  useEffect(() => {if (prevProps.current) {
      // 获取所有的须要观测的数据
      const allKeys = Object.keys({...prevProps.current, ...props});
      const changedProps: IProps = {}; // 产生扭转的属性值

      allKeys.forEach((key) => {
        // 通过 Object.is 判断是否进行更新
        if (!Object.is(prevProps.current[key], props[key])) {changedProps[key] = {from: prevProps.current[key],
            to: props[key],
          };
        }
      });

      // 遍历扭转的属性,有值则输入日志
      if (Object.keys(changedProps).length) {console.log('[why-did-you-update]', componentName, changedProps);
      }
    }

    prevProps.current = props;
  });
}

残缺源码

正文完
 0