源码共读 | react-use 源码

1.源码介绍

2.准备工作

下载源码

bash 复制代码
git clone https://github.com/streamich/react-use

# 可以下载若川大佬的仓库
git clone https://github.com/lxchuan12/react-use-analysis.git

安装依赖及运行

bash 复制代码
yarn install
yarn start

运行成功后会自动打开 Storybook 服务,也就是图中的 http://localhost:6008/

可以看到左侧的目录中既有文档,又有 Demo

3.开始阅读

下面正式开始跟着 若川大佬 和官方文档进行源码阅读。

3.1 useEffectOnce

这个 Hook 的源码非常简单,只有 7 行:

js 复制代码
import { EffectCallback, useEffect } from 'react';

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

export default useEffectOnce;

看到这个源码我们很容易知道它的作用:只运行一次 React 的 useEffect 生命周期

事实上我的代码里很多useEffect(() => {/* my code */}, []);,几乎每个组件里都要写一次,但是真没想到这么简短的也能封装一下,真是把封装做到了极致。

3.1.1 测试用例

项目里提供了非常丰富的测试用例,下面先跟着测试用例看看。 要调试测试用例,需要先在 Vscode 中安装 JestJest Runner 插件,安装完成后,可测试的测试用例上面会有 Run 按钮:

动图运行如下:

定格到最后一帧,我们可以看到,输出和预期是一致的:

3.1.2 Sotrybook Demo

除了在代码中直接运行测试用例外,前面跑起来的 Storybook 服务中也提供了 Demo,可以直观地看到结果:

我们可以看到当我们进入 useEffectOnceDemo 中的时候,控制台输出了 Docs 中 Usage 中的 console.log('Running effect once on mount'),离开 Demo 中的时候又运行了console.log('Running clean-up of effect on unmount'),均符合预期。

3.2 Sensors

从官方文档的左侧边栏可以看出,所有的 Hook 被分成了SensorsStateSide EffectsUILifecycleMiscellaneousAnimation这七大类,我们先来看Sensors

"Sensor Hooks" listen to changes in some interface and force your components to be re-rendered with the new state, up-to-date state. 监听某些界面的变化,并强制组件以新状态、最新状态重新渲染组件

Sensors是传感器的意思,主要的功能是监听用户和系统的行为 ,用户行为包括鼠标(useMouse)、滚轮(useScroll)、键盘(useKey)、输入(useStartTyping)等行为,系统行为包括电池状态(useBattery)、Window 尺寸(useWindowSize)、用户是否活跃(useIdle)等。

下面以用户行为和系统行为为例,各详细演示一个。

3.2.1 useMouse

先上源码:

js 复制代码
import { RefObject, useEffect } from 'react';

import useRafState from './useRafState';
import { off, on } from './misc/util';

export interface State {
  docX: number;
  docY: number;
  posX: number;
  posY: number;
  elX: number;
  elY: number;
  elH: number;
  elW: number;
}

const useMouse = (ref: RefObject<Element>): State => {
  if (process.env.NODE_ENV === 'development') {
    if (typeof ref !== 'object' || typeof ref.current === 'undefined') {
      console.error('useMouse expects a single ref argument.');
    }
  }

  const [state, setState] = useRafState<State>({
    docX: 0,
    docY: 0,
    posX: 0,
    posY: 0,
    elX: 0,
    elY: 0,
    elH: 0,
    elW: 0,
  });

  useEffect(() => {
    const moveHandler = (event: MouseEvent) => {
      if (ref && ref.current) {
        const { left, top, width: elW, height: elH } = ref.current.getBoundingClientRect();
        const posX = left + window.pageXOffset;
        const posY = top + window.pageYOffset;
        const elX = event.pageX - posX;
        const elY = event.pageY - posY;

        setState({
          docX: event.pageX,
          docY: event.pageY,
          posX,
          posY,
          elX,
          elY,
          elH,
          elW,
        });
      }
    };

    on(document, 'mousemove', moveHandler);

    return () => {
      off(document, 'mousemove', moveHandler);
    };
  }, [ref]);

  return state;
};

export default useMouse;

使用的 Demo:

js 复制代码
import {useMouse} from 'react-use';

