自从学了 react-use 源码,我写自定义 React Hooks 越来越顺了~

1. 前言

大家好,我是若川。我倾力持续组织了一年多源码共读,感兴趣的可以加我微信 lxchuan12 参与。另外,想学源码,极力推荐关注我写的专栏《学习源码整体架构系列》,目前是掘金关注人数(4.7k+人)第一的专栏,写有20余篇源码文章。 最近 React 出了 新文档 react.dev[1],新中文文档 zh-hans.react.dev[2]

现在用 react 开发离不开各种 hooks。学习各种 hooks 的工具库,有助于我们更好的使用和理解 hooks 。前端社区中有活跃的 ahooks[3]。不过,这次我们来学习目前 36.2k star 的 react-use[4] 库。

react-use 文档[5]是用 storybook[6] 搭建的。

如果公司项目需要搭建组件库或者 hooks、工具库等,storybook[7] 或许是不错的选择。

react-use 中文翻译仓库[8],最后更新是2年前,可能有点老。

2. 环境准备

看一个开源仓库,第一步一般是看 README.mdcontributing.md 贡献文档。第二步的克隆下来。按照贡献指南文档,把项目跑起来。

贡献文档中有如下文档。

2.1 创建一个新 hook 的步骤

  1. 创建 src/useYourHookName.tsstories/useYourHookName.story.tsx,然后运行 yarn start

  2. 创建 tests/useYourHookName.test.ts,运行 yarn test:watch 监听测试用例执行。

  3. 创建 docs/useYourHookName.md 文档。

  4. src/index.ts 文件导出你写的 hook,然后添加你的 hookREAMDE.md 中。

我们可以得知具体要做什么,新增 hook 关联哪些文件。

# 推荐克隆我的仓库
git clone https://github.com/lxchuan12/react-use-analysis.git
cd react-use-analysis/react-use
# 也可以克隆官方项目
git clone https://github.com/streamich/react-use.git
cd react-use
yarn install
yarn start

克隆项目到本地,安装依赖完成后,执行 yarn start

e19752d3cc26cc2dec24f00a13f68dc2.png
命令终端运行 yarn start

本地环境打开 useEffectOnce docshttp://localhost:6008/?path=/story/lifecycle-useeffectonce--docs

我们先挑选这个 useEffectOnce 简单的 hook 来分析。

2.2 useEffectOnce

2.2.1 react-use/src/useEffectOnce.ts
// react-use/src/useEffectOnce.ts
import { EffectCallback, useEffect } from 'react';

// 源码非常简单,不依赖任何参数的函数。

const useEffectOnce = (effect: EffectCallback) => {
  useEffect(effect, []);
};

export default useEffectOnce;

我们来看测试用例,直接使用测试用例调试 useEffectOnce 源码。

我之前写过相关文章。可以参考学习。据说90%的人不知道可以用测试用例(Vitest)调试开源项目(Vue3) 源码

我装了 jestjest runner vscode 插件,装完后测试用例中会直接显示 run、和 debug 按钮。还在装了 vitestvitest runner vscode 插件,装完后测试用例中会直接显示 run(vitest)debug(vitest) 按钮。

如下图所示。

d42fcca48160f45b1f5d4a333b69144f.png
runner

这个项目使用的是 jest。于是我点击最右侧的 debug

32f37b9191ef5fb57a5b9c16d67c404d.jpeg

调试图
2.2.2 react-use/tests/useEffectOnce.test.ts
// react-use/tests/useEffectOnce.test.ts
import { renderHook } from '@testing-library/react-hooks';
import { useEffectOnce } from '../src';

// mock 函数
const mockEffectCleanup = jest.fn();
const mockEffectCallback = jest.fn().mockReturnValue(mockEffectCleanup);

it('should run provided effect only once', () => {
  const { rerender } = renderHook(() => useEffectOnce(mockEffectCallback));
  // 只调用一次
  expect(mockEffectCallback).toHaveBeenCalledTimes(1);

  // 重新渲染时,只调用一次
  rerender();
  expect(mockEffectCallback).toHaveBeenCalledTimes(1);
});

it('should run clean-up provided on unmount', () => {
  const { unmount } = renderHook(() => useEffectOnce(mockEffectCallback));
  expect(mockEffectCleanup).not.toHaveBeenCalled();

  unmount();
  // 卸载时 执行一次
  expect(mockEffectCleanup).toHaveBeenCalledTimes(1);
});
2.2.3 react-use/stories/useEffectOnce.story.tsx

