根据文档横向对比vue2.0与vue3.0(三)

806 阅读4分钟

前提

在上篇文章里,根据vue2.0与vue3.0的文档,针对组件部分进行了横向对比。组件部分的差异其实是比较小的(个人认为组件部分是vue的核心,改动很大的话,岂不是啥玩意都要改啦),所以在上篇文章中,除了组件细节的横向对比外,大部分是组件部分的内容总结。

现在对比进行到了第三阶段啦,本篇内容主要为vue2.0与vue3.0的可复用&组合部分的横向对比。

这部分内容在我看来,其实已经不只是“横向对比细节差异”了,通过下方的目录对比就很显而易见,vue3.0新增内容占比更大。

可复用目录对比.png

这部分内容真的太多啦,文档如果是纸质的都要被翻烂😅,如果这篇文章能够给你带来收获就好了。

可复用部分对比思维导图.png

(对比差异部分,使用2️⃣3️⃣分别表示vue2.0与vue3.0)

3️⃣ 组合式API

3️⃣vue3.0 新增了 组合式API。

介绍

  • 为什么新增了组合式API:希望能够将同一个逻辑关注点相关的代码放在一起。

    个人认为,原来的组件为页面组件,注重页面和功能的公共性。组合式API为逻辑组件,注重逻辑功能上的公共性。

  • 使用:setup 选项位置就是组合式API入口。

setup 组件选项

基础结构

setup(props,context){return{}}

生命周期

在组件创建之前执行,当props被解析后,setup作为组合式API的入口。

所以,执行setup时,只能访问 props,attrs,slots,emit;不能访问data,computed,methods,refs(模板)

此外,setup 中应该避免使用 this,因为它不会找到组件实例。

setup的参数分析

props

1、props 是响应式的,当 prop 更新,此处数据也会更新。

2、不能直接使用 ES6 进行解构(会消除响应式)。正确的解构方法:

1)一般使用 toRefs

// 举例:
import { toRefs } from 'vue'

setup(props) {
  const { title } = toRefs(props)
  console.log(title.value)
}

2)当对应的 props 是可选项,即可能不存在时,需要使用toRef,创建对应 ref 来代替:

// 举例:
import { toRef } from 'vue'

setup(props) {
  const title = toRef(props, 'title')
  console.log(title.value)
}

context

1、context 是一个普通 JavaScript 对象,向内暴露了可能在 setup 中有用的值。

2、context 包含的内容:attrs、slots 插槽、emit 触发事件、expose 暴露公共property函数。

其中,attrs 与 slots 为非响应式对象,如果需要根据其更改做点什么,应该在onBeforeUpdate生命周期内执行。

// 举例:
export default {
  setup(props, context) {
    // Attribute (非响应式对象,等同于 $attrs)
    console.log(context.attrs)

    // 插槽 (非响应式对象,等同于 $slots)
    console.log(context.slots)

    // 触发事件 (方法,等同于 $emit)
    console.log(context.emit)

    // 暴露公共 property (函数)
    console.log(context.expose)
  }
}

3、context 特点:非响应式(故可以直接进行ES6解构)。

// 一般写法:
setup(props, context){} 

// 解构写法:
setup(props, { attrs, slots, emit, expose }) {}

setup 返回内容

  • 介绍:

    返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板(反正 return 出来的东西都可以在组件的其余部分使用)。

  • 可返回的内容:

    1、对象,包裹数据。如 return { readersNumber, book }

    2、渲染函数:

    //举例
    import { h, ref, reactive } from 'vue'
    
    export default {
      setup() {
        const readersNumber = ref(0)
        const book = reactive({ title: 'Vue 3 Guide' })
    
        return () => h('div', [readersNumber.value, book.title])
      }
    }
    

    返回渲染函数,就不能返回其他东西了的问题,可以用 expose 解决。

    expose 内的东西在外部也能被访问,return 就专心传渲染函数。

    // 举例
    import { h, ref } from 'vue'
    export default {
      setup(props, { expose }) {
        const count = ref(0)
        const increment = () => ++count.value
    
        expose({
          increment
        })
    
        return () => h('div', count.value)
      }
    }
    
    

