zoukankan      html  css  js  c++  java
  • vue-hooks学习笔记(含源码解读)

    背景
    hooks 百度翻译为钩子,不要把 Hooks 和 Vue 的 生命周期钩子(Lifecycle Hooks) 弄混了,Hooks 是 React 在 V16.7.0-alpha 版本中引入的,而且几天后 Vue 发布了其概念验证版本。
    最近尤大发布了一个最新的npm包
    Hook是react中得一项新功能提案,可以让开发人员在不编写Class的情况下使用状态和其他React功能。

    定义
    Hooks 主要是对模式的复用提供了一种更明确的思路 —— 避免重写组件本身,并允许有状态逻辑的不同部分能无缝地进行协同工作。

    无状态函数式组件也非常受欢迎,但由于它们只能单纯地渲染,所以它们的用途仅限于展示任务。

    Hooks 允许我们使用函数调用来定义组件的有状态逻辑,从而解决这些问题。这些函数调用变得更具有组合性、可复用性,并且允许我们在使用函数式组件的同时能够访问和维护状态。

    为什么 Vue 中需要 Hooks?
    Hooks 在 Vue 中必须提供什么。这似乎是一个不需要解决的问题。毕竟,类并不是 Vue 主要使用的模式。Vue 提供无状态函数式组件(如果需要它们),但为什么我们需要在函数式组件中携带状态呢?我们有 mixins 用于组合可以在多个组件复用的相同逻辑。问题解决了。

    源码解读

    h函数是createElement,生产一个VNode节点,即html DOM节点

    createElement(也就是h)是vuejs里的一个函数。这个函数的作用就是生成一个 VNode节点,render 函数得到这个 VNode 节点之后,返回给 Vue.js 的 mount 函数,渲染成真实 DOM 节点,并挂载到根节点上。

    1、useEffect 做了什么?

    通过使用这个 Hook,通知 React 组件需要在渲染后执行什么操作。React 将记住传递的 function(把这个 function 成为 “effect”),并在执行 DOM 更新后调用这个 function。在这个效果中,主要的功能仍旧是设置 document.title,但是也可以执行数据获取,或者是调用其他的命令式的 API。

    isMounting:是否为首次渲染

    vue options上声明的几个本地变量:

    • _state:放置响应式数据
    • _refsStore:放置非响应式数据,且返回引用类型
    • _effectStore:存放副作用逻辑和清理逻辑
    • _computedStore:存放计算属性

    vue-hooks暴露了一个hooks函数,开发者在入口Vue.use(hooks)之后,可以将内部逻辑混入所有的子组件。这样,我们就可以在SFC组件中使用hooks啦。

    Hooks 和 mixins 之间的主要区别之一是 Hooks 实际上可以互相传值
    _vnode初始化为null,在mounted阶段会被赋值为当前组件的v-dom
    Hooks的思路是将一个组件拆分为较小的函数,而不是基于生命周期方法强制拆分。
    seEffect提供了类似于 componentDidMount等生命周期钩子的功能 vue里面的mounted

    hooks的方法 useData useState只能在hooks或者widthHooks中使用

    hooks中的数据是根据useState出现的顺序来定的

    借助withHooks,我们可以发挥hooks的作用,但牺牲来很多vue的特性,比如props,attrs,components等。

    所谓的 “Effect” 对应的概念叫做 “side effect”。指的是状态改变时,相关的远端数据异步请求、事件绑定、改变 DOM 等;因为此类操作要么会引发其他组件的变化,要么在渲染周期中并不能立刻完成,所以就称其为“副作用”。

    REACT
    useEffect 能够在组件 render 之后进行不同类型的副作用。某些 effect 可能需要清理,因此可以在 effect 中返回一个 function:

    参考文档
    react
    http://www.ptbird.cn/react-hoot-useEffect.html
    vue
    https://www.jianshu.com/p/f1e6597b19de 简书
    http://www.sohu.com/a/321909448_500651 精度vue-hooks
    https://juejin.im/post/5c7784d5f265da2de713629c 掘金
    https://mp.weixin.qq.com/s/p2f3jsko91iGhrbtjgmt7g?utm_medium=hao.caibaojian.com&utm_source=hao.caibaojian.com 云前端
    https://1byte.io/react-hooks/ react
    https://blog.csdn.net/liuyingv8/article/details/84068075 react 30分钟

    传统vue组件的缺点

    • 跨组件代码难以复用
    • 大组件,维护困难,颗粒度不好控制,细粒度划分时,组件嵌套存层次太深-影响性能
    • 类组件,this不可控,逻辑分散,不容易理解
    • mixins具有副作用,逻辑互相嵌套,数据来源不明,且不能互相消费

    Q:
    1,currentInstance是如何记录当前实例的
    当前hooks文件的this就是当前的vue实例,将this赋值给currentInstance,然后将_effectStore等赋值给当前vue实例即可

    2,currentInstance是如何成为proxy对象的 未知
    currentInstance为当前vue实例,this即为proxy对象

    3,hooks如何解决minix的问题的

    • 数据消费
      hooks能够方位当前vue实例的数据,可以相互消费
    • 数据来源
      hooks为我们手动调用的,所以数据来源为哪里就显然易见了

    4,beforeMount里面,将currentInstance赋值了又置为空
    赋值后,触发了render函数,注册了事件,置空当前变量

    5,reder时,h的两次的用义
    foo函数中的h函数是为了将jsx转为option对象,第二个h函数是为了option对象转为虚拟dom

    6,id递增是为了每次获取新值?
    vue-hooks将数据的获取与设置以id来代替,访问id即可得到映射的值,每个vue实例中的数据所对应的id是固定的

    7, currentInstance.$on('hook:mounted')的emit在哪里
    vue源码支持,详见截图

    8,hooks是否能够生命data或者computed,props
    能访问,是否能定义还未知
    不需要定义,直接在vue实例中的hooks钩子中return即可,template就能

    mixins混入的问题是什么?vue-hooks是怎么解决其问题的
    mixins 不能相互消费和使用状态,但 Hooks 可以。
    hooks的用法?

    9,什么时候会多次渲染

    10,hooks钩子在哪个生命周期后面执行
    beforeMount

    11,不能放在条件或循环中

    • 对 useState() 的调用次数必须是一样的。
    • 与各状态对应的 useState()的调用顺序是一样的。

    12,自定义hooks是什么,解决什么问题,怎么使用,会有什么问题?

    13,不全局使用vue-hooks,只在相应的hooks文件import可以吗?
    不行,withHooks依旧是返回一个vue component的配置项options,后续的hooks相关的属性都挂载在本地提供的options上。

    14,不能申明相同的属性_state,会被覆盖

    vue-hooks解决的问题

    • 实现了mixins的功能,并且解决了mixins的两个问题
      • 允许相互传递状态
      • 明确指出了逻辑来自哪里
        使用 Hooks,函数的返回值会记录消费的值。
    • vue-hooks是简化组件定义、复用状态逻辑的一种最新尝试,且结合 Vue 实例的特点提供了适用的 Hooks

    hooks.js中的this为当前vue实例

    react-hooks
    hooks只能出现在函数作用域的顶级,不能出现在条件语句、循环语句中、嵌套函数中。

    总结

    withHooks 返回一个包装过的 Vue 实例配置

    hooks 以 mixin 的形式发挥作用,注入两个生命周期

    用模块局部变量 currentInstance 记录了 Hooks 生效的 Vue 实例

    使用方式

    withHooks为vue组件提供了hooks+VNode,使用方式如下:

    withHooks 返回一个包装过的 Vue 实例配置

    • Vue式钩子
    • 在普通Vue组件中的用法

    使用注意点
    如果 useState 被包裹在 condition 中,那每次执行的下标就可能对不上,导致 useState 导出的 setter 更新错数据。

    源码解读

    let currentInstance = null //缓存当前的vue实例
    let isMounting = false // render是否为首次渲染
    let callIndex = 0 // 当前数据对应的索引,当往options上挂载属性时,使用callIndex作为唯一当索引标识
    
    function ensureCurrentInstance() { // 是否有实例
      if (!currentInstance) {
        // 无效的挂钩调用:只能在传递给withhooks的函数中调用挂钩
        throw new Error(
          `invalid hooks call: hooks can only be called in a function passed to withHooks.`
        )
      }
    }
    
    export function useState(initial) {
      ensureCurrentInstance()
      const id = ++callIndex
      const state = currentInstance.$data._state
      // 通过闭包提供了一个更新器updater
      const updater = newValue => {
        state[id] = newValue
      }
      if (isMounting) {
        currentInstance.$set(state, id, initial)
      }
      // 下一次的render过程,不会在重新使用$set初始化
      return [state[id], updater]
    }
    
    // 负责副作用处理和清理逻辑
    // 这里的副作用可以理解为可以根据依赖选择性的执行的操作
    // 没必要每次re-render都执行,比如dom操作,网络请求等。
    // 而这些操作可能会导致一些副作用,比如需要清除dom监听器,清空引用等等。
    export function useEffect(rawEffect, deps) {
      ensureCurrentInstance()
      const id = ++callIndex
    
      // 初始化时,声明了清理函数和副作用函数,并将effect的current指向当前的副作用逻辑,
      // 在mounted阶段调用一次副作用函数,将返回值当成清理逻辑保存。
      // 同时根据依赖来判断是否在updated阶段再次调用副作用函数。
      if (isMounting) {
        const cleanup = () => {
          const { current } = cleanup
          if (current) {
            current()
            cleanup.current = null
          }
        }
        const effect = function() {
          const { current } = effect
          if (current) {
            // 将返回值当成清理逻辑保存
            cleanup.current = current.call(this)
            effect.current = null
          }
        }
        // 将effect的current指向当前的副作用逻辑,在mounted阶段调用一次副作用函数
        effect.current = rawEffect
    
        currentInstance._effectStore[id] = {
          effect,
          cleanup,
          deps
        }
        // vue-devsrccoreinstancelifecycle.js
        currentInstance.$on('hook:mounted', effect)
        currentInstance.$on('hook:destroyed', cleanup)
        if (!deps || deps.length > 0) {
          currentInstance.$on('hook:updated', effect)
        }
      } else {
        // 非首次渲染时,会根据deps依赖来判断是否需要再次调用副作用函数,
        // 需要再次执行时,先清除上一次render产生的副作用,
        // 并将副作用函数的current指向最新的副作用逻辑,等待updated阶段调用。
        const record = currentInstance._effectStore[id]
        const { effect, cleanup, deps: prevDeps = [] } = record
        record.deps = deps
        if (!deps || deps.some((d, i) => d !== prevDeps[i])) {
          cleanup()
          effect.current = rawEffect
        }
      }
    }
    
    // f初始化会返回一个携带current的引用,current指向初始化的值
    export function useRef(initial) {
      ensureCurrentInstance()
      const id = ++callIndex
      const { _refsStore: refs } = currentInstance
      return isMounting ? (refs[id] = { current: initial }) : refs[id]
    }
    
    // 挂载一个响应式数据,但是没有提供更新器
    export function useData(initial) {
      const id = ++callIndex
      const state = currentInstance.$data._state
      if (isMounting) {
        currentInstance.$set(state, id, initial)
      }
      return state[id]
    }
    
    // useEffect依赖传[]时,副作用函数只在mounted阶段调用。
    export function useMounted(fn) {
      useEffect(fn, [])
    }
    
    // useEffect依赖传[]且存在返回函数,返回函数会被当作清理逻辑在destroyed调用。
    export function useDestroyed(fn) {
      useEffect(() => fn, [])
    }
    
    // 如果deps固定不变,传入的useEffect会在mounted和updated阶段各执行一次,
    // 这里借助useRef声明一个持久化的变量,来跳过mounted阶段。
    export function useUpdated(fn, deps) {
      const isMount = useRef(true)
      useEffect(() => {
        if (isMount.current) {
          isMount.current = false
        } else {
          return fn()
        }
      }, deps)
    }
    
    export function useWatch(getter, cb, options) {
      ensureCurrentInstance()
      // 加了一个是否初次渲染判断,防止re-render产生多余Watcher观察者。
      if (isMounting) {
        currentInstance.$watch(getter, cb, options)
      }
    }
    
    export function useComputed(getter) {
      ensureCurrentInstance()
      const id = ++callIndex
      const store = currentInstance._computedStore
      if (isMounting) {
        // 先会计算一次依赖值并缓存
        store[id] = getter()
        // 调用$watch来观察依赖属性变化,并更新对应的缓存值。
        currentInstance.$watch(getter, val => {
          store[id] = val
        }, { sync: true })
      }
      return store[id]
    }
    
    export function withHooks(render) {
      return {
        data() {
          return {
            _state: {}  // 不能申明相同的属性_state,会被覆盖
          }
        },
        created() {
          this._effectStore = {}
          this._refsStore = {}
          this._computedStore = {}
        },
        render(h) {
          callIndex = 0
          currentInstance = this // 将当前的
          isMounting = !this._vnode // _vnode初始化为null,在mounted阶段会被赋值为当前组件的v-dom,isMounting除了控制内部数据初始化的阶段外,还能防止重复re-render
          const ret = render(h, this.$attrs, this.$props) // 传入了attrs和$props作为入参,且在渲染完当前组件后
          currentInstance = null // 重置全局变量,以备渲染下个组件。
          return ret
        }
      }
    }
    
    export function hooks (Vue) {
      Vue.mixin({ // 换入两个生命周期
        beforeCreate() {
          const { hooks, data } = this.$options
          if (hooks) {
            this._effectStore = {}
            this._refsStore = {}
            this._computedStore = {}
            this.$options.data = function () {
              const ret = data ? data.call(this) : {}
              ret._state = {} // 重置_state属性
              return ret
            }
          }
        },
        beforeMount() {
          const { hooks, render } = this.$options
          if (hooks && render) {
            this.$options.render = function(h) {
              callIndex = 0
              currentInstance = this
              isMounting = !this._vnode // _vnode初始化为null,在mounted阶段会被赋值为当前组件的v-dom
              const hookProps = hooks(this.$props) // 调用hooks方法,将return的字段放到实例本身上,即可得到响应数据
              Object.assign(this._self, hookProps)
              const ret = render.call(this, h)
              currentInstance = null
              return ret
            }
          }
        }
      })
    }
    
    
  • 相关阅读:
    保险精算导论
    天津大学C语言程序设计
    会计学
    WIN10 CH340安装失败
    好用的浏览器插件
    好用的壁纸软件
    30讲 窗口看门狗
    STM32替换Arduino直通车
    stm32系列芯片独立看门狗(IWDG)溢出时间计算原理
    AD 电子元器件图片、名称及符号对照
  • 原文地址:https://www.cnblogs.com/Mrdouhua/p/13186232.html
Copyright © 2011-2022 走看看