xxx.story.tsx 渲染组件,可以直接操作。Demodocs

// react-use/stories/useEffectOnce.story.tsx
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import { useEffectOnce } from '../src';
import ConsoleStory from './util/ConsoleStory';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
  useEffectOnce(() => {
    console.log('Running effect once on mount');

    return () => {
      console.log('Running clean-up of effect on unmount');
    };
  });

  return <ConsoleStory />;
};

storiesOf('Lifecycle/useEffectOnce', module)
  .add('Docs', () => <ShowDocs md={require('../docs/useEffectOnce.md')} />)
  .add('Demo', () => <Demo />);

docs/useEffectOnce.md 省略,基本跟测试用例一样。可以说测试用例就是活文档

接下来我们来看其他的 hooks 源码,限于篇幅,主要就讲述源码,不包含测试用例、文档、story

TS 也不会过多描述。如果对TS不太熟悉,推荐学习这个《TypeScript 入门教程》[9]

我们先来看 Sensors 行为部分。

3. Sensors 行为

3.1 useIdle

useIdle docs[10] |useIdle demo[11]

tracks whether user is being inactive. 跟踪用户是否处于非活动状态。

主要是:监听用户行为的事件(默认的 'mousemove', 'mousedown', 'resize', 'keydown', 'touchstart', 'wheel' ),指定时间内没有用户操作行为就是非活动状态。

import { useEffect, useState } from 'react';
// 防抖、节流
import { throttle } from 'throttle-debounce';
// 事件解绑和监听函数
import { off, on } from './misc/util';

// 监听默认事件
const defaultEvents = ['mousemove', 'mousedown', 'resize', 'keydown', 'touchstart', 'wheel'];
const oneMinute = 60e3;

const useIdle = (
  ms: number = oneMinute,
  initialState: boolean = false,
  events: string[] = defaultEvents
): boolean => {
  const [state, setState] = useState<boolean>(initialState);

  useEffect(() => {
    let mounted = true;
    let timeout: any;
    let localState: boolean = state;
    const set = (newState: boolean) => {
      if (mounted) {
        localState = newState;
        setState(newState);
      }
    };

    const onEvent = throttle(50, () => {
      if (localState) {
        set(false);
      }

      clearTimeout(timeout);
      timeout = setTimeout(() => set(true), ms);
    });
    const onVisibility = () => {
      if (!document.hidden) {
        onEvent();
      }
    };

    for (let i = 0; i < events.length; i++) {
      on(window, events[i], onEvent);
    }
    on(document, 'visibilitychange', onVisibility);

    timeout = setTimeout(() => set(true), ms);

    return () => {
      mounted = false;

      // 销毁 解绑事件
      for (let i = 0; i < events.length; i++) {
        off(window, events[i], onEvent);
      }
      off(document, 'visibilitychange', onVisibility);
    };
  }, [ms, events]);

  return state;
};

export default useIdle;

我们接着来看,useLocation hook

3.2 useLocation

useLocation docs[12] |useLocation demo[13]

React sensor hook that tracks brower's location.

主要获取 window.location 等对象信息。

mdn History API[14]

阮一峰老师的网道:history[15]阮一峰老师的网道:location[16]

自定义事件 mdn 创建和触发 events[17]

import { useEffect, useState } from 'react';
// 判断浏览器
import { isBrowser, off, on } from './misc/util';

const patchHistoryMethod = (method) => {
  const history = window.history;
  const original = history[method];

  history[method] = function (state) {
    // 原先函数
    const result = original.apply(this, arguments);
    // 自定义事件 new Event 、 dispatchEvent
    const event = new Event(method.toLowerCase());

    (event as any).state = state;

    window.dispatchEvent(event);

    return result;
  };
};

if (isBrowser) {
  patchHistoryMethod('pushState');
  patchHistoryMethod('replaceState');
}
// 省略 LocationSensorState 类型

const useLocationServer = (): LocationSensorState => ({
  trigger: 'load',
  length: 1,
});

const buildState = (trigger: string) => {
  const { state, length } = window.history;

  const { hash, host, hostname, href, origin, pathname, port, protocol, search } = window.location;

  return {
    trigger,
    state,
    length,
    hash,
    host,
    hostname,
    href,
    origin,
    pathname,
    port,
    protocol,
    search,
  };
};