const Demo = () => {
  const ref = React.useRef(null);
  const {docX, docY, posX, posY, elX, elY, elW, elH} = useMouse(ref);

  return (
    <div ref={ref}>
      <div>Mouse position in document - x:{docX} y:{docY}</div>
      <div>Mouse position in element - x:{elX} y:{elY}</div>
      <div>Element position- x:{posX} y:{posY}</div>
      <div>Element dimensions - {elW}x{elH}</div>
    </div>
  );
};

效果展示:

可以看到,这个 hook 的使用非常简单,只要传入一个元素的 ref 就能够返回鼠标在这个元素内部的各种信息(主要是位置信息)。

另外比较神奇的是,这些参数(docXdocY等)都具有 state 的响应性,这里之所以能做到这点,主要是用了 useRafState,项目里也有源码,这里不再赘述。

3.2.2 useBattery

这个 hook 是检测电池状态的,手机上我没有试过,但是电脑上,确实有用!

源码:

js 复制代码
import { useEffect, useState } from 'react';
import { isNavigator, off, on } from './misc/util';
import isDeepEqual from './misc/isDeepEqual';

export interface BatteryState {
  charging: boolean;
  chargingTime: number;
  dischargingTime: number;
  level: number;
}

interface BatteryManager extends Readonly<BatteryState>, EventTarget {
  onchargingchange: () => void;
  onchargingtimechange: () => void;
  ondischargingtimechange: () => void;
  onlevelchange: () => void;
}

interface NavigatorWithPossibleBattery extends Navigator {
  getBattery?: () => Promise<BatteryManager>;
}

type UseBatteryState =
  | { isSupported: false } // Battery API is not supported
  | { isSupported: true; fetched: false } // battery API supported but not fetched yet
  | (BatteryState & { isSupported: true; fetched: true }); // battery API supported and fetched

const nav: NavigatorWithPossibleBattery | undefined = isNavigator ? navigator : undefined;
const isBatteryApiSupported = nav && typeof nav.getBattery === 'function';

function useBatteryMock(): UseBatteryState {
  return { isSupported: false };
}

function useBattery(): UseBatteryState {
  const [state, setState] = useState<UseBatteryState>({ isSupported: true, fetched: false });

  useEffect(() => {
    let isMounted = true;
    let battery: BatteryManager | null = null;

    const handleChange = () => {
      if (!isMounted || !battery) {
        return;
      }
      const newState: UseBatteryState = {
        isSupported: true,
        fetched: true,
        level: battery.level,
        charging: battery.charging,
        dischargingTime: battery.dischargingTime,
        chargingTime: battery.chargingTime,
      };
      !isDeepEqual(state, newState) && setState(newState);
    };

    nav!.getBattery!().then((bat: BatteryManager) => {
      if (!isMounted) {
        return;
      }
      battery = bat;
      on(battery, 'chargingchange', handleChange);
      on(battery, 'chargingtimechange', handleChange);
      on(battery, 'dischargingtimechange', handleChange);
      on(battery, 'levelchange', handleChange);
      handleChange();
    });

    return () => {
      isMounted = false;
      if (battery) {
        off(battery, 'chargingchange', handleChange);
        off(battery, 'chargingtimechange', handleChange);
        off(battery, 'dischargingtimechange', handleChange);
        off(battery, 'levelchange', handleChange);
      }
    };
  }, []);

  return state;
}

export default isBatteryApiSupported ? useBattery : useBatteryMock;

Demo及演示:

js 复制代码
import {useBattery} from 'react-use';

const Demo = () => {
  const batteryState = useBattery();

  if (!batteryState.isSupported) {
    return (
      <div>
        <strong>Battery sensor</strong>: <span>not supported</span>
      </div>
    );
  }

  if (!batteryState.fetched) {
    return (
      <div>
        <strong>Battery sensor</strong>: <span>supported</span> <br />
        <strong>Battery state</strong>: <span>fetching</span>
      </div>
    );
  }

  return (
    <div>
      <strong>Battery sensor</strong>:&nbsp;&nbsp; <span>supported</span> <br />
      <strong>Battery state</strong>: <span>fetched</span> <br />
      <strong>Charge level</strong>:&nbsp;&nbsp; <span>{ (batteryState.level * 100).toFixed(0) }%</span> <br />
      <strong>Charging</strong>:&nbsp;&nbsp; <span>{ batteryState.charging ? 'yes' : 'no' }</span> <br />
      <strong>Charging time</strong>:&nbsp;&nbsp;
      <span>{ batteryState.chargingTime ? batteryState.chargingTime : 'finished' }</span> <br />
      <strong>Discharging time</strong>:&nbsp;&nbsp; <span>{ batteryState.dischargingTime }</span>
    </div>
  );
};

