vueuse state篇、请求篇、事件篇

state篇

createGlobalState:全局响应式状态

  • 通过scope.run来管理依赖,作用域销毁时自动清除依赖,避免内存泄漏
export function createGlobalState<T>(
  stateFactory: () => T,
): CreateGlobalStateReturn<T> {
  let initialized = false
  let state: T
  const scope = effectScope(true)

  return () => {
    if (!initialized) {
      state = scope.run(stateFactory)!
      initialized = true
    }
    return state
  }
}

createInjectionState:通过provide/inject实现状态管理

  • 随时可以更新provide
  • 内部闭包通过symbol避免重复
export function createInjectionState<Arguments extends Array<any>, Return>(
  composable: (...args: Arguments) => Return,
): readonly [useProvidingState: (...args: Arguments) => Return, useInjectedState: () => Return | undefined] {

  const key: string | InjectionKey<Return> = Symbol('InjectionState')
  // 外部可以通过该方法随时修改provide
  const useProvidingState = (...args: Arguments) => {
    const state = composable(...args)
    provide(key, state)
    return state
  }
  const useInjectedState = () => inject(key)
  
  return [useProvidingState, useInjectedState]
}

createSharedComposable:共享状态

  • 可以自动卸载依赖
export function createSharedComposable<Fn extends((...args: any[]) => any)>(composable: Fn): Fn {
  let subscribers = 0
  let state: ReturnType<Fn> | undefined
  let scope: EffectScope | undefined
  // 当返回的hook执行时所在的scope卸载时执行
  const dispose = () => {
    subscribers -= 1
    if (scope && subscribers <= 0) {
      // 当外部用到的返回hook都清除完毕后,卸载掉自身内部的scope,避免内存泄漏
      scope.stop()
      state = undefined
      scope = undefined
    }
  }

  return <Fn>((...args) => {
    subscribers += 1
    if (!state) {
      scope = effectScope(true)
      state = scope.run(() => composable(...args))
    }
    tryOnScopeDispose(dispose)
    return state
  })
}
export function tryOnScopeDispose(fn: Fn) {
  if (getCurrentScope()) {
    onScopeDispose(fn)
    return true
  }
  return false
}

useManualRefHistory:具有改变记录、撤销和恢复状态

  • 栈顶元素配合undo和redo数组实现
export function useManualRefHistory<Raw, Serialized = Raw>(
  source: Ref<Raw>,
  options: UseManualRefHistoryOptions<Raw, Serialized> = {},
): UseManualRefHistoryReturn<Raw, Serialized> {

  const {
    clone = false,
    dump = defaultDump<Raw, Serialized>(clone),
    parse = defaultParse<Raw, Serialized>(clone),
    setSource = fnSetSource,
  } = options

  function _createHistoryRecord(): UseRefHistoryRecord<Serialized> {
    return markRaw({
      snapshot: dump(source.value),
      timestamp: timestamp(),
    })
  }
  // 可以理解为栈顶元素
  const last: Ref<UseRefHistoryRecord<Serialized>> = ref(_createHistoryRecord()) as Ref<UseRefHistoryRecord<Serialized>>

  // undo 存放最新 commit 之前的 commit 记录
  const undoStack: Ref<UseRefHistoryRecord<Serialized>[]> = ref([])
  // 存放每次commit
  const redoStack: Ref<UseRefHistoryRecord<Serialized>[]> = ref([])

  // 设置源内容的值、设置栈顶
  const _setSource = (record: UseRefHistoryRecord<Serialized>) => {
    setSource(source, parse(record.snapshot))
    last.value = record
  }

  // commit时往撤销undo中存入上次commit时的状态,然后更新当前commit时状态
  const commit = () => {
    undoStack.value.unshift(last.value)
    last.value = _createHistoryRecord()

    if (options.capacity && undoStack.value.length > options.capacity)
      undoStack.value.splice(options.capacity, Infinity)
    if (redoStack.value.length)
      redoStack.value.splice(0, redoStack.value.length)
  }

  const clear = () => {
    undoStack.value.splice(0, undoStack.value.length)
    redoStack.value.splice(0, redoStack.value.length)
  }

  // 撤销操作
  const undo = () => {
    // 把栈顶之前的元素拿出来,用于设置为栈顶
    const state = undoStack.value.shift()

    if (state) {
      // redo中存入栈顶
      redoStack.value.unshift(last.value)
      _setSource(state)
    }
  }

  // 恢复撤销操作
  const redo = () => {
    // 保存的都是之前的栈顶元素
    const state = redoStack.value.shift()

    if (state) {
      // 因为是恢复,将当前栈顶元素变成历史元素
      undoStack.value.unshift(last.value)
      // 将当前栈顶元素设置成保存的之前的栈顶元素
      _setSource(state)
    }
  }

  const reset = () => {
    _setSource(last.value)
  }

  // 这里history的记录,首项为最新的栈顶元素,这样设计是为了undo撤销时,将最新状态存入恢复redo栈中,然后undo数组弹出的其实是第二次的栈顶元素
  const history = computed(() => [last.value, ...undoStack.value])

  const canUndo = computed(() => undoStack.value.length > 0)
  const canRedo = computed(() => redoStack.value.length > 0)

  return {
    source,
    undoStack,
    redoStack,
    last,
    history,
    canUndo,
    canRedo,

    clear,
    commit,
    reset,
    undo,
    redo,
  }
}

