2020年9月
发布100+位贡献者,2600+次提交,30+个RFC,600+次PR,99位贡献者
Vue3 支持 Vue2 的大多数特性
更好的支持 Typescript
打包大小减少41%
初次渲染快55%, 更新渲染快133%
内存减少54%
使用 Proxy 代替 defineProperty 实现数据响应式
重写虚拟 DOM 的实现和 Tree-Shaking
Composition (组合) API
setup
配置ref
和 reactive
watch
和 watchEffect
新的生命周期函数
provide
与 inject
新的内置组件
Fragment
– 文档碎片
Teleport
– 瞬移组件的位置
Suspense
– 异步加载组件的 loading 界面
其它 API 更新
全局 API 的修改
将原来的全局 API 转移到应用对象
模板语法变化
新的生命周期钩子
函数
移除 keyCode 支持作为 v-on 的修饰符
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级@vue/cli
npm install -g @vue/cli
## 创建项目
vue create my-project
新一代前端构建工具
,由原生 ESM 驱动的 Web 开发构建工具
在开发环境下基于浏览器原生 ES imports 开发
快速的冷启动,不需要等待打包操作
即时的热模块更新,替换性能和模块数量的解耦让更新飞起
真正的按需编译,不再等待整个应用编译完成
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
main.ts
// 程序的主入口文件,ts 文件,是 main.ts
// 引入 createApp 函数,创建对应的应用,产生应用的实例对象
import { createApp } from 'vue'
// 引入App组件(所有组件的父级组件)
import App from './App.vue'
// 创建App应用返回对应的实例对象,调用 mount 方法进行挂载
createApp(App).mount('#app')
App.vue
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
template>
<script lang="ts">
// 这里可以使用 TypeScript 代码
import { Options, Vue } from 'vue-class-component';
import HelloWorld from './components/HelloWorld.vue';
// 利用传入的配置项,暴露出一个定义好的组件
@Options({
components: {
HelloWorld,
},
})
export default class App extends Vue {}
script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
style>
shims-due.d.ts
/* eslint-disable */
declare module '*.vue' {
// defineComponent 函数,目的是定义一个组件,内部可以传入一个配置对象
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
Vue3 中新的 option 配置项,值为一个函数,所有的组合API函数都在此使用, 只在初始化时执行一次
函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
<template>
<div>{{number}}div>
template>
<script lang="ts">
// defineComponent 函数,目的是定义一个组件,内部可以传入一个配置对象
import { defineComponent } from 'vue'
// 暴露出去一个定义好的组件
export default defineComponent({
// 当前组件的名字是 App
name: 'App',
setup() {
const number = 10
return {
number
}
}
})
script>
setup 执行的时机
beforeCreate
之前执行(一次
), 此时组件对象还没有创建this 是 undefined, 不能通过 this 来访问 data / computed / methods / props
不可以
<template>
<h2>Child子级组件h2>
<h3>msg: {{msg}}h3>
template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Child',
props: ['msg'],
// setup 细节问题
// setup 是在 beforeCreate 生命周期回调之前就执行了,而且就执行一次
// 由此可以推断出: setup 在执行的时候,当前的组件还没有创建出来,也就意味着: 组件实例对象 this 根本就不能用
// this 是 undefined, 不能通过 this 来调用 data / computed / methods / props 中相关内容了
// 其实所有的 composition API 相关回调函数中也都不可以
// 数据初始化的生命周期回调
beforeCreate() {
console.log('beforeCreate 执行了')
},
// 界面渲染完毕
// mounted() {
// },
setup() {
console.log('setup 执行了');
return {
// setup 中一般都是返回一个对象,对象中的属性和方法都可以在 html 模版中直接使用
}
}
})
script>
setup 的返回值
返回一个对象
模板中可以直接使用此对象中的所有属性 / 方法
有重名, setup 优先
不要混合使用
setup 不能是一个 async 函数
<template>
<h2>Child子级组件h2>
<h3>msg: {{msg}}h3>
template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Child',
props: ['msg'],
// setup 细节问题
// setup 中的返回值是一个对象,内部的属性和方法是给 html 模版使用的
// setup 中的对象内部的属性和 data 函数中的 return 对象的属性都可以在 html 模版中使用
// setup 中的对象中的属性和 data 函数中的对象中的属性会合并为组件对象的属性
// setup 中的对象中的方法和 methods 对象中的方法会合并为组件对象的方法
// 在 Vue3 中尽量不要混合的使用 data 和 setup 及 methods 和 setup
// 一般不要混合使用: methods 中可以访问 setup 提供的属性和方法, 但在 setup 方法中不能访问 data 和 methods
// setup 不能是一个 async 函数: 因为返回值不再是 return 的对象,而是 promise,模版看不到 return 对象中的属性数据
// 界面渲染完毕
mounted() {
console.log(this)
},
setup() {
console.log('setup 执行了');
const showMsg1 = () => {
console.log('showMsg1方法')
}
return {
// setup 中一般都是返回一个对象,对象中的属性和方法都可以在 html 模版中直接使用
showMsg1
}
},
data () {
return {
count: 10
}
},
methods: {
showMsg2() {
console.log('showMsg方法')
}
}
})
script>
setup 的参数
props
attrs
this.$attrs
slots
this.$slots
emit
this.$emit
定义一个数据的响应式
const xxx = ref(initValue)
创建一个包含响应式数据的引用 (reference) 对象
js 中操作数据: xxx.value
模板中操作数据: 不需要 .value
一般用来定义一个基本类型的响应式数据
<template>
<h3>{{ count }}h3>
<button @click="updateCount">更新数据button>
template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'App',
// 需求:页面打开后可以直接看到一个数据,点击按钮后,该数据可以发生变化
// vue2 的方式实现
// data() {
// return {
// count: 0, // 属性
// }
// },
// methods: {
// updateCount() { // 方法
// this.count++
// }
// }
// vue3 的方式实现
// setup 是组合API的入口函数
setup() {
// 变量
// let count = 0 // 此时的数据并不是响应式的数据(响应式数据: 数据变化,页面跟着渲染变化)
// ref 是一个函数,作用: 定义一个响应式的数据,返回的是一个 Ref 对象,对象中有一个 value 属性,
// 如果需要对数据进行操作,需要使用该 Ref 对象调用 value 属性的方式进行数据的操作
// html 模版中是不需要使用 .value 属性的写法
// 一般用来定义一个基本类型的响应式数据
// count 是 Ref 类型
const count = ref(0)
// 方法
function updateCount() {
// 报错的原因: count 是一个 Ref 对象,对象是不能进行 ++ 的操作
// count++
count.value++
}
// 返回的是一个对象
return {
// 属性
count,
// 方法
updateCount
}
}
})
script>
定义多个数据的响应式
const proxy = reactive(obj)
接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“深层的”
会影响对象内部所有嵌套的属性
ES6 的 Proxy
实现
通过代理对象操作源对象内部数据都是响应式的
<template>
<h2>name: {{user.name}}h2>
<h2>age: {{user.age}}h2>
<h2>{{user.gender}}h2>
<h2>son: {{user.son}}h2>
<button @click="updateUser">更新数据button>
template>
<script lang="ts">
import { defineComponent, reactive } from 'vue'
export default defineComponent({
name: 'App',
// 需求:显示用户的相关信息,点击按钮,可以更新用户的相关信息数据
/**
* 作用: 定义多个数据的响应式
* const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
* 响应式转换是“深层的”: 会影响对象内部所有嵌套的属性
* 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
*/
setup() {
// const obj: any = {
const obj = {
name: '张三',
age: 28,
sex: 'man',
son: {
name: '李四',
age: 10
}
}
// 把数据变成响应式的数据
// 返回的是一个 proxy 的代理对象,被代理的目标对象就是 obj 对象
// user 现在是代理对象,obj 是目标对象
// user 对象的类型是 Proxy
const user = reactive<any>(obj)
// 方法
const updateUser = () => {
// 直接使用目标对象的方式来更新目标对象中的成员的值,是不可能的,只能使用代理对象的方式来更新数据(响应式数据)
// user.name = '王五',
// user.son.name = '王小五'
// user 代理对象,obj 目标对象
// user 对象或者 obj 对象添加一个新的属性,哪一种方式会影响界面的更新
// obj.gender = '男' // 这种方式,界面没有更新渲染
// user.gender = '男' // 这种方式,界面可以更新渲染,而且这个数据最终也添加到了 obj 对象上了
// user 对象或者 obj 对象中移除一个已经存在的属性,哪一种方式会影响界面的更新
// delete obj.age // 界面没有更新渲染,obj中确实没有了 age 这个属性
// delete user.age // 界面更新渲染了,obj中确实没有了 age 这个属性
// 如果操作代理对象,目标对象中的数据也会随之变化,同时如果想要在操作数据的时候,界面也要跟着重新更新渲染,
// 那么也是操作代理对象
}
return {
user,
updateUser
}
}
})
script>
reactive
和 ref
是Vue3 的 composition API 中2个最重要的响应式API
基本类型数据
, reactive 用来处理对象(递归深度响应式)
ref 对象/数组
, 内部会自动将对象/数组转换为 reactive 的代理对象
给 value 属性添加 getter / setter 来实现对数据的劫持
使用 Proxy 来实现对对象内部所有数据的劫持, 并通过 Reflect 操作对象内部数据
在 js 中要 .value, 在模板中不需要(内部解析模板时会自动添加 .value)
<template>
<h2>Apph2>
<p>m1: {{m1}}p>
<p>m2: {{m2}}p>
<p>m3: {{m3}}p>
<button @click="update">更新button>
template>
<script lang="ts">
import { reactive, ref } from 'vue'
export default {
setup () {
const m1 = ref('abc')
const m2 = reactive({x: 1, y: {z: 'abc'}})
// 使用ref处理对象 ==> 对象会被自动reactive为proxy对象
const m3 = ref({a1: 2, a2: {a3: 'abc'}})
console.log(m1, m2, m3)
console.log(m3.value.a2) // 也是一个proxy对象
function update() {
m1.value += '--'
m2.x += 1
m2.y.z += '++'
m3.value = {a1: 3, a2: {a3: 'abc---'}}
m3.value.a2.a3 += '==' // reactive对对象进行了深度数据劫持
console.log(m3.value.a2)
}
return {
m1,
m2,
m3,
update
}
}
}
script>
Vue2 的响应式
核心
通过 defineProperty 对对象的已有属性值的读取和修改进行劫持(监视/拦截)
通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
问题
Vue3 的响应式
通过 Proxy(代理)
通过 Reflect(反射)
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.deleteProperty(target, prop)
}
})
proxy.name = 'tom'
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Proxy 与 Reflecttitle>
head>
<body>
<script>
// 目标对象
const user = {
name: "John",
age: 12,
son: {
name: 'mark'
}
};
/*
proxyUser是代理对象, user是被代理对象
后面所有的操作都是通过代理对象来操作被代理对象内部属性
*/
// 把目标对象变为代理对象
// 参数1: user --> target 目标对象
// 参数2: handler --> 处理器对象,用来监视数据,及数据的操作
const proxyUser = new Proxy(user, {
// 获取目标对象的某个属性值
get(target, prop) {
console.log('劫持get()', prop)
return Reflect.get(target, prop)
},
// 修改目标对象的属性值/为目标对象添加新的属性
set(target, prop, val) {
console.log('劫持set()', prop, val)
return Reflect.set(target, prop, val); // (2)
},
// 删除目标对象上的某个属性
deleteProperty (target, prop) {
console.log('劫持delete属性', prop)
return Reflect.deleteProperty(target, prop)
}
});
// 通过代理对象获取目标对象中的某个属性值
// 读取属性值
console.log(proxyUser===user)
console.log(proxyUser.name, proxyUser.age)
// 通过代理对象更新目标对象上的某个属性值
proxyUser.name = 'bob'
proxyUser.age = 13
console.log(user)
// 通过代理对象向目标对象中添加一个新的属性
proxyUser.sex = '男'
console.log(user)
// 删除目标对象上的某个属性
delete proxyUser.sex
console.log(user)
// 更新目标对象中的某个属性对象中的属性值
proxyUser.son.name = 'david'
console.log(user)
script>
body>
html>
computed 函数
watch 函数
watchEffect 函数
<template>
<h2>计算属性和监视h2>
<fieldset>
<legend>姓名操作legend>
姓氏:<input type="text" placeholder="请输入姓氏" v-model="user.firstName" /><br/>
名字:<input type="text" placeholder="请输入名字" v-model="user.lastName" /><br/>
fieldset>
<fieldset>
<legend>计算属性和监视的演示legend>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName1" /><br/>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName2" /><br/>
姓名:<input type="text" placeholder="显示姓名" v-model="fullName3" /><br/>
fieldset>
template>
<script lang="ts">
import {computed, defineComponent, reactive, ref, watch, watchEffect} from 'vue'
export default defineComponent({
name: 'App',
setup() {
// 定义一个响应式对象
const user = reactive({
// 姓氏
firstName: '东方',
// 名字
lastName: '不败'
})
// 通过计算属性的方式,实现第一个姓名的显示
// vue3 中的计算属性
// 计算属性的函数中如果只传入一个回调函数,表示的是 get
// 第一个姓名:
// 返回的是一个 Ref 类型的对象
const fullName1 = computed(() => {
return user.firstName + '_' + user.lastName
})
// 第二个姓名:
const fullName2 = computed({
get() {
return user.firstName + '_' + user.lastName
},
set(val: string) {
// console.log('=======', val)
const names = val.split('_')
user.firstName = names[0]
user.lastName = names[1]
}
})
// 第三个姓名:
const fullName3 = ref('')
// 监视 --- 监视指定的数据
watch(user, ({firstName, lastName}) => {
fullName3.value = firstName + '_' + lastName
},{immediate: true, deep: true})
// immediate 默认会执行一次 watch,deep 深度监视
// 监视,不需要配置 immediate, 本身默认就会进行监视,(默认执行一次)
// watchEffect(() => {
// fullName3.value = user.firstName + '_' + user.lastName
// })
// 监视fullName3的数据,改变firstName和lastName
watchEffect(() => {
const names = fullName3.value.split('_')
user.firstName = names[0]
user.lastName = names[1]
})
// watch -- 可以监视多个数据的
// watch([user.firstName, user.lastName,fullName3],() => {
// // 这里的代码就没有执行,fullName3 是响应式的数据,但是 user.firstName, user.lastName 不是响应式的数据
// console.log('===');
// })
// 当我们使用 watch 监视非响应式的数据的时候,代码需要改一下
watch([() => user.firstName,() => user.lastName, fullName3], () => {
// 这里代码就没有执行,fullName3 是响应式的数据,但是,user.firstName, user.lastName 不是响应式的数据
console.log('====');
})
return {
user,
fullName1,
fullName2,
fullName3
}
}
})
script>
<template>
<h2>Apph2>
fistName: <input v-model="user.firstName"/><br>
lastName: <input v-model="user.lastName"/><br>
fullName1: <input v-model="fullName1"/><br>
fullName2: <input v-model="fullName2"><br>
fullName3: <input v-model="fullName3"><br>
template>
<script lang="ts">
/*
计算属性与监视
1. computed函数:
与computed配置功能一致
只有getter
有getter和setter
2. watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
3. watchEffect函数
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
*/
import {
reactive,
ref,
computed,
watch,
watchEffect
} from 'vue'
export default {
setup () {
const user = reactive({
firstName: 'A',
lastName: 'B'
})
// 只有getter的计算属性
const fullName1 = computed(() => {
console.log('fullName1')
return user.firstName + '-' + user.lastName
})
// 有getter与setter的计算属性
const fullName2 = computed({
get () {
console.log('fullName2 get')
return user.firstName + '-' + user.lastName
},
set (value: string) {
console.log('fullName2 set')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
}
})
const fullName3 = ref('')
/*
watchEffect: 监视所有回调中使用的数据
*/
/*
watchEffect(() => {
console.log('watchEffect')
fullName3.value = user.firstName + '-' + user.lastName
})
// 监视fullName3的数据,改变firstName和lastName
// watchEffect(() => {
// const names = fullName3.value.split('_')
// user.firstName = names[0]
// user.lastName = names[1]
// })
*/
/*
使用watch的2个特性:
深度监视
初始化立即执行
*/
watch(user, () => {
fullName3.value = user.firstName + '-' + user.lastName
}, {
immediate: true, // 是否初始化立即执行一次, 默认是false
deep: true, // 是否是深度监视, 默认是false
})
/*
watch一个数据
默认在数据发生改变时执行回调
*/
watch(fullName3, (value) => {
console.log('watch')
const names = value.split('-')
user.firstName = names[0]
user.lastName = names[1]
})
/*
watch多个数据:
使用数组来指定
如果是ref对象, 直接指定
如果是reactive对象中的属性, 必须通过函数来指定
*/
watch([() => user.firstName, () => user.lastName, fullName3], (values) => {
console.log('监视多个数据', values)
})
return {
user,
fullName1,
fullName2,
fullName3
}
}
}
script>
vue2.x 的生命周期
vue3 的生命周期
与 2.x 版本生命周期相对应的组合式 API
beforeCreate
-> 使用 setup()
created
-> 使用 setup()
beforeMount
-> onBeforeMount
mounted
-> onMounted
beforeUpdate
-> onBeforeUpdate
updated
-> onUpdated
beforeDestroy
-> onBeforeUnmount
destroyed
-> onUnmounted
errorCaptured
-> onErrorCaptured
新增的钩子函数
onRenderTracked
onRenderTriggered
<template>
<div class="about">
<h2>msg: {{msg}}h2>
<hr>
<button @click="update">更新button>
div>
template>
<script lang="ts">
import {
ref,
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount
} from "vue"
export default {
beforeCreate () {
console.log('beforeCreate()')
},
created () {
console.log('created')
},
beforeMount () {
console.log('beforeMount')
},
mounted () {
console.log('mounted')
},
beforeUpdate () {
console.log('beforeUpdate')
},
updated () {
console.log('updated')
},
beforeUnmount () {
console.log('beforeUnmount')
},
unmounted () {
console.log('unmounted')
},
setup() {
const msg = ref('abc')
const update = () => {
msg.value += '--'
}
onBeforeMount(() => {
console.log('--onBeforeMount')
})
onMounted(() => {
console.log('--onMounted')
})
onBeforeUpdate(() => {
console.log('--onBeforeUpdate')
})
onUpdated(() => {
console.log('--onUpdated')
})
onBeforeUnmount(() => {
console.log('--onBeforeUnmount')
})
onUnmounted(() => {
console.log('--onUnmounted')
})
return {
msg,
update
}
}
}
script>
<template>
<h2>Apph2>
<button @click="isShow=!isShow">切换button>
<hr>
<Child v-if="isShow"/>
template>
<script lang="ts">
import Child from './Child.vue'
export default {
data () {
return {
isShow: true
}
},
components: {
Child
}
}
script>
组合 API 封装的可复用的功能函数
vue2 中的 mixin 技术
很清楚复用功能代码的来源, 更清楚易懂
hooks / useMousePosition.ts
import { ref, onMounted, onUnmounted } from 'vue'
/*
收集用户鼠标点击的页面坐标
*/
export default function useMousePosition () {
// 初始化坐标数据
const x = ref(-1)
const y = ref(-1)
// 用于收集点击事件坐标的函数
const updatePosition = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
// 挂载后绑定点击监听
onMounted(() => {
document.addEventListener('click', updatePosition)
})
// 卸载前解绑点击监听
onUnmounted(() => {
document.removeEventListener('click', updatePosition)
})
return {x, y}
}
<template>
<div>
<h2>x: {{x}}, y: {{y}}h2>
div>
template>
<script>
import { ref } from "vue"
/*
在组件中引入并使用自定义hook
自定义hook的作用类似于vue2中的mixin技术
自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
*/
import useMousePosition from './hooks/useMousePosition'
export default {
setup() {
const {x, y} = useMousePosition()
return {
x,
y,
}
}
}
script>
利用TS泛型强化类型检查
hooks / useRequest.ts
import { ref } from 'vue'
import axios from 'axios'
/*
使用axios发送异步ajax请求
*/
export default function useUrlLoader<T>(url: string) {
// 加载的状态
const loading = ref(true)
// 请求成功的数据
const result = ref<T | null>(null)
// 错误信息
const errorMsg = ref(null)
axios.get(url)
.then(response => {
// 改变加载状态
loading.value = false
result.value = response.data
})
.catch(e => {
// 改变加载状态
loading.value = false
errorMsg.value = e.message || '未知错误'
})
return {
loading,
result,
errorMsg,
}
}
<template>
<div class="about">
<h2 v-if="loading">LOADING...h2>
<h2 v-else-if="errorMsg">{{errorMsg}}h2>
<ul v-for="p in result" :key="p.id">
<li>id: {{p.id}}li>
<li>title: {{p.title}}li>
<li>price: {{p.price}}li>
ul>
div>
template>
<script lang="ts">
import {
watch
} from "vue"
import useRequest from './hooks/useRequest'
// 定义接口,约束对象的类型
// 地址数据接口
interface AddressResult {
id: number;
name: string;
distance: string;
}
// 产品数据接口
interface ProductResult {
id: string;
title: string;
price: number;
}
export default {
setup() {
// const {loading, result, errorMsg} = useRequest('/data/address.json')
const {loading, result, errorMsg} = useRequest<ProductResult[]>('/data/products.json')
watch(result, () => {
if (result.value) {
console.log(result.value.length) // 有提示
}
})
return {
loading,
result,
errorMsg
}
}
}
script>
响应式对象
转换成普通对象
,该普通对象的每个 property 都是一个 ref
当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
<template>
<h2>Apph2>
<h3>foo: {{foo}}h3>
<h3>bar: {{bar}}h3>
<h3>foo2: {{foo2}}h3>
<h3>bar2: {{bar2}}h3>
template>
<script lang="ts">
import { reactive, toRefs } from 'vue'
/*
toRefs:
将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象
应用: 当从合成函数返回响应式对象时,toRefs 非常有用,
这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
*/
export default {
setup () {
const state = reactive({
foo: 'a',
bar: 'b',
})
// toRefs 可以把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
const stateAsRefs = toRefs(state)
// 定时器,更新数据(如果数据变化了,界面也会随之变化,肯定是响应式的数据)
setTimeout(() => {
state.foo += '++'
state.bar += '++'
}, 2000);
const {foo2, bar2} = useReatureX()
return {
// ...state,
...stateAsRefs,
foo2,
bar2
}
},
}
function useReatureX() {
const state = reactive({
foo2: 'a',
bar2: 'b',
})
setTimeout(() => {
state.foo2 += '++'
state.bar2 += '++'
}, 2000);
return toRefs(state)
}
script>
<template>
<h2>Apph2>
<input type="text">---
<input type="text" ref="inputRef">
template>
<script lang="ts">
import { onMounted, ref } from 'vue'
/*
ref获取元素: 利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
*/
export default {
setup() {
const inputRef = ref<HTMLElement|null>(null)
onMounted(() => {
inputRef.value && inputRef.value.focus()
})
return {
inputRef
}
},
}
script>
shallowReactive
: 只处理了对象内最外层属性的响应式(也就是浅响应式)shallowRef
: 只处理了value的响应式, 不进行对象的reactive处理<template>
<h2>Apph2>
<h3>m1: {{m1}}h3>
<h3>m2: {{m2}}h3>
<h3>m3: {{m3}}h3>
<h3>m4: {{m4}}h3>
<button @click="update">更新button>
template>
<script lang="ts">
import { reactive, ref, shallowReactive, shallowRef } from 'vue'
/*
shallowReactive 与 shallowRef
shallowReactive: 只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
总结:
reactive与ref实现的是深度响应式, 而shallowReactive与shallowRef是浅响应式
什么时候用浅响应式呢?
一般情况下使用ref和reactive即可,
如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
*/
export default {
setup () {
const m1 = reactive({a: 1, b: {c: 2}})
const m2 = shallowReactive({a: 1, b: {c: 2}})
const m3 = ref({a: 1, b: {c: 2}})
const m4 = shallowRef({a: 1, b: {c: 2}})
const update = () => {
// m1.b.c += 1
// m2.b.c += 1
// m3.value.a += 1
m4.value.a += 1
}
return {
m1,
m2,
m3,
m4,
update,
}
}
}
script>
<template>
<h2>Apph2>
<h3>{{state}}h3>
<button @click="update">更新button>
template>
<script lang="ts">
import { reactive, readonly, shallowReadonly } from 'vue'
/*
readonly: 深度只读数据
获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
只读代理是深层的:访问的任何嵌套 property 也是只读的。
shallowReadonly: 浅只读数据
创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
应用场景:
在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
*/
export default {
setup () {
const state = reactive({
a: 1,
b: {
c: 2
}
})
// const rState1 = readonly(state)
const rState2 = shallowReadonly(state)
const update = () => {
// rState1.a++ // error
// rState1.b.c++ // error
// rState2.a++ // error
rState2.b.c++
}
return {
state,
update
}
}
}
script>
toRaw
<template>
<h2>{{state}}h2>
<button @click="testToRaw">测试toRawbutton>
<button @click="testMarkRaw">测试markRawbutton>
template>
<script lang="ts">
/*
toRaw: 得到reactive代理对象的目标数据对象
*/
import {
markRaw,
reactive, toRaw,
} from 'vue'
export default {
setup () {
const state = reactive<any>({
name: 'tom',
age: 25,
})
const testToRaw = () => {
const user = toRaw(state)
user.age++ // 界面不会更新
}
const testMarkRaw = () => {
const likes = ['a', 'b']
// state.likes = likes
state.likes = markRaw(likes) // likes数组就不再是响应式的了
setTimeout(() => {
state.likes[0] += '--'
}, 1000)
}
return {
state,
testToRaw,
testMarkRaw,
}
}
}
script>
<template>
<h2>Apph2>
<p>{{state}}p>
<p>{{foo}}p>
<p>{{foo2}}p>
<button @click="update">更新button>
<Child :foo="foo"/>
template>
<script lang="ts">
/*
toRef:
为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
应用: 当要将某个 prop 的 ref 传递给复合函数时,toRef 很有用
*/
import {
reactive,
toRef,
ref,
} from 'vue'
import Child from './Child.vue'
export default {
setup () {
const state = reactive({
foo: 1,
bar: 2
})
const foo = toRef(state, 'foo')
const foo2 = ref(state.foo)
const update = () => {
state.foo++
// foo.value++
// foo2.value++ // foo和state中的数据不会更新
}
return {
state,
foo,
foo2,
update,
}
},
components: {
Child
}
}
script>
<template>
<h2>Childh2>
<h3>{{foo}}h3>
<h3>{{length}}h3>
template>
<script lang="ts">
import { computed, defineComponent, Ref, toRef } from 'vue'
const component = defineComponent({
props: {
foo: {
type: Number,
require: true
}
},
setup (props, context) {
const length = useFeatureX(toRef(props, 'foo'))
return {
length
}
}
})
function useFeatureX(foo: Ref) {
const lenth = computed(() => foo.value.length)
return lenth
}
export default component
script>
<template>
<h2>Apph2>
<input v-model="keyword" placeholder="搜索关键字"/>
<p>{{keyword}}p>
template>
<script lang="ts">
/*
customRef:
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
需求:
使用 customRef 实现 debounce 的示例
*/
import {
ref,
customRef
} from 'vue'
export default {
setup () {
const keyword = useDebouncedRef('', 500)
console.log(keyword)
return {
keyword
}
},
}
/*
实现函数防抖的自定义ref
*/
function useDebouncedRef<T>(value: T, delay = 200) {
let timeout: number
return customRef((track, trigger) => {
return {
get() {
// 告诉Vue追踪数据
track()
return value
},
set(newValue: T) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
// 告诉Vue去触发界面更新
trigger()
}, delay)
}
}
})
}
script>
<template>
<h1>父组件h1>
<p>当前颜色: {{color}}p>
<button @click="color='red'">红button>
<button @click="color='yellow'">黄button>
<button @click="color='blue'">蓝button>
<hr>
<Son />
template>
<script lang="ts">
import { provide, ref } from 'vue'
/*
- provide` 和 `inject` 提供依赖注入,功能类似 2.x 的 `provide/inject
- 实现跨层级组件(祖孙)间通信
*/
import Son from './Son.vue'
export default {
name: 'ProvideInject',
components: {
Son
},
setup() {
const color = ref('red')
provide('color', color)
return {
color
}
}
}
script>
<template>
<div>
<h2>子组件h2>
<hr>
<GrandSon />
div>
template>
<script lang="ts">
import GrandSon from './GrandSon.vue'
export default {
components: {
GrandSon
},
}
script>
<template>
<h3 :style="{color}">孙子组件: {{color}}h3>
template>
<script lang="ts">
import { inject } from 'vue'
export default {
setup() {
const color = inject('color')
return {
color
}
}
}
script>
const reactiveHandler = {
get (target, key) {
if (key==='_is_reactive') return true
return Reflect.get(target, key)
},
set (target, key, value) {
const result = Reflect.set(target, key, value)
console.log('数据已更新, 去更新界面')
return result
},
deleteProperty (target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('数据已删除, 去更新界面')
return result
},
}
/*
自定义shallowReactive
*/
function shallowReactive(obj) {
return new Proxy(obj, reactiveHandler)
}
/*
自定义reactive
*/
function reactive (target) {
if (target && typeof target==='object') {
if (target instanceof Array) { // 数组
target.forEach((item, index) => {
target[index] = reactive(item)
})
} else { // 对象
Object.keys(target).forEach(key => {
target[key] = reactive(target[key])
})
}
const proxy = new Proxy(target, reactiveHandler)
return proxy
}
return target
}
/* 测试自定义shallowReactive */
const proxy = shallowReactive({
a: {
b: 3
}
})
proxy.a = {b: 4} // 劫持到了
proxy.a.b = 5 // 没有劫持到
/* 测试自定义reactive */
const obj = {
a: 'abc',
b: [{x: 1}],
c: {x: [11]},
}
const proxy = reactive(obj)
console.log(proxy)
proxy.b[0].x += 1
proxy.c.x[0] += 1
/*
自定义shallowRef
*/
function shallowRef(target) {
const result = {
_value: target, // 用来保存数据的内部属性
_is_ref: true, // 用来标识是ref对象
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
/*
自定义ref
*/
function ref(target) {
if (target && typeof target==='object') {
target = reactive(target)
}
const result = {
_value: target, // 用来保存数据的内部属性
_is_ref: true, // 用来标识是ref对象
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
/* 测试自定义shallowRef */
const ref3 = shallowRef({
a: 'abc',
})
ref3.value = 'xxx'
ref3.value.a = 'yyy'
/* 测试自定义ref */
const ref1 = ref(0)
const ref2 = ref({
a: 'abc',
b: [{x: 1}],
c: {x: [11]},
})
ref1.value++
ref2.value.b[0].x++
console.log(ref1, ref2)
const readonlyHandler = {
get (target, key) {
if (key==='_is_readonly') return true
return Reflect.get(target, key)
},
set () {
console.warn('只读的, 不能修改')
return true
},
deleteProperty () {
console.warn('只读的, 不能删除')
return true
},
}
/*
自定义shallowReadonly
*/
function shallowReadonly(obj) {
return new Proxy(obj, readonlyHandler)
}
/*
自定义readonly
*/
function readonly(target) {
if (target && typeof target==='object') {
if (target instanceof Array) { // 数组
target.forEach((item, index) => {
target[index] = readonly(item)
})
} else { // 对象
Object.keys(target).forEach(key => {
target[key] = readonly(target[key])
})
}
const proxy = new Proxy(target, readonlyHandler)
return proxy
}
return target
}
/* 测试自定义readonly */
/* 测试自定义shallowReadonly */
const objReadOnly = readonly({
a: {
b: 1
}
})
const objReadOnly2 = shallowReadonly({
a: {
b: 1
}
})
objReadOnly.a = 1
objReadOnly.a.b = 2
objReadOnly2.a = 1
objReadOnly2.a.b = 2
/*
判断是否是ref对象
*/
function isRef(obj) {
return obj && obj._is_ref
}
/*
判断是否是reactive对象
*/
function isReactive(obj) {
return obj && obj._is_reactive
}
/*
判断是否是readonly对象
*/
function isReadonly(obj) {
return obj && obj._is_readonly
}
/*
是否是reactive或readonly产生的代理对象
*/
function isProxy (obj) {
return isReactive(obj) || isReadonly(obj)
}
/* 测试判断函数 */
console.log(isReactive(reactive({})))
console.log(isRef(ref({})))
console.log(isReadonly(readonly({})))
console.log(isProxy(reactive({})))
console.log(isProxy(readonly({})))
<template>
<h2>aaaah2>
<h2>aaaah2>
template>
<template>
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
button>
div>
div>
teleport>
template>
<script>
import { ref } from 'vue'
export default {
name: 'modal-button',
setup () {
const modalOpen = ref(false)
return {
modalOpen
}
}
}
script>
<style>
.modal {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
background-color: rgba(0,0,0,.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.modal div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: white;
width: 300px;
height: 300px;
padding: 5px;
}
style>
<template>
<h2>Apph2>
<modal-button>modal-button>
template>
<script lang="ts">
import ModalButton from './ModalButton.vue'
export default {
setup() {
return {
}
},
components: {
ModalButton
}
}
script>
<template>
<Suspense>
<template v-slot:default>
<AsyncComp/>
template>
<template v-slot:fallback>
<h1>LOADING...h1>
template>
Suspense>
template>
<script lang="ts">
/*
异步组件 + Suspense组件
*/
// import AsyncComp from './AsyncComp.vue'
import AsyncAddress from './AsyncAddress.vue'
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => import('./AsyncComp.vue'))
export default {
setup() {
return {
}
},
components: {
AsyncComp,
AsyncAddress
}
}
script>
<template>
<h2>AsyncComp22h2>
<p>{{msg}}p>
template>
<script lang="ts">
export default {
name: 'AsyncComp',
setup () {
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve({
// msg: 'abc'
// })
// }, 2000)
// })
return {
msg: 'abc'
}
}
}
script>
<template>
<h2>{{data}}h2>
template>
<script lang="ts">
import axios from 'axios'
export default {
async setup() {
const result = await axios.get('/data/address.json')
return {
data: result.data
}
}
}
script>