Vue3.x和Vue2.x的不同点

1、main.js

引入方式不一样

// vue3
//code by www.joynop.com
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
// 没有全局的Vue
// vue2.X
//code by www.joynop.com
import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})

2、全局API

Vue3里面没有全局的Vue,但是有个新的全局API:createApp

//code by www.joynop.com
import { Vue, createApp } from 'vue'
console.log(Vue)
// undefined

const app = createApp({})
console.log(app)

component: ƒ component(name, component)
config: Object
directive: ƒ directive(name, directive)
mixin: ƒ mixin(mixin)
mount: (containerOrSelector) => {…}
provide: ƒ provide(key, value)
unmount: ƒ unmount()
use: ƒ use(plugin, ...options)
version: "3.0.0-rc.10"
_component: {}
_container: null
_context: {app: {…}, config: {…}, mixins: Array(0), components: {…}, directives: {…}, …}
_props: null
_uid: 1
get config: ƒ config()
set config: ƒ config(v)
2.x 全局 API3.x全局 API
Vue.configapp.config
Vue.config.productionTip移除
Vue.config.ignoredElementsapp.config.isCustomElement
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use

使用第三方插件

示例:使用router,引入 app.use()

    const app = createApp(MyApp)
    app.use(VueRouter)

3、Composition API

import {ref, reactive, watch, computed, reactive, toRefs, createApp} from 'vue'

1. setup

要开始使用Composition API,我们首先需要一个可以实际使用它的地方。在Vue组件中,我们将此位置称为setup。

setup执行时尚未创建组件实例,所以不能使用this,此时this 为 undefined。除了props,无法访问组件中声明的任何data、computed、methods。

参数(props, context)

// props 父组件传过来的props props是具有反应性的(传入新的props时会自动更新)
// context {attrs, emit, slots}
// by www.joynop.com
setup(props, context) {
    console.log(context)
    /**
     *  attrs: Proxy
     *  emit: (event, ...args) => instance.emit(event, ...args)
     *  slots: Proxy
    */
}

setup 生命周期钩子

钩子函数setup钩子
beforeCreate没有
created没有
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
由于setup是围绕beforeCreate和created生命周期挂钩运行的,因此您无需显式定义它们。换句话说,应该在这些钩子中编写的任何代码都应直接在setup函数中编写。

JSX

setup() {
    const root = ref(null)
    return () => <div ref={root} />
}

2. watch监听

vue中引入 watch 可以参考 第4点 Reactivity(反应性)

  1. 监听 ref 声明的 反应性数据
  2. 监听 reactive 声明的反应性数据
  3. 监听 props 里面的数据
<script>
//一定使用之前引入
import {ref, watch} from 'vue'
setup(props) {
    //监听 ref 数据
    let count = ref(0)
    watch(count, (val, old) => {
        // 新数据、老数据
    })
    
    //监听 reactive 数据  对象
    let person = reactive({
        age: 18
    })
    watch(() => person.age, (val, old) => {
        //新数据、老数据
    })
    //监听 reactive 数据  数组
    let arr = reactive([1, 2, 3])
    watch(arr, (val, old) => {
        //新数据、老数据
    })
    
    //监听 props 的数据 加入props传了一个 testkey
    watch(() => props.testkey, () => {
    })
    
    //要注意 return
    return {
        count,
        person,
        arr
    }
}
</script>

3. computed 计算属性

从 vue 中引入 computed

<script>
import {ref, computed} from 'vue'
setup(props) {
    let count = ref(0)
    
    //当前组件的计算属性
    let countCom = computed(() => {
        return count.value * 2
    })
    //props传进来的计算属性
    let countCom2 = computed(() => {
        return props.propsKey * 2
    })
}
</script>

4. setup 生命周期钩子使用

  1. setuponMounted 执行 在 整个组件的 mounted 之前
  2. setuponMounted 执行 在 setup 同步组件执行之后
  3. setuponMounted 里面以及 setup 不能使用this
<script>
import {onMounted} from 'vue'
mounted () {
    console.log(4)
},
setup () {
    onMounted(() => {
        console.log(1)
    })
    console.log(2)
    setTimeout(() => {
        console.log(3)
    }, 0)
    //打印顺序
    // 2 1 4 3
    
    
    onMounted(() => {
        console.log(this)
    })
    //打印
    //undefined
}
</script>

5. provide和inject

从 vue 中引入

父组件 provide

<script>
import {provide} from 'vue'

export default {
    setup () {
        provide('person', {
            name: 'JoyNop',
            age: 18
        })
        provide('country', 'China')
    }
}
</script>

子孙组件 inject

<script>
import {inject} from 'vue'

export default {
    setup () {
        //父辈组件 provide 的数据
        let person = inject('person')
        let city = inject('city')
        //父辈组件 没有提供的值,给个默认值 不给默认为为 undefined 而且会有Vue warn提醒
        let noProvide = inject('noProvide', 'noProvide hah')
    }
}
</script>