const useLocationBrowser = (): LocationSensorState => {
  const [state, setState] = useState(buildState('load'));

  useEffect(() => {
    const onPopstate = () => setState(buildState('popstate'));
    const onPushstate = () => setState(buildState('pushstate'));
    const onReplacestate = () => setState(buildState('replacestate'));

    on(window, 'popstate', onPopstate);
    on(window, 'pushstate', onPushstate);
    on(window, 'replacestate', onReplacestate);

    return () => {
      off(window, 'popstate', onPopstate);
      off(window, 'pushstate', onPushstate);
      off(window, 'replacestate', onReplacestate);
    };
  }, []);

  return state;
};

const hasEventConstructor = typeof Event === 'function';

export default isBrowser && hasEventConstructor ? useLocationBrowser : useLocationServer;

接着我们继续来看 State 状态部分。

4. State 状态

4.1 useFirstMountState

useFirstMountState docs[18] | useFirstMountState demo[19]

Returns true if component is just mounted (on first render) and false otherwise. 若组件刚刚加载(在第一次渲染时),则返回 true,否则返回 false

import { useRef } from 'react';

export function useFirstMountState(): boolean {
  const isFirst = useRef(true);

  if (isFirst.current) {
    isFirst.current = false;

    return true;
  }

  return isFirst.current;
}

4.2 usePrevious

usePrevious docs[20] |usePrevious demo[21]

React state hook that returns the previous state as described in the React hooks FAQ. 保留上一次的状态。

利用 useRef 的不变性。

import { useEffect, useRef } from 'react';

export default function usePrevious<T>(state: T): T | undefined {
  const ref = useRef<T>();

  useEffect(() => {
    ref.current = state;
  });

  return ref.current;
}

4.3 useSet

useSet docs[22] |useSet demo[23]

React state hook that tracks a Set.

new Set 的 hooks 用法。 useSet 可以用来列表展开、收起等其他场景。 返回 [set ,{add, remove, toggle, reset, has }]

import { useCallback, useMemo, useState } from 'react';

export interface StableActions<K> {
  add: (key: K) => void;
  remove: (key: K) => void;
  toggle: (key: K) => void;
  reset: () => void;
}

export interface Actions<K> extends StableActions<K> {
  has: (key: K) => boolean;
}

const useSet = <K>(initialSet = new Set<K>()): [Set<K>, Actions<K>] => {
  const [set, setSet] = useState(initialSet);

  const stableActions = useMemo<StableActions<K>>(() => {
    const add = (item: K) => setSet((prevSet) => new Set([...Array.from(prevSet), item]));
    const remove = (item: K) =>
      setSet((prevSet) => new Set(Array.from(prevSet).filter((i) => i !== item)));
    const toggle = (item: K) =>
      setSet((prevSet) =>
        prevSet.has(item)
          ? new Set(Array.from(prevSet).filter((i) => i !== item))
          : new Set([...Array.from(prevSet), item])
      );

    return { add, remove, toggle, reset: () => setSet(initialSet) };
  }, [setSet]);

  const utils = {
    has: useCallback((item) => set.has(item), [set]),
    ...stableActions,
  } as Actions<K>;

  return [set, utils];
};

export default useSet;

4.4 useToggle

useToggle docs[24] |useToggle demo[25]

tracks state of a boolean. 跟踪布尔值的状态。 切换 false => true => false

import { Reducer, useReducer } from 'react';

const toggleReducer = (state: boolean, nextValue?: any) =>
  typeof nextValue === 'boolean' ? nextValue : !state;

const useToggle = (initialValue: boolean): [boolean, (nextValue?: any) => void] => {
  return useReducer<Reducer<boolean, any>>(toggleReducer, initialValue);
};

export default useToggle;

我们继续来看 Side-effects 副作用部分。

5. Side-effects 副作用

5.1 useMountedState

useMountedState 属于 lifecycle 模块,但这个 hookuseAsyncFn 中使用,所以放到这里讲述。

useMountedState docs[26] |useMountedState demo[27]

NOTE!: despite having State in its name this hook does not cause component re-render. This component designed to be used to avoid state updates on unmounted components.

注意!:尽管名称中有State,但该钩子不会导致组件重新呈现。此组件设计用于避免对未安装的组件进行状态更新。

Lifecycle hook providing ability to check component's mount state. Returns a function that will return true if component mounted and false otherwise. 生命周期挂钩提供了检查组件装载状态的能力。 返回一个函数,如果组件已安装,则返回true,否则返回false。