图中之所以会变化是因为我拔了电源,然后又插了回去,所以Charging 从 Yes 变成 No,又变回 Yes,不过我没有试过移动端,理论上也是可以做到监听的,有有试过的小伙伴们可以在评论区说一下。

看源代码这个功能主要是通过 window.navigator.getBattery() 实现的,之前还真不知道 navigator 下还有这么个功能,于是又去详细了解了一下 navigator 下的属性和方法,感兴趣的小伙伴也可以看看:Navigator - Web API 接口参考 | MDN (mozilla.org)

Senors 下的 Hook 就分享到这,下面看 State

3.3 State

"State Hooks" allow you to easily manage state of booleans, arrays, and maps. 允许轻松管理 boolean、array 和 map 的 state

3.3.1 useFirstMountState

这个 Hook 主要用来判断组件是否是初次渲染

源码非常简单,useRef 一般有两种用法,一种是作为 DOM 的引用,一种是作为数据的引用,使被引用对象在组件变化的时候保持不变。

js 复制代码
import { useRef } from 'react';

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

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

    // 直接在初次渲染的时候返回 true,主打一个简单粗暴。
    return true;
  }

  return isFirst.current;
}

Demo

javascript 复制代码
import * as React from 'react';
import { useFirstMountState } from 'react-use';

const Demo = () => {
  const isFirstMount = useFirstMountState();
  const update = useUpdate();

  return (
    <div>
      <span>This component is just mounted: {isFirstMount ? 'YES' : 'NO'}</span>
      <br />
      <button onClick={update}>re-render</button>
    </div>
  );
};

由于比较简单,这里就不录制 Gif 动图了,可以直接查看演示:useFirstMountState - Demo

3.3.2 useList

监听一个数组并返回操作数组的方法,相比起默认的数组,通过这个 Hook 返回的方法操作数组可以触发 re-render

这个源码多一些,但是原理也很简单,所有的操作最后都转换成 set 方法了,而 set 方法中调用了另一个 useUpdate Hook,通过这个 Hook 触发更新。

js 复制代码
import { useMemo, useRef } from 'react';
import useUpdate from './useUpdate';
import { IHookStateInitAction, IHookStateSetAction, resolveHookState } from './misc/hookState';

export interface ListActions<T> {
  set: (newList: IHookStateSetAction<T[]>) => void;
  push: (...items: T[]) => void;
  updateAt: (index: number, item: T) => void;
  insertAt: (index: number, item: T) => void;
  update: (predicate: (a: T, b: T) => boolean, newItem: T) => void;
  updateFirst: (predicate: (a: T, b: T) => boolean, newItem: T) => void;
  upsert: (predicate: (a: T, b: T) => boolean, newItem: T) => void;
  sort: (compareFn?: (a: T, b: T) => number) => void;
  filter: (callbackFn: (value: T, index?: number, array?: T[]) => boolean, thisArg?: any) => void;
  removeAt: (index: number) => void;
  remove: (index: number) => void;
  clear: () => void;
  reset: () => void;
}

