Vue3-ElementPlus

Vue3

Vue3带来了什么
  • 性能的提升
    • 打包减少了14%
    • 初次渲染快55%,更新渲染快133%
    • 内存减少54%
  • 源码的升级
    • 使用Proxy 代替defineProperty 实现响应式
    • 重写虚拟DOM 的实现和Tree-Shaking
    • ...
  • 拥抱TypeScript
    • Vue3 可以更好的支持TypeScript
  • 新的特性
    • Composition API(组合API)
      • setup 配置
      • refreactive
      • watchwatchEffect
    • 新的内置组件
      • Fragment
      • Teleport
      • Suspense
    • 其他改变
      • 新的生命周期钩子
      • data 选项应始终被生命为一个函数
      • 移除keyCode 支持作为v-on 的修饰符
Vue3项目创建
  • 使用vue-cli 创建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ## 查看 @vue/cli 版本,确保@vue/cli版本在4.5.0以上
    vue --version
    ## 安装或升级@vue/cli
    npm install -g @vue/cli
    ## 创建
    vue create vue_demo
    ## 启动
    cd vue_demo
    npm run serve
    • 查看

      版本查看并创建项目 项目运行
      版本查看并创建项目 项目运行
  • 使用vite 创建项目

    • 优势

      • 开发环境中,无需打包操作,可快读的冷启动
      • 轻量快速的热重载HMR
      • 真正的按需编译,不再等待整个应用编译完成
    • 项目创建

      1
      2
      3
      4
      5
      6
      7
      8
      ## 创建工程 npm init vite-app(固定写法) <project-name>: 创建的项目名称
      npm init vite-app <project-name>
      ## 进入工程目录
      cd <project-name>
      ## 安装依赖
      npm install
      ## 运行
      npm run dev
      • 运行测试

        vite 项目创建与启动
        vite项目创建与启动
分析工程结构
  • main.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 引入的不在是 Vue 构造函数了,引入的是一个名为 createApp 的工厂函数
    import { createApp } from 'vue'
    import App from './App.vue'

    // 创建应用实例对象-app(类似于之前 vue2中的vm,appvm“轻”)
    const app = createApp(App)
    // mount(挂载)
    app.mount('#app')

    1
    2
    3
    4
    5
    <template>

    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    </template>
  • 安装vue3 对应的开发者工具

    logo(存在一些问题,vue2依然好用)
    logo

常用 CompositionAPI

拉开序幕的 setup
  1. 理解: Vue3.0 中一个新的配置项, 值为一个函数

  2. setup 是所有Composition API(组合API)

  3. 组件中所用到的: 数据、方法等,均要配置在setup

  4. setup 函数的两种返回值

    • 若返回一个对象,则对象中的属性、方法在模板中均可以直接使用(重点关注)
    • 若返回一个渲染函数: 则可以自定义渲染内容
  5. 注意点

    • 尽量不要与Vue2.x 配置混用

      • Vue2.x 配置(data、methods、computed)中可以访问到setup 中的属性、方法
      • 但在setup 中不能访问到Vue2.x 配置(data、methods、computed....)
      • 如果有重名,setup 优先
    • setup 不能是一个async 函数,因为返回值不再是return 的对象,而是promise,模板中看不到return 对象中的属性

    • 实例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      <template>
      <h2 @click="sayUserInfo">我是 APP 的根组件</h2>
      </template>

      <script>
      export default {
      name: 'App',
      // Vue3 新的配置项: 将 data,methods,watch,computed 等都配置在其中....
      setup() {
      // 数据
      let username = 'coder-itl'
      let age = 18
      // 方法
      function sayUserInfo() {
      console.log(username, age)
      }

      return {
      username,
      age,
      // 返回方法不加 小括号
      sayUserInfo,
      }

      },
      }
      </script>

ref 函数
  • 作用: 定义一个响应式的数据

  • 语法const xxx=ref(initValue)

    • 创建一个包含响应式数据的引用对象(reference对象)
    • JS 中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接<div>{{xxx}}</div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    <template>
    <h2 @click="sayUserInfo">我是 APP 的根组件</h2>
    <p>用户名: {{ username }}</p>
    <p>年龄: {{ age }}</p>
    <ul>
    <li v-for="(v, k) in job">{{ k }}: {{ v }}</li>
    </ul>
    </template>

    <script>
    import { ref } from 'vue'
    setup(){

    // 定义数据为响应式
    let username = ref('coder-itl')
    let age = ref(18)
    let job = ref({
    type: '前端工程师',
    salary: '13k',
    info: { a: 12, b: 22 },
    })

    // 方法
    function sayUserInfo() {
    // 更新数据值
    username.value = 'coder'
    age.value = 19
    console.log('修改之后: ', username, age)
    // 更新对象数据值
    console.log(job)
    job.value.type = 'aa'
    job.value.salary = '20k'
    }

    return {
    username,
    age,
    // 返回方法不加 小括号
    sayUserInfo,
    job,
    }
    }
    </script>
  • 备注

    • 接受的数据可以是: 基本类型、也可以是对象类型
    • 基本类型的数据: 响应式依然是靠Object.defineProperty() getset 完成的
    • 对象类型的数据: 内部求助Vue3.0 中的一个新函数reactive 函数
