Vue3’s Composition API gives the idea of composing business logic that can be independent or coupled, which requires that some common logic be decouples to create its own “hooks” wheel Can you precipitate something like the Ahooks in React?

How to make a wheel

Combination of thinking

Composition API the Composition API is designed to extract repeatable parts of an interface and their functions into reusable code segments, making code more reusable and logical. Which logic needs to be encapsulated for reuse is the key to build the wheel, so we have to find a common logic reuse scheme from the stock problem.

Composition API: v3.cn.vuejs.org/guide/compo…

This is a classic Vue framework structure, Template and script through two-way binding data flow update, various third-party plug-ins are also cross-referenced in the business logic. When the volume of service increases rapidly, the logic code under script will expand infinitely, so the direction of the wheel encapsulation should be two:

  • Common business logic is pulled away
  • Secondary encapsulation of third-party libraries

I like to call this encapsulation vue-hooks because encapsulation is similar to react custom Hooks. As you can see, vue-hooks are encapsulation from Module and business logic, respectively. Using the “Hooks” wheel does not affect the original business logic, so pluggability of the tool must be considered, multiple use scenarios must be designed, while ensuring reuse.

Usage patterns

Let’s first look at how custom combinatorial functions are used,Vue-HooksIt is essentially a function, so we only care about what arguments are passed in, what values are returned, and whether or not we can type, resulting in the usage pattern shown in the figure below.

Local localStorage useStorage, pass in the first value is key, the second is the initial value, the third parameter is optional, you can choose operation sessionStorage.

const state = useStorage<Student>('locale-setting', {
  name: 'lisi'.class: 'machine',})Copy the code

Then we try to wrap a vue-hook according to the preset usage mode.

export function useStorage<T extends(string|number|boolean|object|null) > (key: string, defaultValue: T, storage: Storage = localStorage) {
  // step1 initialize a responsive data set with ref
  const data = ref<T>(defaultValue)

  function read() {
    try {
      let rawValue = storage.getItem(key)
      if (rawValue === undefined && defaultValue) {
        // Store the default value as string
        rawValue = transValue(defaultValue)
        storage.setItem(key, rawValue)
      }
      else {
        // Parse local string data
        data.value = transValue(rawValue, defaultValue)
      }
    }
    catch (e) {
      console.warn(e)
    }
  }

  read()

  // step2 listen for changes in storage. If other pages are also modified, linkage can be performed
  useEventListener('storage', read)

  // step3 listen for changes in data and modify localStorage
  watch(
    data,
    () = > {
      try {
        if (data.value == null)
          storage.removeItem(key)
        else
          storage.setItem(key, transValue(data.value))
      }
      catch (e) {
        console.warn(e)
      }
    },
    { flush: 'post'.deep: true},)return data
}
Copy the code

step1

The first step is to use ref to define the incoming default value as a response to facilitate page binding of the return value

step2

Use useEventListener (the same as the window.addEventListener binding) to listen for events, which are converted by different functions based on the value passed in, and keep cross-tab listening on the browser side.

step3

Use the WATCH API for data monitoring, if any data changes, it will synchronize localStorage.

From the above three steps, we can see that a general purpose vuE-hook is inseparable from vue3’s responsive API and Composition API, and it is more elegant to write with TS.

VueUse

VueUse was inspired by react-Use. React-use has been well developed, with over 16K stars on Github. VueUse also has a 1.8K star and is growing fast. VueUse is taking advantage of the latest vue3 update to keep up with the trend for responsive apis. VueUse is a collection of Composition API tools for vue2.x or vue3.x, similar to react hooks.

Here are the features of VueUse:

  • ⚡ Zero dependencies: Don’t worry about code size
  • 🌴 tree Shaking structure: Only the code you want is brought in
  • 🏷 Strong type checking: ts code full coverage
  • 🕶 Seamless migration: both Vue2 and vu3 are supported
  • 🌎 browser compatibility: can be directly CDN import
  • 🔌 Supports other tools

portal

Use the entry

NPM into

npm i @vueuse/core # yarn add @vueuse/core
Copy the code

Here is how the CDN is introduced. The browser environment can call the API directly using window.vueuse

<script src="https://unpkg.com/@vueuse/core"></script>
Copy the code

Let’s look at some of the more commonly used apis

createGlobalState

GlobalState management comes to mind when you first look at globalState. Common state management across components in vue is done using vuex, but vuex is tied to a single vue instance. CreateGlobalState of VUE-Use is used for common state management across VUE instances.

// store.js
import { createGlobalState, useStorage } from '@vueuse/core'