provide 带有反应性的数据 父组件值修改,子孙组件值会对应的修改

<script>
import {provide} from 'vue'

export default {
    setup () {
        let city = ref('烟台')
        provide('city', city)
    }
}
</script>

provide 一个 函数

<script>
import {provide} from 'vue'

export default {
    setup () {
        let provideFun = () => {
            console.log('provide fun')
        }
        provide('provideFun', provideFun)
    }
}
</script>

4、Reactivity(反应性)

Vue最独特的功能之一是不引人注目的反应系统。

当您将纯JavaScript对象作为data选项传递给应用程序或组件实例时,Vue将遍历其所有属性,并使用带有getter和setter的处理程序将它们转换为Proxies
  • 跟踪更改它的函数:在代理的getter中进行此操作 effect
  • 触发函数,以便它可以更新最终值:在代理中的setter中进行操作 trigger

声明反应性数据

一般使用 refreactivereadonly 来声明数据

  • ref 声明基本类型
  • reactive 声明引用类型
  • readonly 声明只读引用类型
import { ref, reactive, readonly } from 'vue'

export default {
    setup () {
        // 一般用 ref 声明 基本 类型
        // 用 reactive 声明 引用 类型
        let count = ref(0) // 在 script 使用 count.value,在 template 使用 {{count}}
        let state = reactive({
            name: 'Bob'
        }) // 在 script 使用 state.name, 在 template 使用 {{state.name}}
        
        // 用 ref 声明 引用类型
        let obj1 = ref({
            count: 1
        })
        // 用 reactive 声明 基本 类型 警告⚠️ 可以正常使用,但是没有反应性
        let num1 = reactive(10) //value cannot be made reactive: 10
        
        // readonly
        // readonly 和 reactive 一样,但是声明的是只读数据 声明基本类型和 reactive 一样警告提醒
        let person = readonly({age: 18})
        setTimeout(() => {
            // 定时器 修改 警告 ⚠️
            person.age = 10 // Set operation on key "age" failed: target is readonly
        });
        
        // 一定要return 才可以在 template 和 script 其他地方使用
        return {
            count, 
            state,
            obj1
        }
    }
}

count打印出的数据

RefImpl {_rawValue: 0, _shallow: false, __v_isRef: true, _value: 0}
    __v_isRef: true
    _rawValue: 0
    _shallow: false
    _value: 0
    value: 0

state打印出的数据

Proxy {name: "Bob"}
    [[Handler]]: Object
        deleteProperty: ƒ deleteProperty(target, key)
        get: ƒ (target, key, receiver)
        has: ƒ has(target, key)
        ownKeys: ƒ ownKeys(target)
        set: ƒ (target, key, value, receiver)
    [[Target]]: Object
        name: "Bob"
    [[IsRevoked]]: false

obj1打印出的数据

RefImpl {_rawValue: {…}, _shallow: false, __v_isRef: true, _value: Proxy}
    __v_isRef: true
    _rawValue: {count: 1}
    _shallow: false
    _value: Proxy {count: 1}
    value: Proxy
        [[Handler]]: Object
        [[Target]]: Object
            count: 1
        [[IsRevoked]]: false

num1打印的数据

10

template中使用

<!-- 使用ref定义的基本类型直接使用 -->
<!-- 使用reactive定义的引用类型直接.对应的属性 -->
<div class="vue3_pro">
    count: {{count}}
    state: {{state.name}}
</div>

5、filters 过滤器

在 vue2.x 中使用 filters 过滤器,一种很方便也很实用的语法。

但是在vue3.x 中过滤器属性已删除,不再支持

在3.x中,过滤器已删除,不再受支持。相反,我们建议将它们替换为方法调用或计算的属性。

6、directive 自定义指令

在 vue2.x 中,会使用自定义指令来进行一些新的指令的绑定,例如,fouces,drag等

在 vue3.x 中,也有自定义指令的使用,只是方法改掉了

在 2.x 中的钩子函数

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。

3.x 中的钩子函数

2.x3.x
bindbeforeMount
insertedmounted
beforeUpdate : 新增(这在元素本身更新之前被调用)
update已删除
componentUpdatedupdated
beforeUnmount 新增(在卸载元素之前立即调用。)
unbindunmounted

3.x 中的自定义指令

