useControllableValue:管理组件受控非受控状态

  • 受控组件:值状态完全由外部父组件管理,控制权在调用的父组件上
  • 非受控组件:值状态由内部子组件管理
  • hooks 传参:props - ```typescript export interface Options { defaultValue?: T; defaultValuePropName?: string; valuePropName?: string; trigger?: string; }

export type Props = Record;

export interface StandardProps { value: T; defaultValue?: T; onChange: (val: T) => void; }

function useControllableValue(props: StandardProps): [T, (val: T) => void]; function useControllableValue( props?: Props, options?: Options, ): [T, (v: T, …args: any[]) => void]; function useControllableValue(props: Props = {}, options: Options = {}) { const { defaultValue, defaultValuePropName = ‘defaultValue’, valuePropName = ‘value’, trigger = ‘onChange’, } = options;

const value = props[valuePropName] as T; const isControlled = valuePropName in props;

const initialValue = useMemo(() => { if (isControlled) { return value; } if (defaultValuePropName in props) { return props[defaultValuePropName]; } return defaultValue; }, []);

const stateRef = useRef(initialValue); if (isControlled) { stateRef.current = value; }

const update = useUpdate();

const setState = (v: T, …args: any[]) => { if (!isControlled) { stateRef.current = v; update(); } if (props[trigger]) { propstrigger; } };

return [stateRef.current, useMemoizedFn(setState)] as const; }

  1. <a name="c838N"></a>
  2. ### useCreation:useMemo 或 useRef 替代品,保证创建出来的常量,不会被重复创建
  3. useCreation 存在的意义<br />![截屏2022-01-07 下午6.14.52.png](https://cdn.nlark.com/yuque/0/2022/png/22895623/1641550545678-396d14df-4e1c-4601-9d9a-f5c5c0e16fc4.png#clientId=u3091cd92-19f5-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u85dddef7&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2022-01-07%20%E4%B8%8B%E5%8D%886.14.52.png&originHeight=874&originWidth=2548&originalType=binary&ratio=1&rotation=0&showTitle=false&size=303580&status=done&style=none&taskId=u4e2a5cc6-0e37-49e8-bef4-b8acdbb174a&title=)<br />useMemo:<br />![截屏2022-01-07 下午6.36.59.png](https://cdn.nlark.com/yuque/0/2022/png/22895623/1641551858227-6cfa845f-adfc-45fb-a944-5b24c1874f3f.png#clientId=u3091cd92-19f5-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u1c501b5a&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2022-01-07%20%E4%B8%8B%E5%8D%886.36.59.png&originHeight=310&originWidth=1592&originalType=binary&ratio=1&rotation=0&showTitle=false&size=98333&status=done&style=none&taskId=u46717fbd-856a-4aef-9dc2-c93e1093c16&title=)<br />源码实现:
  4. - 本质还是使用 useRef,将 factory 函数执行的结果保存在 current.obj 内
  5. - 使用 initialized 标记是不是已经初始化过
  6. ```typescript
  7. import type { DependencyList } from 'react';
  8. import { useRef } from 'react';
  9. import depsAreSame from '../utils/depsAreSame';
  10. export default function useCreation<T>(factory: () => T, deps: DependencyList) {
  11. const { current } = useRef({
  12. deps,
  13. obj: undefined as undefined | T,
  14. initialized: false,
  15. });
  16. // 只有 initialized 是 false
  17. // 或者 依赖列表发生变化的时候
  18. // 才需要重新执行 factory(), 获取新的实例
  19. if (current.initialized === false || !depsAreSame(current.deps, deps)) {
  20. current.deps = deps;
  21. current.obj = factory();
  22. current.initialized = true;
  23. }
  24. // 返回这个实例
  25. return current.obj as T;
  26. }