import { useCallback, useEffect, useRef } from 'react';

export default function useMountedState(): () => boolean {
  const mountedRef = useRef<boolean>(false);
  const get = useCallback(() => mountedRef.current, []);

  useEffect(() => {
    mountedRef.current = true;

    return () => {
      mountedRef.current = false;
    };
  }, []);

  return get;
}

5.2 useAsyncFn

useAsyncFn docs |useAsyncFn demo

React hook that returns state and a callback for an async function or a function that returns a promise. The state is of the same shape as useAsync. 为异步函数或返回promise的函数返回状态和回调的React钩子。状态与useAsync的形状相同。

看了 useMountedState hook,我们继续看 useAsyncFn 函数源码。

主要函数传入 Promise 函数 fn,然后执行函数 fn.then()。 返回 state、callback(fn.then)。

// 省略若干代码
export default function useAsyncFn<T extends FunctionReturningPromise>(
  fn: T,
  deps: DependencyList = [],
  initialState: StateFromFunctionReturningPromise<T> = { loading: false }
): AsyncFnReturn<T> {
  const lastCallId = useRef(0);
  const isMounted = useMountedState();
  const [state, set] = useState<StateFromFunctionReturningPromise<T>>(initialState);

  const callback = useCallback((...args: Parameters<T>): ReturnType<T> => {
    const callId = ++lastCallId.current;

    if (!state.loading) {
      set((prevState) => ({ ...prevState, loading: true }));
    }

    return fn(...args).then(
      (value) => {
        isMounted() && callId === lastCallId.current && set({ value, loading: false });

        return value;
      },
      (error) => {
        isMounted() && callId === lastCallId.current && set({ error, loading: false });

        return error;
      }
    ) as ReturnType<T>;
  }, deps);

  return [state, callback as unknown as T];
}

5.3 useAsync

useAsync docs[28] |useAsync demo[29]

React hook that resolves an async function or a function that returns a promise; 解析异步函数或返回 promise 的函数的 React 钩子;

import { DependencyList, useEffect } from 'react';
import useAsyncFn from './useAsyncFn';
import { FunctionReturningPromise } from './misc/types';

export { AsyncState, AsyncFnReturn } from './useAsyncFn';

export default function useAsync<T extends FunctionReturningPromise>(
  fn: T,
  deps: DependencyList = []
) {
  const [state, callback] = useAsyncFn(fn, deps, {
    loading: true,
  });

  useEffect(() => {
    callback();
  }, [callback]);

  return state;
}

5.4 useAsyncRetry

useAsyncRetry docs[30] |useAsyncRetry demo[31]

Uses useAsync with an additional retry method to easily retry/refresh the async function; 重试

主要就是变更依赖,次数(attempt),变更时会执行 useAsyncfn 函数。

import { DependencyList, useCallback, useState } from 'react';
import useAsync, { AsyncState } from './useAsync';

export type AsyncStateRetry<T> = AsyncState<T> & {
  retry(): void;
};

const useAsyncRetry = <T>(fn: () => Promise<T>, deps: DependencyList = []) => {
  const [attempt, setAttempt] = useState<number>(0);
  const state = useAsync(fn, [...deps, attempt]);

  const stateLoading = state.loading;
  const retry = useCallback(() => {
    // 省略开发环境警告提示

    setAttempt((currentAttempt) => currentAttempt + 1);
  }, [...deps, stateLoading]);

  return { ...state, retry };
};

export default useAsyncRetry;

5.5 useTimeoutFn

useTimeoutFn 属于 Animations 模块,但这个 hookuseDebounce 中使用,所以放到这里讲述。

useTimeoutFn docs[32] | useTimeoutFn demo[33]

Calls given function after specified amount of milliseconds. 在指定的毫秒数后调用给定的函数。

主要是 useRefsetTimeout 结合实现的。

import { useCallback, useEffect, useRef } from 'react';

export type UseTimeoutFnReturn = [() => boolean | null, () => void, () => void];