reactive 函数
  • ref reactive 比较

    ref reactive 比较
    ref与reactive比较
  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用 ref函数)

  • 语法const 代理对象 = refactive(源对象) 接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称 proxy对象)

  • reactive 定义的响应式数据是深层次的

  • 内部基于ES6 proxy 实现,通过代理对象操作源对象内部数据进行操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <template>
    <h1 @click="sayUserInfo">修改对象的值</h1>
    <ul>
    <li v-for="(v, k) in job1">{{ k }}: {{ v }}</li>
    </ul>
    <ul>
    <li v-for="(v, k) in job2">{{ k }}: {{ v }}</li>
    </ul>
    </template>

    <script>
    import { reactive, ref } from 'vue'
    export default {
    name: 'App',
    // Vue3 新的配置项: 将 data,methods,watch,computed 等都配置在其中....
    setup() {
    let job1 = ref({
    type: '前端工程师-job1',
    salary: '13k',
    info: { a: 12, b: 22 },
    })
    let job2 = reactive({
    type: '前端工程师-job2',
    salary: '13k',
    info: { a: 12, b: 22 },
    })
    // 方法
    function sayUserInfo() {
    console.log(job1)
    console.log(job2)
    console.log('修改 job2: ', (job2.type = 'java工程师'))
    }

    return {
    // 返回方法不加 小括号
    sayUserInfo,
    job1,
    job2,
    }
    },
    }
    </script>


Vue3.0中的响应式原理

Vue2.x的响应式原理
  • 实现原理

    • 对象类型: 通过Object.defineProperty() 对属性的读取,修改进行拦截(数据劫持)
    • 数组类型: 通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Object.defineProperty(data,'count',{
    get(){},
    set(){}
    })

    // vue2 对象新增属性动态(两种)写法
    this.$set(Object,key,value)
    Vue.set(Object,key,value)
    // 移除
    this.$delete(Object,key)
    Vue.delete(Object,key)
    • 存在的问题
      • 新增属性、删除属性,界面不会更新
      • 直接通过下标修改数组,界面不会自动更新
Vue3.x的响应式原理
  • 解决Vue2.x 存在的问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <template>
    <h1 @click="sayUserInfo">修改对象的值</h1>
    <ul>
    <li v-for="(v, k) in job1">{{ k }}: {{ v }}</li>
    </ul>
    <button @click="addSex">添加一个属性</button>
    <button @click="deleteType">删除一个属性</button>
    <button @click="updateHobby">修改数组第一个值</button>
    </template>

    <script>
    import { reactive, ref } from 'vue'
    export default {
    name: 'App',
    // Vue3 新的配置项: 将 data,methods,watch,computed 等都配置在其中....
    setup() {
    let job1 = reactive({
    type: '前端工程师-job1',
    salary: '13k',
    info: { a: 12, b: 22 },
    hobby: ['a', 'ab', 'abc'],
    })

    // 方法
    function sayUserInfo() {
    console.log(job1)
    }
    function addSex() {
    job1.sex = '女'
    }
    function deleteType() {
    delete job1.type
    }
    function updateHobby() {
    job1.hobby[0] = 'coder-itl'
    }
    return {
    // 返回方法不加 小括号
    sayUserInfo,
    job1,
    addSex,
    deleteType,
    updateHobby,
    }
    },
    }
    </script>

  • 实现原理

    • 通过Proxy(代理):拦截对象中任意属性的变化,包括: 属性值的读写、属性的添加、属性的删除等

    • 通过Reflect(反射):对被代理对象的属性进行操作

    • 实例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <script>
      new Proxy(data, {
      // 拦截读取属性值
      get(target, prop) {
      return Reflect.get(target, prop)
      },
      // 拦截设置属性值或添加新属性
      set(target, prop, value) {
      return Reflect.set(target, prop, value)
      },
      // 拦截删除属性
      deleteProperty(target, prop) {
      return Reflect.delete(target, prop)
      }
      })
      </script>