export const useGlobalState = createGlobalState(
  () = > useStorage('vue-use-locale-storage'),Copy the code

The above defines a common store, combined with the previous useStorage, for common state management of components created under different instances.

import { useGlobalState } from './store'

export default defineComponent({
  setup() {
    const state = useGlobalState()
    return { state }
  },
})
Copy the code

Creating an initial value of Reactive in globalState and then creating a VUE instance to bind to. This reactive state is universal across instances. And because it is called in createApp, the initialization logic may be created before the life cycle of all components.

function withScope<T extends object> (factory: () => T) :T {
  const container = document.createElement('div')

  let state: T = null as any

  // Create a vue instance to initialize the binding
  createApp({
    setup() {
      state = reactive(factory()) as T
    },
    render: () = > null,
  }).mount(container)

  return state
}

export function createGlobalState<T extends object> (
  factory: () => T,
) {
  let state: T

  // Use closures to cache state
  return () = > {
    if (state == null)
      state = withScope(factory)

    return state
  }
}
Copy the code

useEventListener

UseEventListener is mentioned in the package useStorage above, so the design of this API must consider the following:

  • The event type
  • Listen for the callback logic
  • Event capture is still bubbling
  • The mount object to listen on

If the mounted object has not been successfully loaded in the page, the JS context cannot get the corresponding DOM instance, and the binding event logic cannot be completed. Therefore, the binding event logic must be carried out in the life cycle of Vue. At the same time, when the component is uninstalled, the events that have been bound must also be uninstalled to prevent resource waste, which can be separated into common logic.

export function useEventListener(type: string, listener: EventListenerOrEventListenerObject, options? : boolean | AddEventListenerOptions, target: EventTarget =window.) {
  // Handle mounted binding logic
  tryOnMounted(() = > {
    target.addEventListener(type, listener, options)
  })

  // Process unMounted unbinding logic
  tryOnUnmounted(() = > {
    target.removeEventListener(type, listener, options)
  })
}

export function tryOnMounted(fn: () => void, sync = true) {
  // Check whether the vue instance exists
  if (getCurrentInstance())
    onMounted(fn)
  else if (sync)
    fn()
  else
    nextTick(fn)
}
Copy the code

As you can see from the code above, the core processing is still in the implementation of tryOnMouned and tryOnUnMouned, taking care of the entire lifecycle.

useAsyncState

Data requests in vUE projects typically use AXIOS, and useAsyncState encapsulates some of the asynchronous operations, which is ideal for using in conjunction with AXIOS to make the asynchronous logic clearer without having to write assignment operations into the resolve callback.

const { state, ready } = useAsyncState(
  axios
  .get('https://jsonplaceholder.typicode.com/todos/1')
  .then(t= > t.data),
  {},
  2000.)Copy the code

Any promise action can be wrapped in useAsyncState, and the ready return value can generally be used as a loading of the request transition state.

export function useAsyncState<T> (
  promise: Promise<T>,
  defaultState: T,
  delay = 0,
  catchFn = (e: Error) = > {},) {
  // Initialize the state value
  const state = ref(defaultState)
  const ready = ref(false)

  function run() {
    promise
      .then((data) = > {
        state.value = data
        ready.value = true
      })
      .catch(catchFn)
  }

  // Delay execution can be set
  if(! delay) run()else
    useTimeoutFn(run, delay)

  return { state, ready }
}
Copy the code

useDebounceFn/useThrottleFn

Before talking about these two apis, do you know the difference between debounce and throttle?

Stabilization:

The function is executed only once within n seconds after the high-frequency event is triggered. If the high-frequency event is triggered again within n seconds, the time is recalculated

Throttling:

High frequency events fire, but only execute once in n seconds, so throttling dilutes the frequency of the function’s execution

So the difference between the two is whether the delay function waiting to execute each time the event is triggered needs to be redefined. ** Stabilization is generally used as a listener for window scaling events, while throttling is used as a listener for input input events.

useDebounceFn

export function useDebounceFn<T extends Function> (fn: T, delay = 200) :T {
  if (delay <= 0)
    return fn

  let timer: ReturnType<typeof setTimeout> | undefined

  function wrapper(this: any, ... args: any[]) {
    const exec = () = > {
      timer = undefined
      return fn.apply(this, args)
    }
		// If the time callback already exists, refresh the timer again
    if (timer)
      clearTimeout(timer)

    timer = setTimeout(exec, delay)
  }

  return wrapper as any as T
}
Copy the code

useThrottleFn

export function useThrottleFn<T extends Function> (fn: T, delay = 200, trailing = true) :T {
  if (delay <= 0)
    return fn

  let lastExec = 0
  let timer: ReturnType<typeof setTimeout> | undefined
  let lastThis: any
  let lastArgs: any[]

  function clear() {
    if (timer) {
      clearTimeout(timer)
      timer = undefined}}function timeoutCallback() {
    clear()
    fn.apply(lastThis, lastArgs)
  }

  function wrapper(this: any, ... args: any[]) {
    const elapsed = Date.now() - lastExec

    clear()
		// Compare the last execution with the gap this time
    if (elapsed > delay) {
      lastExec = Date.now()
      fn.apply(this, args)
    }
    else if (trailing) {
      // Record the context of the current stack execution
      lastArgs = args
      lastThis = this
      timer = setTimeout(timeoutCallback, delay)
    }
  }

  return wrapper as any as T
}
Copy the code

useWindowScroll

UseWindowScroll is used to monitor page scroll events, useEventListener directly listens to window scroll events, do control sidebar display and hide is more convenient.

// How to use it
const { x, y } = useWindowScroll()

export function useWindowScroll() {
  const x = ref(isClient ? window.pageXOffset : 0)
  const y = ref(isClient ? window.pageYOffset : 0)

  useEventListener(
    'scroll'.() = > {
      x.value = window.pageXOffset
      y.value = window.pageYOffset
    },
    {
      capture: false.passive: true,})return { x, y }
}
Copy the code

The end of the

Vue-use also provides an API for handling firebase, RXJS, which contains Hooks from firebase and RXJS. If you want to Use vue-Use, you can Use it to check if there are any Hooks in vue-use that you can replace. If you want to Use vue-Use, you can Use it to save the Hooks from your own business scenarios. Wouldn’t it be nice to make a small contribution to the community

Creation is not easy, hope to dig a lot of praise + pay attention to erlian, continue to update!!

PS: If there are any mistakes in this article, please kindly correct them

Past wonderful 📌

  • Open react-redux ✨ using Hooks
  • React Hooks✨
  • React DevTools ✨
  • Vue3 hugs TypeScript properly ✨
  • Vue3-one piece尝鲜:React Hooks VS Composition API ✨
  • Optimize performance ✨

Public number: front end small DT