内部注册生命周期钩子

  • 钩子名称:形式上,包含的可调用生命周期钩子,除了beforeCreate 和 created 没有,其他钩子会在原来的API名称基础上,添加前缀on。(如 mounted 写作 onMounted)

  • 钩子的执行:当组件生命周期被调用时,setup 内部的对应生命周期,也会执行对应的回调。

  • 钩子的使用:钩子函数有一个回调,当钩子被组件调用时,该回调就会被执行。

    1、引用的方式:如 onMounted(getUserRepositories) ,表示在 mounted 时调用 getUserRepositories

    2、回调的方式:如onMounted(() => { console.log('Component is mounted!') }), 当钩子被组件调用时,回调函数被执行。

ref 函数(reference)

  • 作用:创建响应式引用,使得无论是什么数据类型,均能响应。

  • 优点:保持 JavaScript 中不同数据类型的行为统一。

    在 js 中,基本类型的值是值复制而非引用,即不同根。使用 ref,则不管什么数据类型,因为都封装了对象,所以都是引用类型,都同根,这就能保证数据的响应式。

  • 参数:ref 的参数是定值,这是与 toRefs 的区别。

  • 本质:把值封装在对象中,变成响应式。

    ref 接收参数并将其包裹在一个带有 value property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值。

  • 使用步骤:

    1、从vue中引用。

    2、给 ref 函数传参(静态值),获取对应的响应式的值进行使用。

    import { ref } from 'vue'
    
    const counter = ref(0)
    console.log(counter.value) // 0
    
    counter.value++
    console.log(counter.value) // 1
    

toRefs

类似 ref 函数,也需要从 vue 中引用才能使用,其参数是 setup 中的 props,能创造 props 的响应式引用。

独立的watch

  • 引用:需要从vue中引用。

    import { ref, watch } from 'vue'

  • watch 的参数:

    1、一个想要侦听的响应式引用或 getter 函数:

    1)若为单个数据源,直接使用:
    const counter = ref(0)
    watch(counter, (newValue, oldValue) => {
       console.log('The new counter value is: ' + counter.value)
    })
    
    2)若需要侦听多个数据源,可用数组:
    watch([firstName, lastName], (newValues, prevValues) => {
      console.log(newValues, prevValues)
    })
    
    3)若完全侦听深度嵌套的对象和数组,可能需要对值进行深拷贝:
    import _ from 'lodash'
    
    const state = reactive({
      id: 1,
      attributes: {
        name: '',
      }
    })
    
    watch(
      () => _.cloneDeep(state),
      (state, prevState) => {
        console.log(state.attributes.name, prevState.attributes.name)
      }
    )
    
    state.attributes.name = 'Alex' // 日志: "Alex" ""
    

    2、一个回调函数。

    3、可选的配置选项:侦听深度嵌套的对象或数组中的属性变化 {deep:true}

  • 触发时间:每当侦听的值被修改,则会触发侦听并执行回执。

  • 对比:

    //新写法:
    const counter = ref(0)
    watch(counter, (newValue, oldValue) => {
      console.log('The new counter value is: ' + counter.value)
    })
    
    //相当于原来的:
    export default {
      data() {
        return {
          counter: 0
        }
      },
      watch: {
        counter(newValue, oldValue) {
          console.log('The new counter value is: ' + this.counter)
        }
      }
    }
    

独立的computed

  • 引用:从vue引用。

    import { ref, computed } from 'vue'

  • computed 的参数:

    一般类似 getter 的回调,输出只读的响应式的引用。

    也可用带有 get 和 set 的参数,创建可写的 ref 对象:

    const count = ref(1)
    const plusOne = computed({
      get: () => count.value + 1,
      set: val => {
        count.value = val - 1
      }
    })
    
    plusOne.value = 1
    console.log(count.value) // 0
    
  • 触发时间:每当依赖变化,则只读的计算值改变。

    import { ref, computed } from 'vue'
    
    const counter = ref(0)
    const twiceTheCounter = computed(() => counter.value * 2)
    
    counter.value++
    console.log(counter.value) // 1
    console.log(twiceTheCounter.value) // 2
    

组合式函数

  • 产生组合式函数的原因:

    每个逻辑块都放到setup里,会变得非常大,所以在继续其他任务之前,需要把代码提取到独立的组合式函数中。

    此外,使用一些组件的时候,其实并不关心过程,只是想要获取对应结果。

  • 思路:先做出单独的功能模块,再在组合式函数里融合。