请求篇

useAsyncState:类似react-query

  • 传入一个Promise或返回Promise的异步方法,实现类似react-query的调用
export function useAsyncState<Data, Shallow extends boolean = true>(
  promise: Promise<Data> | ((...args: any[]) => Promise<Data>),
  initialState: Data, // 初始状态
  options?: UseAsyncStateOptions<Shallow, Data>,
): UseAsyncStateReturn<Data, Shallow> {

  const {
    immediate = true, //自动调用,否则需要开发者手动调用excute执行请求
    delay = 0,
    onError = noop,
    onSuccess = noop,
    resetOnExecute = true, // 每次excute重置state为初始状态,适用于刷新的场景
    shallow = true, // shallow避免数据过大影响性能
    throwError,
  } = options ?? {}

  const state = shallow ? shallowRef(initialState) : ref(initialState)
  const isReady = ref(false)
  const isLoading = ref(false)
  const error = ref<unknown | undefined>(undefined)

  async function execute(delay = 0, ...args: any[]) { // 加上async使得手动调用时能够await
    if (resetOnExecute)
      state.value = initialState
    error.value = undefined
    isReady.value = false
    isLoading.value = true

    if (delay > 0)
      // delay时间到了才resolve
      await promiseTimeout(delay)

    const _promise = typeof promise === 'function'
      ? promise(...args)
      : promise

    try {
      const data = await _promise
      state.value = data
      isReady.value = true
      onSuccess(data)
    }
    catch (e) {
      error.value = e
      onError(e)
      if (throwError)
        throw error
    }
    finally {
      isLoading.value = false
    }

    return state.value as Data
  }

  if (immediate)
    execute(delay)

  return {
    state: state as Shallow extends true ? Ref<Data> : Ref<UnwrapRef<Data>>,
    isReady,
    isLoading,
    error,
    execute,
  }
}

事件篇

useEventListener:事件注册、手动|自动卸载事件

  • 支持切换元素时,自动切换绑定事件
/**
 * @param target // 要绑定事件的dom元素,可以传ref或者el,不传默认绑定window
 * @param event // 要绑定的事件名 
 * @param listener // 事件回调,可以是个数组
 * @param options // 传递给addEventListener的第三个参数
 */
export function useEventListener(...args: any[]) {
  let target: MaybeComputedRef<EventTarget> | undefined
  let events: Arrayable<string>
  let listeners: Arrayable<Function>
  let options: any
  
  // 判断是否传递了target,不传默认为window
  if (isString(args[0]) || Array.isArray(args[0])) {
    [events, listeners, options] = args
    target = defaultWindow
  }
  else {
    [target, events, listeners, options] = args
  }

  if (!target)
    return noop
  // 事件名和回调统一变成数组结构
  if (!Array.isArray(events))
    events = [events]
  if (!Array.isArray(listeners))
    listeners = [listeners]
    
  // 存放事件的卸载方法
  const cleanups: Function[] = []
  
  const cleanup = () => {
    cleanups.forEach(fn => fn())
    cleanups.length = 0
  }
 
  const register = (el: any, event: string, listener: any) => {
    el.addEventListener(event, listener, options)
    return () => el.removeEventListener(event, listener, options)
  }
  
  const stopWatch = watch(
    // 元素可以传递一个ref,如果是ref返回ref.$el
    () => unrefElement(target as unknown as MaybeElementRef),
    (el) => {
      // 注册前先清除之前回调
      cleanup()
      if (!el)
        return
     
      cleanups.push(
        // 扁平化listeners对应的卸载方法,因为['click','tap'],[listen1,listen2] 会返回 [[clickListen1,tapListen1],[clickListen2,tapListen2]]
        ...(events as string[]).flatMap((event) => {
          return (listeners as Function[]).map(listener => register(el, event, listener))
        }),
      )
    },
    { immediate: true, flush: 'post' },
  )
  // 交给开发者手动清除回调
  const stop = () => {
    stopWatch()
    cleanup()
  }
  // 所在scope作用域销毁时清除所有回调
  tryOnScopeDispose(stop)

  return stop
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值