学习vue3的过程,总结一下自己的经验

428 阅读8分钟

为什么要学习 vue 3

基本介绍:

为什么要学vue3:

  1. Vue是国内最火的前端框架,vue3.0迟早都得学
  2. Vue是前端招聘的硬需求(vue3.0是亮点)
  3. Vue3性能更高,体积更小
  4. Vue3.0在经过一年的迭代后,越来越好用

目前已支持 vue3 的UI组件库:

  • ant-design-vue (PC组件库)

    antdv.com/docs/vue/in…

    ant-design-vue 是 Ant Design 的 Vue 实现,组件的风格与 Ant Design 保持同步

  • element-plus

    element-plus.gitee.io/#/zh-CN (PC组件库)

    Element Plus,一套为开发者、设计师和产品经理准备的基于 Vue 3.0 的桌面端组件库。

  • vant

    vant-contrib.gitee.io/vant/v3/#/z…

    轻量、可靠的移动端 Vue 组件库。

    Vant 是有赞前端团队开源的移动端组件库,于 2016 年开源,已持续维护 4 年时间。

    目前 Vant 已完成了对 Vue 3.0 的适配工作,并发布了 Vant 3.0 版本

    ...

Vue3 动机 和 新特性

Vue3 中文文档 vue3js.cn/docs/zh/

Vue3 设计理念 vue3js.cn/vue-composi…

动机与目的:

  1. 更好的逻辑复用 与 代码组织 (composition组合式api)

    optionsAPI(旧) => compositionAPI(新), 效果: 代码组织更方便了, 逻辑复用更方便了 非常利于维护!!

  2. 更好的类型推导 (typescript支持)

    vue3 源码用 ts 重写了, vue3 对 ts 的支持更友好了 (ts 可以让代码更加稳定, 类型检测! )

vue3新特性:

  1. 数据响应式原理重新实现 (ES6 proxy 替代了 ES5 的 Object.defineProperty)

    解决了: 例如数组的更新检测等bug, 大大优化了响应式监听的性能

    (原来检测对象属性的变化, 需要一个个对属性递归监听) proxy 可以直接对整个对象劫持

  2. 虚拟DOM - 新算法 (更快 更小)

  3. 提供了composition api, 可以更好的逻辑复用

  4. 模板可以有多个根元素

  5. 源码用 typescript 重写, 有更好的类型推导 (类型检测更为严格, 更稳定)

    ...

小结: vue3 性能更高, 体积更小, 更利于复用, 代码维护更方便

Vite 的使用

Vite 是一个原生 ESM 驱动的 web 开发构建工具, 在开发环境下基于浏览器原生 ES imports 开发

可以用于快速构建 vue3 的工程化项目环境 (脚手架也能构建vue3项目, 就是比较重)

注意: node版本 12以上

Vite 的基本使用

按照顺序执行如下的命令,即可基于 vite 创建 vue 3.x 的工程化项目

npm init vite-app 项目名称
或者
yarn create vite-app 项目名称
​
cd 项目名称
yarn
yarn dev

输入地址, 启动项目:

Vue3.0项目main.js分析

说明: 在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中

其中:

  • App.vue 用来编写待渲染的模板结构
  • index.html 中需要预留一个 el 区域
  • main.js 把 App.vue 渲染到了 index.html 所预留的区域中
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'// 将 App.vue 的内容, 渲染到 index.html中
// 调用createApp()函数, 就是在创建一个单页应用实例, 通过.mount方法, 渲染到el区域中
createApp(App).mount('#app')

问题小结:

  • main.js 做的最核心的一件事情是什么 ?
  • vue3 创建实例 和 vue2 创建实例的区别是什么 ?

Composition API