组合式函数中的依赖注入 provide/inject

只能在当前活动实例的 setup() 期间调用。

provide的使用

  • 引入:import { provide } from 'vue'

  • 两个参数:provide(name,value)

  • 举例:

    setup() {
    provide('location', 'North Pole')
    provide('geolocation', {
      longitude: 90,
      latitude: 135
    })
    }
    
  • 添加响应性 (ref/reactive) 举例:

    setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })
    
    provide('location', location)
    provide('geolocation', geolocation)
    }
    

inject的使用

  • 引入:import { inject } from 'vue'

  • 两个参数:inject(name,可选的默认值default value)

  • 举例:

    setup() {
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')
    
    return {
      userLocation,
      userGeolocation
     }
    }
    

组合式函数中的模板引用 ref

1、ref 能够获取dom实例,在onMounted中可以获取得到 ref 对应实例。

<template> 
  <div ref="root">This is a root element</div>
</template>

<script>
  import { ref, onMounted } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      onMounted(() => {
        // DOM 元素将在初始渲染后分配给 ref
        console.log(root.value) // <div>This is a root element</div>
      })

      return {
        root
      }
    }
  }
</script>

2、侦听模板引用时,因为watch和watchEffect是在挂载前起作用的,所以当侦听运行时,模板引用还未更新。

需要添加 flush:'post',将在 DOM 更新后运行副作用,确保模板引用与 DOM 保持同步,并引用正确的元素。

setup() {
      const root = ref(null)

      watchEffect(() => {
        console.log(root.value) // => <div>This is a root element</div>
      }, 
      {
        flush: 'post'
      })

      return {
        root
      }
    }

混入 Mixin

  • 介绍:把可复用功能提取出来,可混入组件中使用,在组件内部进行“混合”。

  • 一般使用方式:

    1、定义mixin:mixin内部具有组件所有的选项,如created、methods。

    2、使用:在组件内 mixins 选项处引用 mixin。

  • 选项合并关系:

    1、数据上:组件和混入对象具有同名选项时,数据以组件优先。

    2、钩子函数上:同名钩子函数将合并为一个数组,因此都将被调用(混入对象的钩子将在组件自身钩子之前调用)。

    3、其他对象选项上:值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象,若同名则以组件优先。

  • 全局混入:会影响每个vue实例,建议少用。

  • 不足:容易发生冲突,可重用性有限。3️⃣vue3.0中推荐使用组合式API。

自定义指令

介绍

  • 自定义指令的适用场景:需要复用对普通DOM元素的底层操作时。

