useControllableValue:管理组件受控非受控状态
- 受控组件:值状态完全由外部父组件管理,控制权在调用的父组件上
- 非受控组件:值状态由内部子组件管理
- hooks 传参:props -
```typescript
export interface Options
{ defaultValue?: T; defaultValuePropName?: string; valuePropName?: string; trigger?: string; }
export type Props = Record
export interface StandardProps
function useControllableValue
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; }
<a name="c838N"></a>### useCreation:useMemo 或 useRef 替代品,保证创建出来的常量,不会被重复创建useCreation 存在的意义<br /><br />useMemo:<br /><br />源码实现:- 本质还是使用 useRef,将 factory 函数执行的结果保存在 current.obj 内- 使用 initialized 标记是不是已经初始化过```typescriptimport type { DependencyList } from 'react';import { useRef } from 'react';import depsAreSame from '../utils/depsAreSame';export default function useCreation<T>(factory: () => T, deps: DependencyList) {const { current } = useRef({deps,obj: undefined as undefined | T,initialized: false,});// 只有 initialized 是 false// 或者 依赖列表发生变化的时候// 才需要重新执行 factory(), 获取新的实例if (current.initialized === false || !depsAreSame(current.deps, deps)) {current.deps = deps;current.obj = factory();current.initialized = true;}// 返回这个实例return current.obj as T;}
useEventEmitter:事件分发
import { useRef, useEffect } from 'react';type Subscription<T> = (val: T) => void;export class EventEmitter<T> {// 事件池子private subscriptions = new Set<Subscription<T>>();// 事件派发emit = (val: T) => {for (const subscription of this.subscriptions) {subscription(val);}};// 事件订阅useSubscription = (callback: Subscription<T>) => {// eslint-disable-next-line react-hooks/rules-of-hooksconst callbackRef = useRef<Subscription<T>>();callbackRef.current = callback;// eslint-disable-next-line react-hooks/rules-of-hooksuseEffect(() => {function subscription(val: T) {if (callbackRef.current) {callbackRef.current(val);}}this.subscriptions.add(subscription);return () => {this.subscriptions.delete(subscription);};}, []);};}export default function useEventEmitter<T = void>() {const ref = useRef<EventEmitter<T>>();// 保证全局下只生成一个 EventEmitter 实例if (!ref.current) {ref.current = new EventEmitter();}return ref.current;}
如何保证全局只有一个 EventEmitter 实例,通过 ref.current 去保存,如果说 ref.current 已经赋值过,则不会再重新 new 生成实例
const ref = useRef<EventEmitter<T>>();// 保证全局下只生成一个 EventEmitter 实例if (!ref.current) {ref.current = new EventEmitter();}return ref.current;
EventEmitter Class 整体结构 ```typescript type Subscription
= (val: T) => void;
export class EventEmitter
// 事件分发 emit = (val: T) => { // … }
// 事件订阅,是个 hooks
useSubscription = (callback: Subscription
- EventEmitter,emit 方法```typescript// 遍历 subscriptions 回调事件,传值循环执行emit = (val: T) => {for (const subscription of this.subscriptions) {subscription(val);}};
EventEmitter,useSubscription 方法,使用 useRef 保存 callback,变更 .current 不会导致组件重复渲染。在初始化时,对 callback 进行包装,注册到 subscription Set 事件数组内,当组件注销时,会在 subscription Set 内删除对应的事件
useSubscription = (callback: Subscription<T>) => {// eslint-disable-next-line react-hooks/rules-of-hooksconst callbackRef = useRef<Subscription<T>>();callbackRef.current = callback;// eslint-disable-next-line react-hooks/rules-of-hooksuseEffect(() => {function subscription(val: T) {if (callbackRef.current) {callbackRef.current(val);}}this.subscriptions.add(subscription);return () => {this.subscriptions.delete(subscription);};}, []);};
useIsomorphicLayoutEffect:同构 useLayoutEffect 的 hooks,解决 SSR 问题
useLayoutEffect:DOM 变更后同步调用的 effect
- useEffect:在每轮渲染完后执行的 effect
- useLayouEffect 无法在 SSR 环境内使用,需要用 useEffect 替换
// 判断是否在浏览器端,是的话则使用 useLayoutEffect,否则使用 useEffectconst useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
useLastest:返回当前最新值的 Hook,避免闭包问题
```javascript import { useRef } from ‘react’;
// 使用 useRef,在整个生命周期内值保持不变
function useLatest
return ref; }
export default useLatest;
<a name="aE2VC"></a>### useMemoizedFn:持久化 function```typescripttype noop = (...args: any[]) => any;function useMemoizedFn<T extends noop>(fn: T) {if (process.env.NODE_ENV === 'development') {if (typeof fn !== 'function') {console.error(`useMemoizedFn expected parameter is a function, got ${typeof fn}`);}}const fnRef = useRef<T>(fn);// why not write `fnRef.current = fn`?// https://github.com/alibaba/hooks/issues/728fnRef.current = useMemo(() => fn, [fn]);const memoizedFn = useRef<T>();if (!memoizedFn.current) {memoizedFn.current = function (...args) {// eslint-disable-next-line @typescript-eslint/no-invalid-thisreturn fnRef.current.apply(this, args);} as T;}return memoizedFn.current;}
useReactive:新的数据响应方式,直接修改值即可
```typescript
// k:v 原对象:代理过的对象 const proxyMap = new WeakMap(); // k:v 代理过的对象:原对象 const rawMap = new WeakMap();
function isObject(val: Record
function observer
// 添加缓存 防止重新构建proxy if (existingProxy) { return existingProxy; }
// 防止代理已经代理过的对象 // https://github.com/alibaba/hooks/issues/839 if (rawMap.has(initialVal)) { return initialVal; }
const proxy = new Proxy
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; } ```