composition API vs options API

  1. vue2 采用的就是 optionsAPI

    (1) 优点:易于学习和使用, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods中)

    (2) 缺点: 相似的逻辑, 不容易复用, 在大项目中尤为明显

    (3) 虽然 optionsAPI 可以通过mixins 提取相同的逻辑, 但是也并不是特别好维护

  2. vue3 新增的就是 compositionAPI

    (1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能 api 相关放到一起

    (2) 即使项目大了, 功能多了, 也能快速定位功能相关的 api

    (3) 大大的提升了 代码可读性可维护性

  3. vue3 推荐使用 composition API, 也保留了options API

    即就算不用composition API, 用 vue2 的写法也完全兼容!!

问题小结:optionsAPI的优缺点是什么? vue3 新增的 compositionAPI 有什么特征? 有什么优势?

体验 composition API

需求: 鼠标移动显示鼠标坐标 x, y

  1. 注册鼠标滑动事件监听
  2. 拿到 e.pageX, e.pageY
  3. 赋值给绑定 x, y

options API 版本

<template>
  <div>当前鼠标位置</div>
  <div>x: {{ x }}</div>
  <div>y: {{ y }}</div>
</template><script>
export default {
  // vue2 中采用的是 options API
  // 常见的配置项: data created methods watch computed components
  data () {
    return {
      x: 0,
      y: 0
    }
  },
  mounted() {
    document.addEventListener('mousemove', this.move)
  },
  methods: {
    move(e) {
      this.x = e.pageX
      this.y = e.pageY
    }
  },
  destroyed() {
    document.removeEventListener('mousemove', this.move)
  }
}
</script>

composition API 版本

<template>
  <div>
    <h1>当前鼠标的位置</h1>
    <div>{{ x }}, {{ y }}</div>
  </div>
</template><script>
import { reactive, onMounted, onUnmounted, toRefs } from 'vue'
export default {
  setup() {
    // 准备数据
    const mouse = reactive({
      x: 0,
      y: 0
    })
    // 声明处理函数
    const move = e => {
      mouse.x = e.pageX
      mouse.y = e.pageY
    }
    onMounted(() => {
      document.addEventListener('mousemove', move)
    })
    onUnmounted(() => {
      document.removeEventListener('mousemove', move)
    })
    return {
      ...toRefs(mouse)
    }
  }
}
</script>

抽离逻辑

// 鼠标移动显示x, y
function mouseHandler () {
  ...
  return mouse
}
​
export default {
  setup() {
    const mouse = mouseHandler()
    return {
      ...toRefs(mouse)
    }
  }
}

问题小结:optionsAPI的优缺点是什么? vue3 新增的 compositionAPI 有什么特征? 有什么优势?

optionsAPI:

  • 优点:易于学习和使用, 每个代码有着明确的位置
  • 缺点: 相似的逻辑, 不容易复用

compositionAPI:

  • 基于 逻辑功能 组织代码
  • 可维护性好!

setup 函数

composition的使用, 需要配置一个setup 函数

  1. setup 函数是一个新的组件选项, 作为组件中 compositionAPI 的起点
  2. 从生命周期角度来看, setup 会在 beforeCreate 钩子函数之前执行
  3. setup 中不能使用 this, this 指向 undefined
  4. 在模版中需要使用的数据和函数,需要在 setup 返回。
<template>
  <div class="container">
    <h1 @click="say()">{{msg}}</h1>
  </div>
</template><script>
export default {
  setup () {
    console.log('setup执行了')
    console.log(this)
    // 定义数据和函数
    const msg = 'hi vue3'
    const say = () => {
      console.log(msg)
    }
​
    return { msg , say}
  },
  beforeCreate() {
    console.log('beforeCreate执行了')
    console.log(this)
  }
}
</script>

reactive 函数

前置说明:

  1. setup 需要有返回值, 只有返回的值才能在模板中使用
  2. 默认普通的数据, 不是响应式的

作用: 传入一个复杂数据类型,将复杂类型数据, 转换成响应式数据 (返回该对象的响应式代理)

<template>
  <div>{{ obj.name }}</div>
  <div>{{ obj.age }}</div>
  <button @click="obj.name = 'ls'">改值</button>
</template><script>
import { reactive } from 'vue'export default {
  setup () {
    // 1. setup 需要返回值, 返回的值才能在模板中使用
    // 2. 默认的普通的值不是响应式的, 需要用 reactive 函数
    const obj = reactive({
      name: 'zs',
      age: 18
    })
​
    return {
      obj
    }
  }
}
</script>

总结: 通常是用来定义响应式 对象数据

问题小结:

  1. 默认 setup 函数中返回的 普通对象 是响应式的么 ?
  2. reactive 函数的作用是什么 ?

ref 函数

reactive 处理的数据, 必须是复杂类型, 如果是简单类型无法处理成响应式, 所以有 ref 函数!

作用: 对传入的数据(一般简单数据类型),包裹一层对象, 转换成响应式。

  1. ref 函数接收一个的值, 返回一个ref 响应式对象, 有唯一的属性 value
  2. 在 setup 函数中, 通过 ref 对象的 value 属性, 可以访问到值
  3. 在模板中, ref 属性会自动解套, 不需要额外的 .value
  4. ref函数也支持传入复杂类型,传入复杂类型,也会做响应式处理
<template>
  <div>{{ money }}</div>
  <button @click="money++">改值</button>
</template><script>
import { reactive, ref } from 'vue'
export default {
  setup() {
    let money = ref(100)
    money.value++
    return {
      money
    }
  }
}
</script>

ref 和 reactive 的最佳使用方式:

  • 明确的对象,明确的属性,用reactive,其他用 ref

问题小结:

  • ref 函数的作用是什么 ?
  • ref 函数包裹简单类型后, 会包裹成对象, 在模板中需要 .value 么? 在 setup 中需要 .value 么?

toRefs 函数

使用场景: 如果对一个响应数据, 进行解构 或者 展开, 会丢失他的响应式特性!

原因: vue3 底层是对 对象 进行监听劫持

作用: 对一个响应式对象的所有内部属性, 都做响应式处理

  1. reactive的响应式功能是赋值给对象的, 如果给对象解构或者展开, 会让数据丢失响应式的能力
  2. 使用 toRefs 可以保证该对象展开的每个属性都是响应式的
<template>
  <div>{{ money }}</div>
  <div>{{ car }}</div>
  <div>{{ name }}</div>
  <button @click="money++">改值</button>
</template><script>
import { reactive, ref, toRefs } from 'vue'
export default {
  setup() {
    // const money = ref(100) // 简单类型, ref
    // const car = reactive({ // 复杂类型, reactive
    //   brand: '赛车',
    //   price: 100
    // })
    // const name = ref('zs') // 简单类型, ref
    // money.value++ // ref需要.value取值
​
    const state = reactive({
      money: 100,
      car: {
        brand: '赛车',
        price: 100
      },
      name: 'zs'
    })
​
    return {
      ...toRefs(state)
    }
  }
}
</script>

问题小结: toRefs 函数的作用是什么 ?

作用: 对一个 响应式对象 的所有内部属性, 都做响应式处理, 保证展开或者解构出的数据也是响应式的

计算属性computed函数

computed函数调用时, 要接收一个处理函数, 处理函数中, 需要返回计算属性的值

<template>
  <div>我今年的年纪 <input type="text" v-model="age"></div>
  <div>我明年的年纪 <input type="text" v-model="nextAge"></div>
</template><script>
import { ref, computed } from 'vue'
export default {
  setup() {
    const age = ref(10)
​
    // computed是一个函数
    // 1. 传入一个函数 getter 返回一个值
    // const nextAge = computed(() => {
    //   return +age.value + 1
    // })
​
    // 2. 传入一个对象, 包括get和set
    const nextAge = computed({
      get() {
        return +age.value + 1
      },
      set(value) {
        age.value = value - 1
      }
    })
    
    return {
      age,
      nextAge
    }
  }
}
</script>

问题小结: computed 函数提供计算属性, 有几种写法?

侦听器watch函数

watch监视, 接收三个参数
1. 参数1: 监视的数据源
2. 参数2: 回调函数
3. 参数3: 额外的配置
<template>
  <div>{{ money }}</div>
  <div>{{ car }}</div>
  <button @click="changeCar">换车</button>
  <button @click="money++">加钱</button>
</template><script>
import { reactive, toRefs, watch } from 'vue'
export default {
  setup() {
    const state = reactive({
      money: 100,
      car: {
        brand: '宝马'
      }
    })
​
    const changeCar = () => {
      state.car.brand = '奔驰'
    }
​
    // watch监视, 接收三个参数
    // 参数1: 监视的数据源
    // 参数2: 回调函数
    // 参数3: 额外的配置
    // 可以直接监听整个reactive对象
    // watch(state,(value, oldValue) => {
    //   console.log('整个state', value)
    // })
​
    watch(() => state.money,(value, oldValue) => {
      console.log('value', value)
    })
    watch(() => state.car,(value, oldValue) => {
      console.log('value', value)
    }, {
      deep: true,
      immediate: true
    })
​
    return {
      ...toRefs(state),
      changeCar
    }
  }
}
</script>

钩子函数的使用

生命周期函数 vue3 中的生命周期函数, 需要在 setup 中调用

可以使用直接导入的 onX 函数注册生命周期钩子:

import { onMounted, onUpdated, onUnmounted } from 'vue'const MyComponent = {
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
}

vue3 - 父子组件通信演示

父组件

<template>
  <div>
    <h3>我是父组件</h3>
    <my-demo @change-money="changeMoneyFn" :money="money"></my-demo>
  </div>
</template><script>
import MyDemo from './components/my-demo.vue'
import { ref } from 'vue'
export default {
  components: {
    MyDemo
  },
  setup () {
    const money = ref(100)
    const changeMoneyFn = (newMoney) => {
      money.value = newMoney
    }
    return {
      money,
      changeMoneyFn
    }
  }
}
</script>

子组件

<template>
  <div>我是my-demo组件 - {{ money }}</div>
  <button @click="changeMoney">改老爹的传值</button>
</template><script>
export default {
  emits: ['change-money'],
  props: {
    money: {
      type: Number,
      default: 1
    }
  },
  setup(props, context) {
    const changeMoney = () => {
      // 子传父
      context.emit('change-money', props.money + 1)
    }
    
    return {
      changeMoney
    }
  }
}
</script>

tips: vue3中 .sync 修饰符已被废弃, 使用 v-model:props 替代

依赖注入 - provide 和 inject

依赖注入, 可以非常方便的实现 跨层级的 组件通信

父组件利用 provide 提供数据

<template>
  <div>
    <h3>我是父组件</h3>
    <button @click="money++">改值</button>
    <my-demo2></my-demo2>
  </div>
</template><script>
import { provide, ref } from 'vue'
import MyDemo2 from './components/my-demo2.vue'
export default {
  setup() {
    const money = ref(100)
    // 组件提供了money属性
    provide('money', money)
    return {
      money
    }
  },
  components: {
    MyDemo2
  }
}
</script>

子组件 (子孙后代, 都可以拿到这个数据)

<template>
  <div>我是子组件 - {{ money }}</div>
</template><script>
import { inject } from 'vue'
export default {
  setup () {
    const money = inject('money')
    return {
      money
    }
  }
}
</script>

如果希望子传父, 可以 provide 传递一个方法

父组件

// 父组件提供修改money的方法
const changeMoney = (newMoney) => {
  money.value = newMoney
}
provide('changeMoney', changeMoney)

子组件

const changeMoney = inject('changeMoney')
changeMoney(200)

模板中 ref 的使用

联想之前的 ref 和 $refs, 获取模板的元素(dom元素,组件)

1 创建 ref => const hRef = ref(null)

2 模板中建立关联 => <h1 ref="hRef">钩子函数-----123</h1>

3 使用 => hRef.value

<template>
  <div>
    <h1 ref="hRef">钩子函数-----123</h1>
    <my-demo ref="myDemoRef"></my-demo>
  </div>
</template><script>
import MyDemo from './components/my-demo.vue'import { onMounted, ref } from 'vue'export default {
  components: {
    MyDemo
  },
  setup () {
    // 创建 ref
    const hRef = ref(null)
    const myDemoRef = ref(null)
​
    onMounted(() => {
      console.log(hRef.value)
      console.log(myDemoRef.value)
    })
    return {
      hRef,
      myDemoRef
    }
  }
}
</script>