ref 与 reactive 的对比
  • 从定义数据角度对比
    • ref 用来定义: 基本数据类型
    • reactive 用来定义: 对象(或数组)类型数据
    • 备注: ref 也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive 转换为代理对象
  • 从原理角度对比
    • ref 通过Object.defineProperty() get set 来实现响应式(数据劫持)
    • reactive 通过使用Proxy 来实现响应式(数据劫持),并通过Reflect 操作源对象内部的数据
  • 从使用角度对比
    • ref 定义的数据:操作数据需要.value 读取数据时模板中直接读取不需要(.value)
    • reactive 定义的数据:操作数据与读取数据,均不需要.value

setup的两个注意点

  • setup 执行的时机

    • beforeCreate 之前执行一次,this undefined
  • setup 的参数

    • props: 值为对象,包含: 组件外部传递过来,且组件内部声明接受了的属性
    • context: 上下文对象
      • attrs: 值为对象,包含: 组件外部传递过来,但没与在props 配置中声明的属性,相当于this.$attrs
      • slots:收到的插槽内容,相当于this.$slots
      • emit:分发自定义事件的函数,相当于this.$emit
  • 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    App.vue:
    <template>
    // 自定义事件
    <demo @hello="showMessage" msg="props-传递数据"></demo>
    </template>

    <script>
    import Demo from '@/components/Demo.vue'
    export default {
    name: 'App',
    components: {
    Demo,
    },
    // Vue3 新的配置项: 将 data,methods,watch,computed 等都配置在其中....
    setup() {
    return {
    showMessage(value) {
    console.log('hello事件已经触发,参数: ', value)
    },
    }
    },
    }
    </script>


    Demo.vue:
    <template>
    <h1>用户名: {{ username }}</h1>

    <button @click="showInfo">显示参数信息</button>
    </template>

    <script>
    import { reactive, ref } from 'vue'
    export default {
    name: 'Demo',
    // 自定义事件
    emits: ['hello'],
    props: ['msg'],
    // Vue3 新的配置项: 将 data,methods,watch,computed 等都配置在其中....
    setup(props, context) {
    return {
    username: 'coder-itl',
    showInfo() {
    console.log(props, context.slots)
    // 触发自定义事件
    context.emit('hello', { data: 666 })
    },
    }
    },
    }
    </script>

计算属性

  • Vue2.x computed 配置功能一致

  • 写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <template>
    <p>
    firstName:
    <input type="text" v-model="person.firstName" />
    </p>
    <p>
    lastName:
    <input type="text" v-model="person.lastName" />
    </p>
    <p>全名: {{ fullName1 }}</p>
    <p>全名: {{ fullName2 }}</p>
    </template>

    <script>
    import { reactive, computed } from 'vue'
    export default {
    name: 'Demo',
    // Vue3 新的配置项: 将 data,methods,watch,computed 等都配置在其中....
    setup() {
    let person = reactive({
    firstName: 'coder',
    lastName: 'itl',
    })
    // 计算属性-简写
    let fullName1 = computed(() => {
    return person.firstName + ' ' + person.lastName
    })
    // 计算属性完整写法
    let fullName2 = computed({
    get() {
    return person.firstName + ' ' + person.lastName
    },
    set(value) {
    const nameArr = value.split(' ')
    person.firstName = nameArr[0]
    person.lastName = nameArr[1]
    },
    })

    return {
    person,
    fullName1,
    fullName2,
    }
    },
    }
    </script>

    <style></style>

watch

  • Vue2.x watch 配置功能一致

  • 两个小坑

    • 监视reactive 定义的响应式数据时:oldValue 无法正确获取,强制开启了深度监视(deep 配置失效)
    • 监视reactive 定义的响应式数据中某个属性时,deep 配置有效
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    <script>
    import { reactive, ref, watch } from 'vue'
    export default {
    name: 'Demo',
    // Vue3 新的配置项: 将 data,methods,watch,computed 等都配置在其中....
    setup() {
    let sum = ref(0)
    let msg = ref('info....')
    let person = reactive({
    type: 'Java工程师',
    salary: 20,
    info: {
    type: 'Java工程师',
    salary: 20,
    },
    })
    // 情况一: 监视 ref 定义的响应式数据
    watch(
    sum,
    (newValue, oldValue) => {
    console.log('sum变化了', newValue, oldValue)
    },
    { immediate: true }
    )
    // 情况二: 监视多个 ref 定义的响应式数据
    watch([sum, msg], (newValue, oldValue) => {
    console.log('sum | msg 变化了', newValue, oldValue)
    })
    /**
    * 情况三: 监视 reactive 定义的响应式数据
    * 若 watch 监视的是 reacctive 定义的响应式数据,则无法正确获得 oldValue
    * 若 watch 监视的是 reacctive 定义的响应式数据,则强制开启了深度监视
    */
    watch(
    person,
    (newValue, oldValue) => {
    console.log('person变化了', newValue, oldValue)
    }, // deep 配置不在奏效
    { immediate: true, deep: false }
    )
    // 情况四: 监视 reactive 所定义的一个响应式数据中的某个属性
    watch(
    () => person.type,
    (newValue, oldValue) => {
    console.log('persontype变化了', newValue, oldValue)
    }
    )
    // 情况五: 监视 reactive 所定义的一个响应式数据中的某些属性
    watch(
    [() => person.type, () => person.salary],
    (newValue, oldValue) => {
    console.log('persontype|salary变化了', newValue, oldValue)
    }
    )
    // 特殊情况
    watch(
    () => person.salary,
    (newValue, oldValue) => {
    console.log('personsalary变化了', newValue, oldValue)
    },
    // 此处由于监视的是 reactive 定义对象中的某些属性,所以 deep 配置有效
    { deep: true }
    )
    return {
    sum,
    msg,
    person,
    }
    },
    }
    </script>

    <style></style>

