VUE内置 HOOKS钩子函数,HOOKS API的Vue实现

53 篇文章 0 订阅
25 篇文章 0 订阅

VUE内置 HOOKS钩子函数,HOOKS API的Vue实现

一、HOOKS

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

后来看到尤大在 Vue 3.0 最近进展 的视频中也提到了 Hooks API,并写了一个在 Vue 中使用 Hooks 的 POC。

可以把hooks当作更强、更实用的mixins。

二、使用步骤

1.withHooks

withHooks 用于包装一个 Vue 版的「函数式组件」,在这个函数式组件中,您可以使用 Hooks API。
使用

import { withHooks, useData, useComputed } from "vue-hooks"

const Foo = withHooks(h => {
  const data = useData({
    count: 0
  })
  const double = useComputed(() => data.count * 2)
  return h('div', [
    h('div', `count is ${data.count}`),
    h('div', `double count is ${double}`),
    h('button', { on: { click: () => {
      data.count++
    }}}, 'count++')
  ])
})

withHooks 实现细节

  1. callIndex,为 Hooks 相关的存储对象提供 key。这里每次渲染,都重置为 0,是为了能够根据调用次序匹配对应的 Hooks。这样处理也限制了 Hooks 只能在顶级代码中调用。
  2. currentInstance,结合 ensureCurrentInstance 函数,用于确保 Hooks 只能在函数式组件中使用。
  3. isMounting,用于标识组件的挂载状态
let currentInstance = null
let isMounting = false
let callIndex = 0

function ensureCurrentInstance() {
  if (!currentInstance) {
    throw new Error(
      `invalid hooks call: hooks can only be called in a function passed to withHooks.`
    )
  }
}

export function withHooks(render) {
  return {
    data() {
      return {
        _state: {}
      }
    },
    created() {
      this._effectStore = {}
      this._refsStore = {}
      this._computedStore = {}
    },
    render(h) {
      callIndex = 0
      currentInstance = this
      isMounting = !this._vnode
      const ret = render(h, this.$attrs, this.$props)
      currentInstance = null
      return ret
    }
  }
}

2.useState

useState 用于为组件添加一个响应式的本地状态,及该状态相关的更新器。
方法签名为: const [state, setState] = useState(initialState);
setState 用于更新状态: setState(newState);
使用

import { withHooks, useState } from "vue-hooks"
const Foo = withHooks(h => {
  const [count, setCount] = useState(0)
  return h("div", [
    h("span", `count is: ${count}`),
    h("button", { on: { click: () => setCount(count + 1) } }, "+" )
  ])
})

useState 实现细节

  1. 函数 ensureCurrentInstance 是为了确保 useState 必须在 render 中执行,也就是限制了必须在函数式组件中执行。
  2. 以 callIndex 生成的自增 id 作为存储状态值的 key。说明 useState 需要依赖第一次渲染时的调用顺序来匹配过去的 state(每次渲染 callIndex 都要重置为0)。这也限制了 useState 必须在顶层代码中使用。
  3. 其它 hooks 也必须遵循以上两点。
export function useState(initial) {
  ensureCurrentInstance()
  const id = ++callIndex
  // 获取组件实例的本地状态。
  const state = currentInstance.$data._state
  // 本地状态更新器,以自增id为键值,存储到本地状态中。
  const updater = newValue => {
    state[id] = newValue
  }
  if (isMounting) {
    // 通过$set保证其是响应式状态。
    currentInstance.$set(state, id, initial)
  }
  // 返回响应式状态与更新器。
  return [state[id], updater]
}

2.useEffect

useEffect 用于添加组件状态更新后,需要执行的副作用逻辑。
useEffect 指定的副作用逻辑,会在组件挂载后执行一次、在每次组件渲染后根据指定的依赖有选择地执行、并在组件卸载时执行清理逻辑(如果指定了的话)。
方法签名为: void useEffect(rawEffect, deps)
使用

import { withHooks, useState, useEffect } from "vue-hooks"

const Foo = withHooks(h => {
  const [count, setCount] = useState(0)
  useEffect(() => {
    document.title = "count is " + count
  })
  return h("div", [
    h("span", `count is: ${count}`),
    h("button", { on: { click: () => setCount(count + 1) } }, "+" )
  ])
})

useState 实现细节

  1. mounted 时,固定地执行一次。
  2. 如果 deps 未指定,则每次 updated 后都执行一次。
  3. 如果 deps 为空数组,则 updated 后不执行。
  4. 如果 deps 指定了依赖项,则当相应的依赖项的值改变时,执行一次。
  ensureCurrentInstance()
  const id = ++callIndex
  if (isMounting) {
    // 组件挂载前,重新包装「清理逻辑」与「副作用逻辑」。
    const cleanup = () => {
      const { current } = cleanup
      if (current) {
        current()
        // 清理逻辑执行完,则重置回 null;
        // 如果副作用逻辑二次执行,cleanup.current 会被重新赋值。
        cleanup.current = null
      }
    }
    const effect = () => {
      const { current } = effect
      if (current) {
        // rawEffect 的返回值,如果是一个函数的话,则定义为 useEffect副作用 的清理函数。
        cleanup.current = current()
        // rawEffect 执行完,则重置为 null;
        // 如果相关的 deps 发生变化,需要二次执行 rawEffect 时 effect.current 会被重新赋值。
        effect.current = null
      }
    }
    effect.current = rawEffect
    // 在组件实例上,存储 useEffect 相关辅助成员。
    currentInstance._effectStore[id] = {
      effect,
      cleanup,
      deps
    }
    // 组件实例 mounted 时,执行 useEffect 逻辑。
    currentInstance.$on('hook:mounted', effect)
    // 组件实例 destroyed 时,执行 useEffect 相关清理逻辑。
    currentInstance.$on('hook:destroyed', cleanup)
    // 若未指定依赖项或存在明确的依赖项时,组件实例 updated 后,执行 useEffect 逻辑。
    // 若指定依赖项为 [], 则 useEffect 只会在 mounted 时执行一次。
    if (!deps || deps.length > 0) {
      currentInstance.$on('hook:updated', effect)
    }
  } else {
    const record = currentInstance._effectStore[id]
    const { effect, cleanup, deps: prevDeps = [] } = record
    record.deps = deps
    if (!deps || deps.some((d, i) => d !== prevDeps[i])) {
      // 若依赖的状态值有变动时,在副作用重新执行前,执行清理逻辑。
      cleanup()
      // useEffect 执行完毕后,会将 current 的属性置为 null. 这里为 effect.current 重新赋值,
      // 是为了在 updated 后执行 rawEffect 逻辑。
      effect.current = rawEffect
    }
  }
}

总结

good afternoon
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值