为什么要学习 vue 3
基本介绍:
- vue3.0 正式版 于 2020年 9月18日 发布
- 目前最新版本:
vue3.2.1github.com/vuejs/vue-n…
为什么要学vue3:
- Vue是国内最火的前端框架,vue3.0迟早都得学
- Vue是前端招聘的硬需求(vue3.0是亮点)
- Vue3性能更高,体积更小
- Vue3.0在经过一年的迭代后,越来越好用
目前已支持 vue3 的UI组件库:
-
ant-design-vue (PC组件库)
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…
动机与目的:
-
更好的逻辑复用 与 代码组织 (composition组合式api)
optionsAPI(旧) => compositionAPI(新), 效果: 代码组织更方便了, 逻辑复用更方便了 非常利于维护!!
-
更好的类型推导 (typescript支持)
vue3 源码用 ts 重写了, vue3 对 ts 的支持更友好了 (ts 可以让代码更加稳定, 类型检测! )
vue3新特性:
-
数据响应式原理重新实现 (ES6 proxy 替代了 ES5 的 Object.defineProperty)
解决了: 例如数组的更新检测等bug, 大大优化了响应式监听的性能
(原来检测对象属性的变化, 需要一个个对属性递归监听) proxy 可以直接对整个对象劫持
-
虚拟DOM - 新算法 (更快 更小)
-
提供了composition api, 可以更好的逻辑复用 -
模板可以有多个根元素
-
源码用 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
-
vue2 采用的就是
optionsAPI(1) 优点:
易于学习和使用, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods中)(2) 缺点: 相似的逻辑, 不容易复用, 在大项目中尤为明显
(3) 虽然 optionsAPI 可以通过mixins 提取相同的逻辑, 但是也并不是特别好维护
-
vue3 新增的就是
compositionAPI(1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能 api 相关放到一起
(2) 即使项目大了, 功能多了, 也能快速定位功能相关的 api
(3) 大大的提升了
代码可读性和可维护性 -
vue3 推荐使用 composition API, 也保留了options API
即就算不用composition API, 用 vue2 的写法也完全兼容!!
问题小结:optionsAPI的优缺点是什么? vue3 新增的 compositionAPI 有什么特征? 有什么优势?
体验 composition API
需求: 鼠标移动显示鼠标坐标 x, y
- 注册鼠标滑动事件监听
- 拿到 e.pageX, e.pageY
- 赋值给绑定 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 函数
- setup 函数是一个新的组件选项, 作为组件中 compositionAPI 的起点
- 从生命周期角度来看, setup 会在 beforeCreate 钩子函数之前执行
- setup 中不能使用 this, this 指向 undefined
- 在模版中需要使用的数据和函数,需要在
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 函数
前置说明:
- setup 需要有返回值, 只有返回的值才能在模板中使用
- 默认普通的数据, 不是响应式的
作用: 传入一个复杂数据类型,将复杂类型数据, 转换成响应式数据 (返回该对象的响应式代理)
<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>
总结: 通常是用来定义响应式 对象数据
问题小结:
- 默认 setup 函数中返回的 普通对象 是响应式的么 ?
- reactive 函数的作用是什么 ?
ref 函数
reactive 处理的数据, 必须是复杂类型, 如果是简单类型无法处理成响应式, 所以有 ref 函数!
作用: 对传入的数据(一般简单数据类型),包裹一层对象, 转换成响应式。
- ref 函数接收一个的值, 返回一个ref 响应式对象, 有唯一的属性 value
- 在 setup 函数中, 通过 ref 对象的 value 属性, 可以访问到值
- 在模板中, ref 属性会自动解套, 不需要额外的 .value
- 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 底层是对 对象 进行监听劫持
作用: 对一个响应式对象的所有内部属性, 都做响应式处理
- reactive的响应式功能是赋值给对象的, 如果给对象解构或者展开, 会让数据丢失响应式的能力
- 使用 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>
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。