export default function useTimeoutFn(fn: Function, ms: number = 0): UseTimeoutFnReturn {
  const ready = useRef<boolean | null>(false);
  const timeout = useRef<ReturnType<typeof setTimeout>>();
  const callback = useRef(fn);

  const isReady = useCallback(() => ready.current, []);

  const set = useCallback(() => {
    ready.current = false;
    timeout.current && clearTimeout(timeout.current);

    timeout.current = setTimeout(() => {
      ready.current = true;
      callback.current();
    }, ms);
  }, [ms]);

  const clear = useCallback(() => {
    ready.current = null;
    timeout.current && clearTimeout(timeout.current);
  }, []);

  // update ref when function changes
  useEffect(() => {
    callback.current = fn;
  }, [fn]);

  // set on mount, clear on unmount
  useEffect(() => {
    set();

    return clear;
  }, [ms]);

  return [isReady, clear, set];
}

5.6 useDebounce

useDebounce docs[34] |useDebounce demo[35]

React hook that delays invoking a function until after wait milliseconds have elapsed since the last time the debounced function was invoked. 防抖

import { DependencyList, useEffect } from 'react';
import useTimeoutFn from './useTimeoutFn';

export type UseDebounceReturn = [() => boolean | null, () => void];

export default function useDebounce(
  fn: Function,
  ms: number = 0,
  deps: DependencyList = []
): UseDebounceReturn {
  const [isReady, cancel, reset] = useTimeoutFn(fn, ms);

  useEffect(reset, deps);

  return [isReady, cancel];
}

5.7 useThrottle

useThrottle docs[36] |useThrottle demo[37]

React hooks that throttle. 节流

import { useEffect, useRef, useState } from 'react';
import useUnmount from './useUnmount';

const useThrottle = <T>(value: T, ms: number = 200) => {
  const [state, setState] = useState<T>(value);
  const timeout = useRef<ReturnType<typeof setTimeout>>();
  const nextValue = useRef(null) as any;
  const hasNextValue = useRef(0) as any;

  useEffect(() => {
    if (!timeout.current) {
      setState(value);
      const timeoutCallback = () => {
        if (hasNextValue.current) {
          hasNextValue.current = false;
          setState(nextValue.current);
          timeout.current = setTimeout(timeoutCallback, ms);
        } else {
          timeout.current = undefined;
        }
      };
      timeout.current = setTimeout(timeoutCallback, ms);
    } else {
      nextValue.current = value;
      hasNextValue.current = true;
    }
  }, [value]);

  useUnmount(() => {
    timeout.current && clearTimeout(timeout.current);
  });

  return state;
};

export default useThrottle;

我们继续来看 UI 用户界面部分。

6. UI 用户界面

6.1 useFullscreen

useFullscreen docs[38] |useFullscreen demo[39]

Display an element full-screen, optional fallback for fullscreen video on iOS. 实现全屏

主要使用 screenfull[40] npm 包实现。

import { RefObject, useState } from 'react';
import screenfull from 'screenfull';
import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';
import { noop, off, on } from './misc/util';

export interface FullScreenOptions {
  video?: RefObject<
    HTMLVideoElement & { webkitEnterFullscreen?: () => void; webkitExitFullscreen?: () => void }
  >;
  onClose?: (error?: Error) => void;
}

const useFullscreen = (
  ref: RefObject<Element>,
  enabled: boolean,
  options: FullScreenOptions = {}
): boolean => {
  const { video, onClose = noop } = options;
  const [isFullscreen, setIsFullscreen] = useState(enabled);

  useIsomorphicLayoutEffect(() => {
    if (!enabled) {
      return;
    }
    if (!ref.current) {
      return;
    }

    const onWebkitEndFullscreen = () => {
      if (video?.current) {
        off(video.current, 'webkitendfullscreen', onWebkitEndFullscreen);
      }
      onClose();
    };

    const onChange = () => {
      if (screenfull.isEnabled) {
        const isScreenfullFullscreen = screenfull.isFullscreen;
        setIsFullscreen(isScreenfullFullscreen);
        if (!isScreenfullFullscreen) {
          onClose();
        }
      }
    };

    if (screenfull.isEnabled) {
      try {
        screenfull.request(ref.current);
        setIsFullscreen(true);
      } catch (error) {
        onClose(error);
        setIsFullscreen(false);
      }
      screenfull.on('change', onChange);
    } else if (video && video.current && video.current.webkitEnterFullscreen) {
      video.current.webkitEnterFullscreen();
      on(video.current, 'webkitendfullscreen', onWebkitEndFullscreen);
      setIsFullscreen(true);
    } else {
      onClose();
      setIsFullscreen(false);
    }

    return () => {
      setIsFullscreen(false);
      if (screenfull.isEnabled) {
        try {
          screenfull.off('change', onChange);
          screenfull.exit();
        } catch {}
      } else if (video && video.current && video.current.webkitExitFullscreen) {
        off(video.current, 'webkitendfullscreen', onWebkitEndFullscreen);
        video.current.webkitExitFullscreen();
      }
    };
  }, [enabled, video, ref]);

  return isFullscreen;
};

