- 本文参加了由 公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 原文链接:自从学了 react-use 源码,我写自定义 React Hooks 越来越顺了~
1.源码介绍
- 源码作者:streamich
- 源码阅读难度:Easy
- 源码包含的 Hooks 很多,但大多数的代码都不长
- 源码使用 Storybook 搭建,有丰富的使用示例
- 功能:在 React 中使用可以大量减少重复代码
- GitHub地址:streamich/react-use: React Hooks --- 👍 (github.com)
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 中安装 Jest
和 Jest Runner
插件,安装完成后,可测试的测试用例上面会有 Run
按钮:
动图运行如下:
定格到最后一帧,我们可以看到,输出和预期是一致的:
3.1.2 Sotrybook Demo
除了在代码中直接运行测试用例外,前面跑起来的 Storybook 服务中也提供了 Demo
,可以直观地看到结果:
我们可以看到当我们进入 useEffectOnce
的 Demo
中的时候,控制台输出了 Docs 中 Usage 中的 console.log('Running effect once on mount')
,离开 Demo 中的时候又运行了console.log('Running clean-up of effect on unmount')
,均符合预期。
3.2 Sensors
从官方文档的左侧边栏可以看出,所有的 Hook 被分成了Sensors
、State
、Side Effects
、UI
、Lifecycle
、Miscellaneous
、Animation
这七大类,我们先来看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 就能够返回鼠标在这个元素内部的各种信息(主要是位置信息)。
另外比较神奇的是,这些参数(docX
、docY
等)都具有 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>: <span>supported</span> <br />
<strong>Battery state</strong>: <span>fetched</span> <br />
<strong>Charge level</strong>: <span>{ (batteryState.level * 100).toFixed(0) }%</span> <br />
<strong>Charging</strong>: <span>{ batteryState.charging ? 'yes' : 'no' }</span> <br />
<strong>Charging time</strong>:
<span>{ batteryState.chargingTime ? batteryState.chargingTime : 'finished' }</span> <br />
<strong>Discharging time</strong>: <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 钩子,主要是将请求的 loading
、error
和 value
封装在钩子里并让它们成为响应式数据,当请求完成状态改变后自动触发 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 中自带了 loading
、error
、value
三个属性
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 mount
和 unmount
生命周期分别调用不同的回调,主要是通过 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;
好啦,大家也赶紧学起来、用起来吧,下一篇源码篇见~
如有错误,烦请指正,感谢大家~