const MyDirective = {
  beforeMount(el, binding, vnode, prevVnode) {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {},
  unmounted() {}
}

把 2.x 中的自定义指令 改成 3.x 的

vue3.x 中使用自定义指令

import { createApp } from 'vue'
import App from './App.vue'
let app = createApp(App)

app.directive('drag', {
  beforeMount (el, binding) {
    console.log(el, binding)
    let oDiv = el;   //当前元素
    oDiv.onmousedown = function (e) {
     //鼠标按下,计算当前元素距离可视区的距离
      let disX = e.clientX - oDiv.offsetLeft;
      let disY = e.clientY - oDiv.offsetTop;
      document.onmousemove = function (e) {
        //通过事件委托,计算移动的距离 
        let l = e.clientX - disX;
        let t = e.clientY - disY;
        //移动当前元素  
        oDiv.style.left = l + 'px';
        oDiv.style.top = t + 'px';
         //将此时的位置传出去
        binding.value({x:e.pageX,y:e.pageY}, el)
      };
      document.onmouseup = function () {
        document.onmousemove = null;
        document.onmouseup = null;
      };
    };
  }
})
app.mount('#app')

template中使用

<template>
    <div v-drag="dragFun" class="div1"></div>
</template>
<script>
export default {
    setup() {
        let dragFun = (val, e) => {
            console.log(val, e)
            // val 是 在binging.value 的值
            // e 是对应的element元素
        }
        return {
            dragFun
        }
    }
}
</script>

7、defineAsyncComponent 异步组件

创建一个仅在必要时加载的异步组件。

在Vue 3中,由于功能组件被定义为纯函数,因此需要通过将异步组件定义包装在新的defineAsyncComponent帮助器中来明确定义异步组件定义

从 vue 中引入

//没有 options 参数时
const testCom = defineAsyncComponent(() => import('./testCom.vue'))

有 options 参数时
const asyncPageWithOptions = defineAsyncComponent({
  loader: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})

所有的参数

const AsyncComp = defineAsyncComponent({
  // 工厂函数
  loader: () => import('./Foo.vue')
  // 加载异步组件时要使用的组件
  loadingComponent: LoadingComponent,
  // 加载失败时使用的组件
  errorComponent: ErrorComponent,
  // 显示加载组件之前的延迟。 默认值:200ms。
  delay: 200,
  // 如果提供并超过了超时,则将显示错误组件。 默认值:无穷大
  timeout: 3000,
  // 一个返回布尔值的函数,该值指示加载程序承诺拒绝时异步组件是否应重试
  retryWhen: error => error.code !== 404,
  // 允许的最大重试次数
  maxRetries: 3,
  // 定义组件是否
  suspensible: false
})

8、子元素 emit 事件

由于 setup 里面不能使用 this,所以不能像 vue2.x 一样使用 this.$emit(‘emitFun’, val),setup 里面有两个参数,第二个参数可以进行结构得到 emit,也可以直接直接使用 context.emit 进行 emit 事件

在setup里面 emit 事件

<button @click="clickCom">点击</button>
setup(props, context) {
    let clickCom = () => {
        context.emit('emit-fun', {emit: true})
    }
    return {
        clickCom
    }
}

9、inline-template 内联模板

在 vue3.x 中对内联模板功能的支持已删除。

在2.x中,Vue inline-template在子组件上提供了属性,以使用其内部内容作为其模板,而不是将其视为分布式内容

3.x不在支持此功能

迁移

  1. 使用
<script type="text/html" id="my-comp-template">
  <div>{{ hello }}</div>
</script>
const MyComp = {
  template: '#my-comp-template'
  // ...
}
  1. 默认插槽
<my-comp v-slot="{ childState }">
  {{ parentMsg }} {{ childState }}
</my-comp>
<!-- 子组件中 -->
<template>
  <slot :childState="childState" />
</template>

10、keyCodes 修饰符

更改点

  1. v-on不再支持使用数字(即keyCodes)作为修饰符
  2. config.keyCodes不再受支持
由于KeyboardEvent.keyCode已弃用,因此Vue 3也不再继续支持此功能。因此,现在建议将kebab-case名称用于要用作修饰符的任何键。

11、render() 渲染

更改点

  1. h 现在已全局导入,而不是传递给渲染函数作为参数
  2. 渲染函数参数已更改为在有状态和功能组件之间更加一致
  3. VNode现在具有扁平的道具结构

h 需要从 vue 中导入

import { h } from 'vue'
在3.x中,由于render函数不再接收任何参数,因此它将主要在setup()函数内部使用。
import { h, reactive } from 'vue'
export default {
  setup(props, { slots, attrs, emit }) {
    const state = reactive({
      count: 0
    })
    function increment() {
      state.count++
    }
    return () =>
      h(
        'div',
        {
          onClick: increment
        },
        state.count
      )
  }
}

在 3.x 中VNode props结构被平铺

{
  class: ['button', 'is-outlined'],
  style: { color: '#34495E' },
  id: 'submit',
  innerHTML: '',
  onClick: submitForm,
  key: 'submit-button'
}

12、slots 插槽

更改点

  1. this.$slots 公开为功能
  2. this.$scopedSlots已删除