function useList<T>(initialList: IHookStateInitAction<T[]> = []): [T[], ListActions<T>] {
  const list = useRef(resolveHookState(initialList));
  const update = useUpdate();

  const actions = useMemo<ListActions<T>>(() => {
    const a = {
      set: (newList: IHookStateSetAction<T[]>) => {
        list.current = resolveHookState(newList, list.current);
        // 触发更新
        update();
      },

      push: (...items: T[]) => {
        items.length && actions.set((curr: T[]) => curr.concat(items));
      },

      updateAt: (index: number, item: T) => {
        actions.set((curr: T[]) => {
          const arr = curr.slice();

          arr[index] = item;

          return arr;
        });
      },

      insertAt: (index: number, item: T) => {
        actions.set((curr: T[]) => {
          const arr = curr.slice();

          index > arr.length ? (arr[index] = item) : arr.splice(index, 0, item);

          return arr;
        });
      },

      update: (predicate: (a: T, b: T) => boolean, newItem: T) => {
        actions.set((curr: T[]) => curr.map((item) => (predicate(item, newItem) ? newItem : item)));
      },

      updateFirst: (predicate: (a: T, b: T) => boolean, newItem: T) => {
        const index = list.current.findIndex((item) => predicate(item, newItem));

        index >= 0 && actions.updateAt(index, newItem);
      },

      upsert: (predicate: (a: T, b: T) => boolean, newItem: T) => {
        const index = list.current.findIndex((item) => predicate(item, newItem));

        index >= 0 ? actions.updateAt(index, newItem) : actions.push(newItem);
      },

      sort: (compareFn?: (a: T, b: T) => number) => {
        actions.set((curr: T[]) => curr.slice().sort(compareFn));
      },

      filter: <S extends T>(
        callbackFn: (value: T, index: number, array: T[]) => value is S,
        thisArg?: any
      ) => {
        actions.set((curr: T[]) => curr.slice().filter(callbackFn, thisArg));
      },

      removeAt: (index: number) => {
        actions.set((curr: T[]) => {
          const arr = curr.slice();

          arr.splice(index, 1);

          return arr;
        });
      },

      clear: () => {
        actions.set([]);
      },

      reset: () => {
        actions.set(resolveHookState(initialList).slice());
      },
    };

    /**
     * @deprecated Use removeAt method instead
     */
    (a as ListActions<T>).remove = a.removeAt;

    return a as ListActions<T>;
  }, []);

  return [list.current, actions];
}

export default useList;

Demo:State / useList - Demo ⋅ Storybook (streamich.github.io)

css 复制代码
import {useList} from 'react-use';

const Demo = () => {
  const [list, { set, push, updateAt, insertAt, update, updateFirst, upsert, sort, filter, removeAt, clear, reset }] = useList([1, 2, 3, 4, 5]);

  return (
    <div>
      <button onClick={() => set([1, 2, 3])}>Set to [1, 2, 3]</button>
      <button onClick={() => push(Date.now())}>Push timestamp</button>
      <button onClick={() => updateAt(1, Date.now())}>Update value at index 1</button>
      <button onClick={() => remove(1)}>Remove element at index 1</button>
      <button onClick={() => filter(item => item % 2 === 0)}>Filter even values</button>
      <button onClick={() => sort((a, b) => a - b)}>Sort ascending</button>
      <button onClick={() => sort((a, b) => b - a)}>Sort descending</button>
      <button onClick={clear}>Clear</button>
      <button onClick={reset}>Reset</button>
      <pre>{JSON.stringify(list, null, 2)}</pre>
    </div>
  );
};

3.4 Side-Effect

"Side-effect Hooks" allow your app trigger various side-effects using browser's API. 允许应用程序使用浏览器的API触发各种副作用

3.4.1 useAsyncFn

为异步函数或 Promise 函数返回状态和回调的 React 钩子,主要是将请求的 loadingerrorvalue 封装在钩子里并让它们成为响应式数据,当请求完成状态改变后自动触发 re-render

js 复制代码
import { DependencyList, useCallback, useRef, useState } from 'react';
import useMountedState from './useMountedState';
import { FunctionReturningPromise, PromiseType } from './misc/types';

export type AsyncState<T> =
  | {
      loading: boolean;
      error?: undefined;
      value?: undefined;
    }
  | {
      loading: true;
      error?: Error | undefined;
      value?: T;
    }
  | {
      loading: false;
      error: Error;
      value?: undefined;
    }
  | {
      loading: false;
      error?: undefined;
      value: T;
    };

type StateFromFunctionReturningPromise<T extends FunctionReturningPromise> = AsyncState<
  PromiseType<ReturnType<T>>
>;

export type AsyncFnReturn<T extends FunctionReturningPromise = FunctionReturningPromise> = [
  StateFromFunctionReturningPromise<T>,
  T
];

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];
}

Demo:可以看到,state 中自带了 loadingerrorvalue 三个属性

js 复制代码
import {useAsyncFn} from 'react-use';

const Demo = ({url}) => {
  const [state, doFetch] = useAsyncFn(async () => {
    const response = await fetch(url);
    const result = await response.text();
    return result
  }, [url]);

  return (
    <div>
      {state.loading
        ? <div>Loading...</div>
        : state.error
          ? <div>Error: {state.error.message}</div>
          : <div>Value: {state.value}</div>
      }
      <button onClick={() => doFetch()}>Start loading</button>
    </div>
  );
};

3.4.2 useAsync

返回一个 Promise 或者一个异步函数,源码比较短,主要是因为使用了 useAsyncFn,它比起 useAsyncFn 多了主动调用异步函数这一步,相当于再封装了一层,使用起来更方便:

js 复制代码
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;
}

Demo:

js 复制代码
import {useAsync} from 'react-use';

const Demo = ({url}) => {
  const state = useAsync(async () => {
    const response = await fetch(url);
    const result = await response.text();
    return result
  }, [url]);

  return (
    <div>
      {state.loading
        ? <div>Loading...</div>
        : state.error
          ? <div>Error: {state.error.message}</div>
          : <div>Value: {state.value}</div>
      }
    </div>
  );
};

3.4.3 useDebounce & useThrottle

防抖和节流,无论是面试还是平时,相信大家都接触得比较多,甚至都可以手写,就不过多介绍了,这里本质也是通过 setTimeout 实现的。

3.5 UI

"UI Hooks" allow you to control and subscribe to state changes of UI elements. 允许控制和订阅 UI 元素的状态更改

3.5.1 useCss

css 样式直接转换成 className,这个之前还挺困扰我的,每次都是写一长串 style 在元素上,有这个就方便多了。源码实现上的话,主要是通过 nano-css 库实现的:

js 复制代码
import { create, NanoRenderer } from 'nano-css';
import { addon as addonCSSOM, CSSOMAddon } from 'nano-css/addon/cssom';
import { addon as addonVCSSOM, VCSSOMAddon } from 'nano-css/addon/vcssom';
import { cssToTree } from 'nano-css/addon/vcssom/cssToTree';
import { useMemo } from 'react';
import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';

type Nano = NanoRenderer & CSSOMAddon & VCSSOMAddon;
const nano = create() as Nano;
addonCSSOM(nano);
addonVCSSOM(nano);

let counter = 0;

const useCss = (css: object): string => {
  const className = useMemo(() => 'react-use-css-' + (counter++).toString(36), []);
  const sheet = useMemo(() => new nano.VSheet(), []);

  useIsomorphicLayoutEffect(() => {
    const tree = {};
    cssToTree(tree, css, '.' + className, '');
    sheet.diff(tree);

    return () => {
      sheet.diff({});
    };
  });

  return className;
};

export default useCss;

Demo:

js 复制代码
import {useCss} from 'react-use';

const Demo = () => {
  const className = useCss({
    color: 'red',
    border: '1px solid red',
    '&:hover': {
      color: 'blue',
    },
  });

  return (
    <div className={className}>
      Hover me!
    </div>
  );
};

3.6 Lifecycles

"Lifecycle Hooks" modify and extend built-in React hooks or imitate React Class component lifecycle patterns. 修改和扩展了内置的 React Hook 并且模仿 React Class 组件的生命周期模式

3.6.1 useLifecycles

在 React mountunmount 生命周期分别调用不同的回调,主要是通过 useEffect 和对应的 cleanUp 实现的:

js 复制代码
import { useEffect } from 'react';

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

export default useLifecycles;

Demo:

js 复制代码
import {useLifecycles} from 'react-use';

const Demo = () => {
  useLifecycles(() => console.log('MOUNTED'), () => console.log('UNMOUNTED'));
  return null;
};

3.6.2 usePromise

返回一个包含 Promise 函数的辅助函数,这个辅助函数会在组件 mount 的时候调用:

js 复制代码
import { useCallback } from 'react';
import useMountedState from './useMountedState';

export type UsePromise = () => <T>(promise: Promise<T>) => Promise<T>;

const usePromise: UsePromise = () => {
  const isMounted = useMountedState();
  return useCallback(
    (promise: Promise<any>) =>
      new Promise<any>((resolve, reject) => {
        const onValue = (value) => {
          isMounted() && resolve(value);
        };
        const onError = (error) => {
          isMounted() && reject(error);
        };
        promise.then(onValue, onError);
      }),
    []
  );
};

export default usePromise;

Demo:

js 复制代码
import {usePromise} from 'react-use';

const Demo = ({promise}) => {
  const mounted = usePromise();
  const [value, setValue] = useState();

  useEffect(() => {
    (async () => {
      const value = await mounted(promise);
      // This line will not execute if <Demo> component gets unmounted.
      setValue(value);
    })();
  });
};

3.7 Animation

"Animation Hooks" usually interpolate numeric values over time.通常随时间插入数值(一些简单的数字动画效果)

3.7.1 useSpring