方法

  • 定义指令方法:

    1、全局/局部注册:directives 方法

    2、定义指令:在对应的钩子函数内定义指令

    // 全局指令:
    2️⃣举例:
    Vue.directive('xxx',{对应钩子函数xxx(el){}})
    
    3️⃣举例:
    const app = Vue.createApp({})
    app.directive('xxx',{对应钩子函数xxx(el){}}
    //------------------------------------------------
    // 局部指令(组件中的directive选项):
    directives:{xxx:{对应钩子函数xxx(el){}}}
    
  • 使用方法方法:v-xxx="value"。如果指令需要多个值,可以传入一个 JavaScript 对象字面量。

  • 简写方式:只关心特定钩子内触发行为,则可以直接使用回调函数,无需写出其他钩子。

    2️⃣在 bind 和 update 时触发相同行为,而不关心其他的钩子:
    
    Vue.directive('color-swatch', function (el, binding) {
      el.style.backgroundColor = binding.value
    })
    
    
    3️⃣在 mounted 和 updated 时触发相同行为,而不关心其他的钩子:
    
    app.directive('pin', (el, binding) => {
      el.style.position = 'fixed'
      const s = binding.arg || 'top'
      el.style[s] = binding.value + 'px'
    })
    

钩子函数

  • 2️⃣vue2.0对应的钩子函数:

    bind:适合初始化,仅调用一次,指令第一次绑定到元素时调用。

    inserted:被绑定元素插入父节点时调用。

    update:所在组件的 VNode 更新时调用。

    componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

    unbind:只调用一次,指令与元素解绑时调用。

    其中,钩子函数的参数为 el(最常用)、binding、vnode 和 oldVnode。除了 el 之外,其它参数都应该是只读的。

  • 3️⃣vue3.0对应的钩子函数:

    created:在绑定元素的 attribute 或事件监听器被应用之前调用。

    beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用。

    mounted:在绑定元素的父组件被挂载前调用。

    beforeUpdate:在更新包含组件的 VNode 之前调用。

    updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用。

    beforeUnmount:在卸载绑定元素的父组件之前调用。

    unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次。

  • 总结:vue2.0与vue3.0在钩子函数的名称上进行了更改。

动态指令参数

v-xxx:[argument]="value"

参数分析:

1、xxx:指令名称。

2、argument:传的属性,自定义指令内部,可通过binding.arg获取。

3、value:传的值,自定义指令内部,可通过 binding.value获取;若需多个值,可传对象字面量。

// 举例:

<div>
  <p v-pin:[direction]="pinPadding">Stick me {{ pinPadding + 'px' }} from the {{ direction || 'top' }} of the page</p>
</div>

// 指令内部
app.directive('pin', (el, binding) => {
    el.style.position = 'fixed'
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
})

传递性

和非 prop 的 attribute 类似,当在组件中使用时,自定义指令总是会被应用在组件的根节点上。

3️⃣和 attribute 不同,指令不会通过 v-bind="$attrs" 被传入另一个元素,vue3.0中允许多个根节点,当被应用在一个多根节点的组件上时,指令会被忽略,并且会抛出一个警告。

3️⃣ Teleport

vue3.0添加了 Teleport。

  • 作用:传送html到某个“父节点”标签下渲染。

  • 特点:仅仅是渲染位置不同,内部的属性逻辑还是原来的。

    如果原来是某个父组件的子组件,并且接受prop,就算被teleport到不同的地方渲染,页面结构上还是在原来的位置,并且仍然接受对应的props。

  • 使用:

    <teleport to="body">
      <div v-if="modalOpen" class="modal">
     </div>
    </teleport>
    
    //表示把teleport内的html传送到body标签下
    

渲染函数 render

  • 写法举例:

    2️⃣通过 createElement 创建
    render:function(createElement){
      return createElement(‘标签’,标签内容)
    }
    
    3️⃣通过 h() 创建
    const { createApp, h } = Vue
    const app = createApp({})
    
    render(){return h(‘标签’,标签内容)}
    
  • 分析:

    2️⃣ createElement=>3️⃣ h()

    vue2.0的 createElement 与vue3.0的 h(),返回的都是“虚拟节点”,也就是VNode,用来告诉VUE页面上要渲染啥节点,其子节点的描述信息。

  • vue2.0的 createElement 与vue3.0的 h() 参数:

    1、(tag) 标签/组件 类(必填):{String | Object | Function}

    2、(props) 与 attribute、prop 和事件相对应的对象(可选):{Object}

    3、(children) 子级虚拟节点 (VNodes)(可选):{String | Array | Object}

插件

  • 介绍:主要用来为VUE添加全局功能。

  • 使用插件方法:

    2️⃣在调用 new Vue() 前,通过 VUE.use() 使用插件。

    3️⃣在 createApp() 后,通过 use() 使用插件。

  • 特点:自动阻止多次注册相同插件。

2️⃣过滤器

3️⃣vue3.0 已经取消了过滤器的内容,过滤器的功能可以用方法或计算属性替代。

结尾

本篇文章主要为根据文档横向对比vue2.0与vue3.0的可复用性&组合部分。

看起来通篇的2️⃣3️⃣符号不多(对比差异部分,使用2️⃣3️⃣分别表示vue2.0与vue3.0),那是因为符号直接标在了大标题上,其实对比真的很明显。

本部分在对比中最大的区别,主要为vue3.0新增了 组合式API 与 Teleport 的内容,此外,还移除了过滤器功能。

接下来计划写这个系列的最后一篇文章啦,主要想写一些系列功能外的内容,类似响应式相关、api总概之类的。

传送门

vue2.0与vue3.0 基础部分 对比:根据文档横向对比vue2.0与vue3.0(一)

vue2.0与vue3.0 组件部分 对比:根据文档横向对比vue2.0与vue3.0(二)

vue2.0与vue3.0 可复用&组合 对比:根据文档横向对比vue2.0与vue3.0(三)

vue2.0与vue3.0 零碎部分 对比:根据文档横向对比vue2.0与vue3.0(四)