watchEffect 函数

  • watch 的套路是: 既要指明监视的属性,也要指明监视的回调

  • watchEffect : 不用指明监视那个属性,监视的回调中用到那个属性,那就监视那个属性

  • watchEffect 有点像computed:

    • computed 注重计算出来的值(回调函数的返回值),所以必须要写返回值

    • watchEffect 更注重的是过程(回调函数的函数体),所以不用写返回值

      1
      2
      3
      4
      5
      6
      import {watchEffect} from "vue" 
      watchEffect(() => {
      const x1 = sum.value
      const x2 = person.salary
      console.log("watchEffect配置的回调执行了.......''")
      })

Vue3 声明周期

  • 无较大变化

自定义 hook 函数

  • 什么是hook? 本质是一个函数,setup 函数中使用的CompositionAPI 进行了封装

  • 类似于Vue2.x 中的mixin

  • 自定义hook 的优势:复用代码,setup 中的逻辑更清楚易懂

  • 案例: 获取鼠标点击

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 定义 hook 函数
    import { onBeforeMount, onMounted, reactive } from 'vue'

    export default function () {
    // 实现鼠标打点相关数据
    let point = reactive({
    x: 0,
    y: 0,
    })
    // 实现鼠标打点相关的方法
    function savePoint(event) {
    point.x = event.pageX
    point.y = event.pageX
    }
    // 实现鼠标打点相关的生命周期钩子
    onMounted(() => {
    window.addEventListener('click', savePoint)
    })
    onBeforeMount(() => {
    window.removeEventListener('click', savePoint)
    })
    }

    // 使用 hook
    import usePoint from "../hooks/savePoint"

    setup(){
    let savePoint = usePoint()
    }

toRef

  • 作用: 创建一个ref 对象,value 值指向另一个对象中的某个属性

  • 语法: const name= toRef(person,'name')

  • 应用: 要将响应式对象中的某个属性单独提供给外部使用

  • 扩展: toRefs toRef 功能一致,但可以批量创建多个ref 对象,语法:toRefs(person)

  • 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <template>
    <h1>Test:</h1>
    <p>{{ person.name }}</p>
    <p>{{ person.age }}</p>
    <p>{{ person.job.type }}</p>
    <p>{{ person.job.sa }}</p>
    <hr />
    <p>{{ name }}</p>
    <p>{{ age }}</p>
    <p>{{ type }}</p>
    <p>{{ salary }}</p>
    </template>

    <script>
    import { reactive, toRef } from 'vue'
    export default {
    name: 'Test',
    // Vue3 新的配置项: 将 data,methods,watch,computed 等都配置在其中....
    setup() {
    let person = reactive({
    name: 'coder-itl',
    age: 18,
    job: {
    type: 'student',
    salary: -1000,
    },
    })

    return {
    person,
    name: toRef(person, 'name'),
    age: toRef(person, 'age'),
    type: toRef(person.job, 'type'),
    salary: toRef(person.job, 'salary'),
    }
    },
    }
    </script>

    <style></style>

其他 Composition API

shallowReactive 与 shallowRef
  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)
  • shallowRef: 只处理基本数据类型的响应式,不进行对象的响应式处理
  • 什么时候使用
    • 如果有一个对象数据,结构层级比较深,但变化时指示外层属性变化=>shallowReactive
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换=>shallowRef
readonly 与 shallowReadonly
  • readonly:让一个响应式数据变为只读的(深只读)
  • shallowReadonly: 让一个响应式数据变为只读的(浅只读)
  • 应对场景: 不希望数据被修改时
toRaw 与 markRaw
  • toRaw
    • 作用: 将一个由reactive 生成的响应式对象转为普通对象
    • 使用场景: 用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
  • markRaw
    • 作用: 标记一个对象,使其永远不会再成为响应式对象
    • 应用场景
      • 有些值不应被设置为响应式,例如复杂的第三方类库等
      • 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

Element-Plus

  • 安装

    1
    npm install element-plus --save