根据弹簧动力学随时间更新单个数值,就是常见的数字滚动增加、减少的效果,需要强调的是,这里用了一个 rebound 第三方库:

js 复制代码
import { useEffect, useMemo, useState } from 'react';
import { Spring, SpringSystem } from 'rebound';

const useSpring = (targetValue: number = 0, tension: number = 50, friction: number = 3) => {
  const [spring, setSpring] = useState<Spring | null>(null);
  const [value, setValue] = useState<number>(targetValue);

  // memoize listener to being able to unsubscribe later properly, otherwise
  // listener fn will be different on each re-render and wouldn't unsubscribe properly.
  const listener = useMemo(
    () => ({
      onSpringUpdate: (currentSpring) => {
        const newValue = currentSpring.getCurrentValue();
        setValue(newValue);
      },
    }),
    []
  );

  useEffect(() => {
    if (!spring) {
      const newSpring = new SpringSystem().createSpring(tension, friction);
      newSpring.setCurrentValue(targetValue);
      setSpring(newSpring);
      newSpring.addListener(listener);
    }

    return () => {
      if (spring) {
        spring.removeListener(listener);
        setSpring(null);
      }
    };
  }, [tension, friction, spring]);

  useEffect(() => {
    if (spring) {
      spring.setEndValue(targetValue);
    }
  }, [targetValue]);

  return value;
};

export default useSpring;

Demo:

js 复制代码
import useSpring from 'react-use/lib/useSpring';

const Demo = () => {
  const [target, setTarget] = useState(50);
  const value = useSpring(target);

  return (
    <div>
      {value}
      <br />
      <button onClick={() => setTarget(0)}>Set 0</button>
      <button onClick={() => setTarget(100)}>Set 100</button>
    </div>
  );
};

本期源码收获

至此,我们就简单分析了一下 react-use 源码各个模块的几个 Hook,虽然整体难度不大,但是数量还是挺多的,感兴趣的小伙伴就赶紧源码看起来吧,我可以你肯定也可以的 :fist:

学完之后的实战使用

Echarts 想来大家都用过,确实好用,尤其是 5.0 版本更新之后支持了按需导入,不仅好用,对项目负担也不大。但有点不好的是,每次使用都需要 echarts.use 一大堆,导包也需要导入一大堆,如下:

js 复制代码
import { PieChart } from 'echarts/charts';
import * as echarts from 'echarts/core';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { LegendComponent } from 'echarts/components';

import { OverviewText } from '@/crm/components/Common/CRMStyle';

echarts.use([LegendComponent, PieChart, CanvasRenderer, LabelLayout]);

学习完这次源码之后正好可以通过自定义 Hook 将这一大堆重复代码封装起来,经过一通操作,最后封装了一个 useEcharts,代码如下:

js 复制代码
import { useEffect, useState } from 'react';
import * as echarts from 'echarts/core';
import {
  ToolboxComponent,
  TooltipComponent,
  GridComponent,
  LegendComponent,
} from 'echarts/components';
import { BarChart, LineChart } from 'echarts/charts';
import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';

echarts.use([
  ToolboxComponent,
  TooltipComponent,
  GridComponent,
  LegendComponent,
  BarChart,
  LineChart,
  CanvasRenderer,
  UniversalTransition,
]);

const useEcharts = (chartRef) => {
  const [initedChart, setInitedChart] = useState(null);

  useEffect(() => {
    if (chartRef) {
      setInitedChart(echarts.init(chartRef.current));
    }
  }, [chartRef]);

  return initedChart;
};

export default useEcharts;

好啦,大家也赶紧学起来、用起来吧,下一篇源码篇见~

如有错误,烦请指正,感谢大家~

相关推荐
学习ing小白1 小时前
JavaWeb - 5 - 前端工程化
前端·elementui·vue
真的很上进1 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
胖虎哥er2 小时前
Html&Css 基础总结(基础好了才是最能打的)三
前端·css·html
qq_278063712 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl2 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js
小徐不会写代码2 小时前
vue 实现tab菜单切换
前端·javascript·vue.js
2301_765347542 小时前
Vue3 Day7-全局组件、指令以及pinia
前端·javascript·vue.js
ch_s_t2 小时前
新峰商城之分类三级联动实现
前端·html
辛-夷2 小时前
VUE面试题(单页应用及其首屏加载速度慢的问题)
前端·javascript·vue.js
田哥coder2 小时前
充电桩项目:前端实现
前端