export default useFullscreen;

我们继续来看 Lifecycles 生命周期部分。

7. Lifecycles 生命周期

7.1 useLifecycles

useLifecycles docs[41] |useLifecycles demo[42]

React lifecycle hook that call mount and unmount callbacks, when component is mounted and un-mounted, respectively. React 生命周期挂钩,分别在组件安装和卸载时调用。

import { useEffect } from 'react';

const useLifecycles = (mount, unmount?) => {
  useEffect(() => {
    if (mount) {
      mount();
    }
    return () => {
      if (unmount) {
        unmount();
      }
    };
  }, []);
};

export default useLifecycles;

7.2 useCustomCompareEffect

useCustomCompareEffect docs[43] |useCustomCompareEffect demo[44]

A modified useEffect hook that accepts a comparator which is used for comparison on dependencies instead of reference equality. 一个经过修改的useEffect钩子,它接受一个比较器,该比较器用于对依赖项进行比较,而不是对引用相等进行比较。

import { DependencyList, EffectCallback, useEffect, useRef } from 'react';

const isPrimitive = (val: any) => val !== Object(val);

type DepsEqualFnType<TDeps extends DependencyList> = (prevDeps: TDeps, nextDeps: TDeps) => boolean;

const useCustomCompareEffect = <TDeps extends DependencyList>(
  effect: EffectCallback,
  deps: TDeps,
  depsEqual: DepsEqualFnType<TDeps>
) => {
  // 省略一些开发环境的警告提示

  const ref = useRef<TDeps | undefined>(undefined);

  if (!ref.current || !depsEqual(deps, ref.current)) {
    ref.current = deps;
  }

  useEffect(effect, ref.current);
};

export default useCustomCompareEffect;

7.3 useDeepCompareEffect

useDeepCompareEffect docs[45] |useDeepCompareEffect demo[46]

A modified useEffect hook that is using deep comparison on its dependencies instead of reference equality. 一个修改后的 useEffect 钩子,它对其依赖项使用深度比较,而不是引用相等。

import { DependencyList, EffectCallback } from 'react';
import useCustomCompareEffect from './useCustomCompareEffect';
import isDeepEqual from './misc/isDeepEqual';

const isPrimitive = (val: any) => val !== Object(val);

const useDeepCompareEffect = (effect: EffectCallback, deps: DependencyList) => {
  // 省略若干开发环境的警告提示

  useCustomCompareEffect(effect, deps, isDeepEqual);
};

export default useDeepCompareEffect;

最后,我们来看 Animations 生命周期部分。

8. Animations 动画

8.1 useUpdate

useUpdate docs[47] |useUpdate demo[48]

React utility hook that returns a function that forces component to re-render when called. React 实用程序钩子返回一个函数,该函数在调用时强制组件重新渲染。

主要用了 useReducer 每次调用 updateReducer 方法,来达到强制组件重新渲染的目的。

import { useReducer } from 'react';

const updateReducer = (num: number): number => (num + 1) % 1_000_000;

export default function useUpdate(): () => void {
  const [, update] = useReducer(updateReducer, 0);

  return update;
}

9. 总结

行文至此,我们简单分析了若干 react-use[49] 的自定义 React Hooks。想进一步学习的小伙伴,可以继续学完剩余的 hooks。还可以学习 ahooks[50]、别人写的 ahooks 源码分析[51]、beautiful-react-hooks[52]、mantine-hooks[53] 等。

学习过程中带着问题多查阅 React 新文档 react.dev[54],新中文文档 zh-hans.react.dev[55],相信收获更大。

如果技术栈是 Vue,感兴趣的小伙伴可以学习 VueUse[56]

如果能看完一些 React Hooks 工具集合库的源码。相信一定能对 React Hooks 有更深的理解,自己写自定义 React Hooks 时也会更加顺利、快速。


如果看完有收获,欢迎点赞、评论、分享支持。你的支持和肯定,是我写作的动力

参考资料

[1]

新文档 react.dev: https://react.dev

[2]

新中文文档 zh-hans.react.dev: https://zh-hans.react.dev/


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值