useEventEmitter:事件分发

  1. import { useRef, useEffect } from 'react';
  2. type Subscription<T> = (val: T) => void;
  3. export class EventEmitter<T> {
  4. // 事件池子
  5. private subscriptions = new Set<Subscription<T>>();
  6. // 事件派发
  7. emit = (val: T) => {
  8. for (const subscription of this.subscriptions) {
  9. subscription(val);
  10. }
  11. };
  12. // 事件订阅
  13. useSubscription = (callback: Subscription<T>) => {
  14. // eslint-disable-next-line react-hooks/rules-of-hooks
  15. const callbackRef = useRef<Subscription<T>>();
  16. callbackRef.current = callback;
  17. // eslint-disable-next-line react-hooks/rules-of-hooks
  18. useEffect(() => {
  19. function subscription(val: T) {
  20. if (callbackRef.current) {
  21. callbackRef.current(val);
  22. }
  23. }
  24. this.subscriptions.add(subscription);
  25. return () => {
  26. this.subscriptions.delete(subscription);
  27. };
  28. }, []);
  29. };
  30. }
  31. export default function useEventEmitter<T = void>() {
  32. const ref = useRef<EventEmitter<T>>();
  33. // 保证全局下只生成一个 EventEmitter 实例
  34. if (!ref.current) {
  35. ref.current = new EventEmitter();
  36. }
  37. return ref.current;
  38. }
  • 如何保证全局只有一个 EventEmitter 实例,通过 ref.current 去保存,如果说 ref.current 已经赋值过,则不会再重新 new 生成实例

    1. const ref = useRef<EventEmitter<T>>();
    2. // 保证全局下只生成一个 EventEmitter 实例
    3. if (!ref.current) {
    4. ref.current = new EventEmitter();
    5. }
    6. return ref.current;
  • EventEmitter Class 整体结构 ```typescript type Subscription = (val: T) => void;

export class EventEmitter { // 所有回调事件,存储在 Set 内 private subscriptions = new Set>();

// 事件分发 emit = (val: T) => { // … }

// 事件订阅,是个 hooks useSubscription = (callback: Subscription) => { // … } }

  1. - EventEmitteremit 方法
  2. ```typescript
  3. // 遍历 subscriptions 回调事件,传值循环执行
  4. emit = (val: T) => {
  5. for (const subscription of this.subscriptions) {
  6. subscription(val);
  7. }
  8. };
  • EventEmitter,useSubscription 方法,使用 useRef 保存 callback,变更 .current 不会导致组件重复渲染。在初始化时,对 callback 进行包装,注册到 subscription Set 事件数组内,当组件注销时,会在 subscription Set 内删除对应的事件

    1. useSubscription = (callback: Subscription<T>) => {
    2. // eslint-disable-next-line react-hooks/rules-of-hooks
    3. const callbackRef = useRef<Subscription<T>>();
    4. callbackRef.current = callback;
    5. // eslint-disable-next-line react-hooks/rules-of-hooks
    6. useEffect(() => {
    7. function subscription(val: T) {
    8. if (callbackRef.current) {
    9. callbackRef.current(val);
    10. }
    11. }
    12. this.subscriptions.add(subscription);
    13. return () => {
    14. this.subscriptions.delete(subscription);
    15. };
    16. }, []);
    17. };

    useIsomorphicLayoutEffect:同构 useLayoutEffect 的 hooks,解决 SSR 问题

  • useLayoutEffect:DOM 变更后同步调用的 effect

  • useEffect:在每轮渲染完后执行的 effect
  • useLayouEffect 无法在 SSR 环境内使用,需要用 useEffect 替换
    1. // 判断是否在浏览器端,是的话则使用 useLayoutEffect,否则使用 useEffect
    2. const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;

    useLastest:返回当前最新值的 Hook,避免闭包问题

    ```javascript import { useRef } from ‘react’;

// 使用 useRef,在整个生命周期内值保持不变 function useLatest(value: T) { const ref = useRef(value); ref.current = value;

return ref; }

export default useLatest;

  1. <a name="aE2VC"></a>
  2. ### useMemoizedFn:持久化 function
  3. ```typescript
  4. type noop = (...args: any[]) => any;
  5. function useMemoizedFn<T extends noop>(fn: T) {
  6. if (process.env.NODE_ENV === 'development') {
  7. if (typeof fn !== 'function') {
  8. console.error(`useMemoizedFn expected parameter is a function, got ${typeof fn}`);
  9. }
  10. }
  11. const fnRef = useRef<T>(fn);
  12. // why not write `fnRef.current = fn`?
  13. // https://github.com/alibaba/hooks/issues/728
  14. fnRef.current = useMemo(() => fn, [fn]);
  15. const memoizedFn = useRef<T>();
  16. if (!memoizedFn.current) {
  17. memoizedFn.current = function (...args) {
  18. // eslint-disable-next-line @typescript-eslint/no-invalid-this
  19. return fnRef.current.apply(this, args);
  20. } as T;
  21. }
  22. return memoizedFn.current;
  23. }

useReactive:新的数据响应方式,直接修改值即可


  • ```typescript

// k:v 原对象:代理过的对象 const proxyMap = new WeakMap(); // k:v 代理过的对象:原对象 const rawMap = new WeakMap();

function isObject(val: Record): boolean { return typeof val === ‘object’ && val !== null; }

function observer>(initialVal: T, cb: () => void): T { const existingProxy = proxyMap.get(initialVal);

// 添加缓存 防止重新构建proxy if (existingProxy) { return existingProxy; }

// 防止代理已经代理过的对象 // https://github.com/alibaba/hooks/issues/839 if (rawMap.has(initialVal)) { return initialVal; }

const proxy = new Proxy(initialVal, { get(target, key, receiver) { console.log(‘get’, target, key, receiver); const res = Reflect.get(target, key, receiver); return isObject(res) ? observer(res, cb) : Reflect.get(target, key); }, set(target, key, val) { console.log(‘set’, target, key, val); const ret = Reflect.set(target, key, val); cb(); return ret; }, deleteProperty(target, key) { console.log(‘deleteProperty’, target, key); const ret = Reflect.deleteProperty(target, key); cb(); return ret; }, });

proxyMap.set(initialVal, proxy); rawMap.set(proxy, initialVal);

return proxy; }

function useReactive>(initialState: S): S { const update = useUpdate(); const stateRef = useRef(initialState);

const state = useCreation(() => { return observer(stateRef.current, () => { update(); }); }, []);

return state; } ```