初识 Vue3
简单介绍
相关信息
- Vue3支持 Vue2的大多数特性
- 更好的支持 Typescript
性能提升
- 使用 Proxy代替 defineProperty实现数据响应式
- 重写虚拟 DOM的实现和 Tree-Shaking
新增特性
- Composition(组合)API
- setup
- ref和 reactive
- computed和 watch
- 新的生命周期函数
- provide与 inject
- ...
- 新组件
- Fragment - 文档碎片
- Teleport - 瞬移组件的位置
- Suspense - 异步加载组件的 loading界面
- 其它 API更新
- 全局 API的修改
- 将原来的全局 API转移到应用对象
- 模板语法变化
创建 Vue3项目
使用 vue-cli创建
## 安装或者升级
npm install -g @vue/cli
## 保证 vue cli 版本在 4.5.0 以上
vue --version
## 创建项目
vue create my-project(项目名称)
- 选择 Manually select features
- 添加 TypeScript
- 余下全部回车即可...
使用 vite创建
Vite 是一个 web 开发构建工具,由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动。
- 快速的冷启动,不需要等待打包操作
- 即时的热模块更新,替换性能和模块数量的解耦让更新飞起
- 真正的按需编译,不再等待整个应用编译完成,这是一个巨大的提升
通过在终端中运行以下命令,可以使用 Vite 快速构建 Vue 项目
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
Composition API(常用部分)
setup 函数
- 一个组件选项,在创建组件之前执行,
- 一旦 props被解析,并作为组合式 API的入口点
- 只在初始化时执行一次
- 此函数如果返回对象,对象中的属性和方法,模板中可以直接使用
入参:
- props
- { attrs, slots, emit } = context
类型声明
interface Data {
[key: string]: unknown
}
interface SetupContext {
attrs: Data
slots: Slots
emit: (event: string, ...args: unknown[]) => void
}
function setup(props: Data, context: SetupContext): Data
ref
- 作用:接受一个内部值并返回一个响应式且可变的 ref 对象(定义一个数据的响应式)
- 语法:const refData = ref(val)
- js中操作数据:xxx.value
- 模板中操作数据:xxx,不需要 .value
- 总结:一般用来定义一个
基本类型的响应式数据
类型声明:
interface Ref<T> {
value: T
}
function ref<T>(value: T): Ref<T>
示例
<template>
<h2>{{count}}</h2>
<hr>
<button @click="handleUpdate">更新</button>
</template>
<script lang='ts>
/**
* lang='ts':表示这里可以使用 ts代码
* defineComponent函数:目的是定义一个组件,其内部可传入一个配置对象
*/
import { defineComponent, ref } from 'vue';
export default defineComponent({
/* 使用vue3的composition API */
setup () {
// 定义响应式数据 ref对象
const count = ref(1)
console.log(count)
// 更新响应式数据的函数
function handleUpdate () {
// alert('update')
count.value = count.value + 1
}
return {
count,
handleUpdate
}
}
})
</script>
ref 的另一个作用:获取元素
- 利用 ref函数获取组件中的标签元素
<template>
<h2>ref的另一个作用:获取页面中的元素</h2>
<hr />
input:<input type="text" ref='inputRef' />
</template>
<script lang='ts'>
import { defineComponent, onMounted, ref } from 'vue'
export default defineComponent({
name: 'App',
components: {
},
setup () {
// 当页面加载完毕后,页面中的文本框可以自动获取焦点
// 默认为空,页面加载完毕后再获取文本框元素
const inputRef = ref<HTMLElement | null>(null)
console.log('inputRef', inputRef)
// 在页面挂载后 onMounted,再获取指定的元素
onMounted(() => {
inputRef.value && inputRef.value.focus()
})
console.log('inputRef', inputRef)
return {
// 页面元素绑定此 ref
inputRef
}
}
})
</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>
reactive
响应式转换是“深层”的——它影响所有嵌套 property。在基于 ES2015 Proxy 的实现中,返回的 proxy 是不等于原始对象的。建议只使用响应式 proxy,避免依赖原始对象。
- 作用:接收一个普通对象然后返回该普通对象的响应式代理器对象
- 语法:const user = reactive(obj)
<template>
<h2>name: {{state.name}}</h2>
<h2>age: {{state.age}}</h2>
<h2>wife: {{state.friend}}</h2>
<hr>
<button @click="handleUpdate">更新</button>
</template>
<script lang='ts>
/**
* lang='ts':表示这里可以使用 ts代码
* defineComponent函数:目的是定义一个组件,其内部可传入一个配置对象
*/
import { defineComponent, reactive } from 'vue';
export default defineComponent({
setup () {
/*
定义响应式数据对象
*/
const state = reactive({
name: 'tom',
age: 25,
friend: {
name: 'jake',
age: 22
},
})
console.log(state, state.wife)
const handleUpdate = () => {
state.name += '--'
state.age += 1
state.wife.name += '++'
state.wife.age += 2
}
return {
state,
handleUpdate
}
}
}
</script>
setup细节
setup执行时机
- 在 beforeCreate之前执行,此时 当前组件对象还没有创建
- 此时 this是 undefined,不能通过 this来访问 data、computed、methods、props中的数据状态
- 同时,在所有的 composition API相关回调函数中也都不可以使用
setup返回值
- 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
- setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
setup的参数
- setup(props, context) / setup(props, { attrs, slots, emit })
- props: 包含 props配置声明且传入了的所有属性的对象
- attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
- lots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
- emit: 用来分发自定义事件的函数, 相当于 this.$emit
示例
// App.vue
<template>
<h2>App父级组件</h2>
<h4>msg: {{msg}}</h4>
<button @click="handleMsg('---')">update</button>
<hr />
<child :msg='msg' @handleMsg='handleMsg' />
</template>
<script lang='ts'>
import { defineComponent, ref } from 'vue'
import Child from './components/Child.vue'
export default defineComponent({
name: 'App',
components: {
Child
},
setup (props, {attrs, emit, slots}) {
const msg = ref('what are you no sha lei')
function handleMsg (content = '123') {
msg.value += content
}
console.log('props', props) // Proxy
console.log('attrs', attrs) // Proxy
console.log('emit', emit) // fn
console.log('slots', slots) // Proxy
return {
msg,
handleMsg
}
}
})
</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>
// ./components/Child.vue
<template>
<h3>Child子级组件</h3>
<h4>msg: {{msg}}</h4>
<button @click='handleMsgChild'>触发父级事件</button>
</template>
<script lang='ts'>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Child',
props: {
msg: {
default: () => ''
}
},
// 父组件传入的 事件、方法
emits: ['handleMsg'],
beforeCreate () {
console.log('beforeCreate', this)
},
setup (props, { attrs, emit, slots }) {
console.log('setup', this)
// setup () {
console.log('props', props) // Proxy
// console.log('attrs', attrs) // Proxy
// attrs:获取没有在 props配置中声明的属性的对象,相当于 this.$attrs
// console.log('emit', emit) // handleMsg
// 获取分发事件
// console.log('slots', slots) // Proxy
// 子组件触发父组件的方法
const handleMsgChild = () => {
emit('handleMsg', '+++')
}
return {
handleMsgChild
}
}
})
</script>
ref与 reactive 细节
- 是 vue3中 composition API中两个最重要的响应式 API
- ref用来处理基本类型数据
- reactive用来处理对象(递归深度响应式)
- 如果 ref的初始数据是 对象、数组,内部会自动将 数组、对象使用 reactive进行处理
- ref内部:通过各 value属性添加 getter/setter来实现对数据的劫持
- reactive内部:通过使用 Proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据
- ref的数据操作:在 js中要使用 xxx.value,在模板中不需要直接使用 xxx(内部解析模板时会自动添加.value)
<template>
<h2>App</h2>
<p>m1: {{m1}}</p>
<p>m2: {{m2}}</p>
<p>m3: {{m3}}</p>
<button @click="handleUpdate">更新</button>
</template>
<script lang="ts">
import {
defineComponent,
reactive,
ref
} from 'vue'
export default defineComponent({
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 handleUpdate () {
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,
handleUpdate
}
}
})
</script>
computed、watch和 watchEffect
computed函数:
- 功能与 vue2.xxx版的 computed一致
- 直传一个参数,表示 getter
- 传入两个参数时,第一个参数表示 getter,第二个参数表示 setter
<template>
<h2>计算属性和监视</h2>
<hr />
<fieldset>
<legend>姓名操作</legend>
姓氏:
<input
type="text"
placeholder="请输入姓氏"
v-model='user.firstName'
/>
<br />
名字:
<input
type="text"
placeholder="请输入名字"
v-model='user.lastName'
/>
</fieldset>
<fieldset>
<legend>计算属性和监视的演示</legend>
姓名:
<input
type="text"
placeholder="显示姓名"
v-model='fullName1'
/>
<br />
姓名:
<input
type="text"
placeholder="显示姓名"
v-model='fullName2'
/>
<br />
</template>
<script lang='ts'>
import { computed, defineComponent, reactive, ref } from 'vue'
export default defineComponent({
name: 'App',
setup () {
const user = reactive({
firstName: '东方',
lastName: '不败'
})
/**
* vue3中的计算属性
* 计算属性的函数中如果只传入一个回调函数,表示的是 get
* 传入两个参数时,第一个参数表示 getter,第二个参数表示 setter
*/
// 第一个姓名:
// 返回的是一个 ref类型的对象
const fullName1 = computed (
() => {
return user.firstName + '_' + user.lastName
}
)
// console.log('fullName1', fullName1) // ComputedRefImpl
// 第二个姓名:
// 当传入一个对象时,参数分别为 get和 set方法
const fullName2 = computed ({
get () {
return user.firstName + '_' + user.lastName
},
set (val: string) {
console.log('val', val)
const nameArr = val.split('_')
user.firstName = nameArr[0]
user.lastName = nameArr[1]
}
})
return {
user,
fullName1,
fullName2
}
}
})
</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>
watch函数:
- 与watch配置功能、用法基本一致
watchEffect函数:
- 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
- 默认初始时就会执行第一次, 从而可以收集需要监视的数据
- 监视数据发生变化时回调
<template>
<h2>计算属性和监视</h2>
<hr />
<fieldset>
<legend>姓名操作</legend>
姓氏:
<input
type="text"
placeholder="请输入姓氏"
v-model='user.firstName'
/>
<br />
名字:
<input
type="text"
placeholder="请输入名字"
v-model='user.lastName'
/>
</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'
/>
</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
}
)
// console.log('fullName1', fullName1) // ComputedRefImpl
// 第二个姓名:
// 当传入一个对象时,参数分别为 get和 set方法
const fullName2 = computed ({
get () {
return user.firstName + '_' + user.lastName
},
set (val: string) {
console.log('val', val)
const nameArr = val.split('_')
user.firstName = nameArr[0]
user.lastName = nameArr[1]
}
})
// 监视指定数据
watch (
user,
({firstName, lastName}) => {
fullName3.value = firstName || '' + '_' + lastName || ''
},
{
immediate: true, // 默认初始化时会执行一次
deep: true // 深度监视
}
)
// 第三个姓名:
const fullName3 = ref('')
// 监视,不需要配置参数 immediate就会在初始化时执行一次
watchEffect (
() => {
fullName3.value = user.firstName + '_' + user.lastName
}
)
// 监视 fullName的数据,改变 firstName和 lastName
watchEffect (
() => {
const nameArr = fullName3.value.split('_')
user.firstName = nameArr[0] || ''
user.lastName = nameArr[1] || ''
}
)
// watch (
// [user.firstName, user.lastName],
// () => {
// // 此处代码没有执行,因为 user.firstName和 user.lastName不是响应式的数据
// console.log('+++')
// }
// )
// 当我们需要 watch监视非响应式的数据时,
// 非响应式数据需要改成回调的方式
watch (
[
() => user.firstName,
() => user.lastName
],
() => {
console.log('+++')
}
)
return {
user,
fullName1,
fullName2,
fullName3
}
}
})
</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>
生命周期
Vue3 与 Vue2.x.. 版本生命周期对应关系
- setup() - beforeCreate()
- setup() - created()
- onBeforeMount() - beforeMount()
- onMounted() - mounted()
- onBeforeUpdate() - beforeUpdate()
- onUpdated() - updated()
- onBeforeUnmount() - beforeDestroy()
- onUnmounted() - destroyed()
- onErrorCaptured() - errorCaptured()
注意:以上 vue3与 vue2对应的生命周期中,vue3的生命周期执行要早于 vue2
示例
<template>
<h3>生命周期</h3>
<div>456</div>
</template>
<script lang='ts'>
import { defineComponent, onBeforeMount, onBeforeUnmount, onMounted, onUnmounted } from 'vue'
export default defineComponent({
name: 'Child',
/**
* vue2.xxx生命周期
*/
beforeCreate () {
console.log('beforeCreate')
},
created () {
console.log('created')
},
mounted () {
console.log('mounted')
},
beforeUnmount () {
console.log('beforeUnmount')
},
// deprecated
// beforeUnmount
beforeDestroy () {
console.log('beforeDestroy')
},
// unMounted
destroyed () {
console.log('destroyed')
}
/**
* vue3.xxx生命周期
* 总结:3.xxx中的生命周期钩子的执行要早于 2.xxx对应的钩子的执行
*/
/**
* hook:在不使用 class的情况下管理里边的状态数据,
* 并且将逻辑思维抽取出来放在一个可复用的功能函数中
*/
setup () {
console.log('setup')
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
})
onUnmounted(() => {
console.log('onUnmounted')
})
}
})
</script>
<style>
</style>
自定义 hook函数
- 使用 vue3的组合 API封装的可复用的功能函数
- 自定义 hook函数的作用类似于 vue2中的 mixin技术
- 自定义 hook函数更加清楚复用功能代码的来源,更清楚易懂
示例
// index.vue
<template>
<h2>自定义hook函数</h2>
<h4>x:{{x}}, y:{{y}}</h4>
<hr />
</template>
<script lang='ts'>
import { defineComponent, ref, watch } from 'vue'
import useMousePosition from './hooks/useMousePosition'
import useUrlLoader from './hooks/useUrlLoader'
// 定义接口,约束对象类型
interface AddressData {
id: number;
address: string;
distance: string;
}
interface ProductsData {
id: string;
title: string;
price: number;
}
export default defineComponent({
name: 'App',
components: {
},
/**
* 用户点击页面后,收集 xy坐标数据,使用hook实现
*/
setup () {
// 将方法逻辑封装到单独的文件中,进行复用
const { x, y } = useMousePosition()
const url = ref('url')
const { data, loading, errorMsg } = useUrlLoader<AddressData>(url.value)
const { data, loading, errorMsg } = useUrlLoader<ProductsData[]>(url.value)
watch(data, () => {
if (data.value) {
console.log(data.value.length)
}
})
return {
x,
y,
data,
loading,
errorMsg
}
}
})
</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>
// useMousePosition.ts
import { onBeforeUnmount, onMounted, ref } from 'vue'
export default function () {
const x = ref(-1)
const y = ref(-1)
const getMousePosition = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
// 页面加载完毕后,再进行点击操作
onMounted(() => {
window.document.addEventListener('click', getMousePosition)
})
onBeforeUnmount(() => {
window.document.removeEventListener('click', getMousePosition)
})
return {
x,
y
}
}
// useUrlLoader.ts
import axios from 'axios'
import { ref } from 'vue'
// T 泛型
export default function <T>(url: string) {
const data = ref<T | null>(null)
const loading = ref(true)
const errorMsg = ref(null)
axios.get(url).then(res => {
loading.value = false
data.value = res.data
})
.catch(e => {
loading.value = false
errorMsg.value = e.message || '未知错误'
})
return {
loading,
data,
errorMsg
}
}
toRefs
- 作用:将一个响应式对象转换成普通对象,并且该普通对象的每个 property都是一个 ref
- 应用:主要用于 解构、合成函数返回响应式对象时,在不丢失响应式的情况下对返回的对象进行分解使用
<template>
<h2>toRefs的使用</h2>
<h4>name:{{name}}</h4>
<h4>age:{{age}}</h4>
<h4>name1:{{name1}}</h4>
<h4>age1:{{age1}}</h4>
<h4>toRefsState.name:{{toRefsState.name.value}}</h4>
<h4>toRefsState.age:{{toRefsState.age.value}}</h4>
<hr />
</template>
<script lang='ts'>
import { defineComponent, reactive, toRefs } from 'vue'
function useFeatureX () {
const state = reactive({
name1: 'Jery',
age1: 12
})
return {
// 对返回的对象使用 toRefs后进行解构处理
...toRefs(state)
}
}
export default defineComponent({
name: 'App',
components: {
},
setup () {
const state = reactive({
name: 'Tom',
age: 45,
fim: {
name: '123',
info: '123'
}
})
// toRefs
const toRefsState = toRefs(state)
// 也可以解构使用
// const { name, age } = toRefs(state)
console.log('toRefsState', toRefsState)
/**
* toRefsState
* name: ObjectRefImpl
*/
// hook与 toRefs的结合
const { name1, age1 } = useFeatureX()
// 定时器更新数据
window.setTimeout(() => {
console.log('setTimeout')
// state.name += '+-+'
toRefsState.name.value += '+'
toRefsState.age.value += 1
// 解构后的使用
name1.value += '-'
age1.value += 1
}, 1000)
return {
// state
// ...state 此方式处理,对象中解构出的属性不是响应式的了;只能使用 toRefs
toRefsState,
...toRefsState,
// 解构后的使用
// name,
// age
name1,
age1
}
}
})
</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>
Composition API(其它)
shallowReactive & shallowRef 的使用
- shallowReactive:只处理了对象内最外层 property的响应式
- shallowRef:只处理了 value的响应式,不进行对象的 reactive处理
<template>
<h2>shallowReactive和shallowRef</h2>
<hr />
<h4>m1:{{m1}}</h4>
<h4>m2:{{m2}}</h4>
<h4>m3:{{m3}}</h4>
<h4>m4:{{m4}}</h4>
<button @click='handleUpdate'>update</button>
</template>
<script lang='ts'>
import { defineComponent, reactive, ref, shallowReactive, shallowRef } from 'vue'
export default defineComponent({
name: 'App',
setup () {
// 深度劫持
const m1 = reactive({
name: 'Tom',
age: 20,
car: {
name: 'BMW',
color: 'red'
}
})
// 浅劫持
const m2 = shallowReactive({
name1: 'Tom',
age1: 20,
car1: {
name1: 'BMW',
color1: 'red'
}
})
// 深度劫持
const m3 = ref({
name: 'Tom',
age: 20,
car: {
name: 'BMW',
color: 'red'
}
})
// 浅劫持
const m4 = shallowRef({
name: 'Tom',
age: 20,
car: {
name: 'BMW',
color: 'red'
}
})
console.log('m1', m1)
console.log('m2', m2)
console.log('m3', m3)
console.log('m4', m4)
const handleUpdate = () => {
// m1.name += '+'
// m1.age += 1
// m1.car.name += '*'
// m1.car.color += '-'
// 这样写时,浅响应会变为深响应
m2.name1 += '+'
m2.age1 += 1
// m2.car1.name1 += '*'
// m2.car1.color1 += '-'
// m3.value.name += '+'
// m3.value.age += 1
// m3.value.car.name += '*'
// m3.value.car.color += '-'
m4.value.name += '+'
m4.value.age += 1
// m4.value.car.name += '*'
// m4.value.car.color += '-'
}
return {
m1,
m2,
m3,
m4,
handleUpdate
}
}
})
</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>
readonly & shallowReadonly
readonly:
- 深度只读数据
- 获取一个对象(响应式或纯对象)或 ref并返回原始数据的只读代理
- 只读代理是深层的,访问的任何嵌套 property也是只读的
shallowReadonly
- 浅层只读数据
- 只处理对象(响应式或单纯对象)或 ref数据的 浅层 property为只读,但不执行嵌套对象的深度只读转换
<template>
<h2>readonly和shallowReadonly</h2>
<hr />
<h4>state:{{state}}</h4>
<!-- <h4>state2:{{state2}}</h4> -->
<h4>state3:{{state3}}</h4>
<button @click='handleUpdate'>update</button>
</template>
<script lang='ts'>
import { defineComponent, reactive, readonly, shallowReadonly } from 'vue'
// import { defineComponent, reactive, shallowReadonly } from 'vue'
interface StateInterface {
name: string;
age: number;
car: {
name: string;
color: string;
};
}
export default defineComponent({
name: 'App',
setup () {
const state = reactive({
name: 'Tom',
age: 22,
car: {
name: 'BMW',
color: 'yellow'
}
})
// 只读的数据 -> 深度只读
// const state2 = readonly<StateInterface>(state)
// 只读的数据 -> 浅只读
const state3 = shallowReadonly(state)
const handleUpdate = () => {
console.log('handleUpdate')
// state2.name += '+' // error readonly
// state2.car.name += '+' // error readonly
// state3.name += '-' // error readonly
state3.car.name += '*'
}
return {
state,
// state2,
state3,
handleUpdate
}
}
})
</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>
toRaw & markRaw
toRaw
- 返回由 reactive或 readonly方法转换成响应式代理的普通对象
- 这是一个还原方法,可用于临时读取,访问不会被代理、跟踪,写入时也不会触发界面更新
markRaw
-
标记一个对象,使其永远不会转换为代理;返回对象本身
-
使用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue组件对象
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能
实例
<template>
<h2>toRaw和markRaw</h2>
<hr />
<h4>state:{{ state }}</h4>
<button @click='handleToRaw'>toRawUpdate</button>
<button @click='handleMarkRaw'>markRawUpdate</button>
</template>
<script lang='ts'>
// import { defineComponent, reactive, readonly, shallowReadonly } from 'vue'
import { defineComponent, markRaw, reactive, toRaw } from 'vue'
interface UserInfo {
name: string;
age: number;
likes?: string[];
}
export default defineComponent({
name: 'App',
setup () {
const state = reactive<UserInfo>({
name: 'Tom',
age: 25
})
const handleToRaw = () => {
// 将代理对象变为普通对象,数据变化,界面不变
const user = toRaw(state)
user.name += '*'
}
const handleMarkRaw = () => {
const likes = ['a', 'b', 'c']
// markRaw标记的对象数据,从此以后不能变为代理对象
state.likes = markRaw(likes) // likes数组不再是响应式了
window.setTimeout(() => {
state.likes[0] += '///'
}, 1000)
}
return {
state,
handleToRaw,
handleMarkRaw
}
}
})
</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>
toRef
- 为源响应式对象上的某个属性创建一个 ref对象,二者内部操作的是同一个数据值,更新时二者是同步的
- 区别 ref:拷贝来了一份新的数据值单独操作,修改时互不影响
- 应用:当要将某个 prop的 ref传递给复合函数、子组件时,toRef很有用
实例
// App.vue
<template>
<h2>toRef</h2>
<hr />
<h4>state:{{ state }}</h4>
<h4>foo:{{ foo }}</h4>
<h4>foo2:{{ foo2 }}</h4>
<hr />
<!--
foo:是值传递
-->
<child :foo='foo' />
<button @click='handleUpdate'>handleUpdate</button>
</template>
<script lang='ts'>
// import { defineComponent, reactive, readonly, shallowReadonly } from 'vue'
import { defineComponent, reactive, ref, toRef } from 'vue'
import Child from './components/Child.vue'
interface UserInfo {
name: string;
age: number;
likes?: string[];
}
export default defineComponent({
name: 'App',
components: {
Child
},
setup () {
const state = reactive({
foo: 1,
bar: 2
})
/**
* 为 响应式对象上的某个属性创建一个 ref对象,
* 二者内部操作的是同一个数据值, 更新时二者是同步的
*/
const foo = toRef(state, 'foo')
/**
* 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
*/
const foo2 = ref(state.foo)
const handleUpdate = () => {
/**
* 以下二者内部操作的是同一个数据值, 更新时二者是同步的
*/
state.foo++
// foo.value++
/**
* 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
*/
// foo2.value++
}
return {
state,
foo,
foo2,
handleUpdate
}
}
})
</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>
// Child.vue
<template>
<h3>Child</h3>
<div>foo: {{foo}}</div>
<div>length: {{length}}</div>
</template>
<script lang='ts'>
import { computed, defineComponent, toRef, Ref } from 'vue'
// import useFeatureX from '../hooks/useFeatureX'
/**
* 也可以使用上边的钩子函数
*/
function useFeatureX (foo: Ref) {
console.log('foo: ', foo)
const len = computed(() => {
return foo.value.toString().length
})
return len
}
export default defineComponent({
name: 'Child',
props: {
/**
* foo:是值传递
*/
foo: {
type: Number,
required: true
}
},
setup (props) {
console.log('props: ', props)
const length = useFeatureX(toRef(props, 'foo'))
return {
length
}
}
})
</script>
<style>
</style>
customRef
创建一个自定义的 ref,并对其依赖项跟踪和更新触发 进行显式控制- 它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并应返回一个带有 get 和 set 的对象
- 需求:使用 customRef实现 debounce的示例
实例
// App.vue
<template>
<h2>customRef</h2>
<hr />
<input
type="text"
v-model='keyword'
placeholder='search...'
/>
<h4>keyword: {{ keyword }}</h4>
</template>
<script lang='ts'>
// import { defineComponent, reactive, readonly, shallowReadonly } from 'vue'
import { defineComponent, ref } from 'vue'
/**
* hooks
*/
import useDebouncedRef from './hooks/useDebouncedRef'
export default defineComponent({
name: 'App',
components: {
},
setup () {
// const keyword = ref('')
const keyword = useDebouncedRef('', 500)
// console.log('keyword: ', keyword)
return {
keyword
}
}
})
</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>
// './hooks/useDebouncedRef.ts'
import { customRef } from 'vue'
export default function<T> (value: T, delay = 200) {
let timeId: number
return customRef((track, trigger) => {
return {
// 返回数据
get() {
// 告诉 Vue追踪数据
track()
return value
},
// 修改数据
set(newValue: T) {
// console.log('newValue')
/**
* 设置定时器
*/
window.clearTimeout(timeId)
timeId = window.setTimeout(() => {
// 新数据传给 value
value = newValue
// 告诉 Vue去触发界面更新
trigger()
}, delay)
}
}
})
}
provide & inject
- provide和inject提供依赖注入,功能类似 2.x 的provide/inject
- 实现跨层级组件(祖孙)间通信
实例
// App.vue
<template>
<h2>provide和 inject</h2>
<hr />
<p>当前颜色:{{ color }}</p>
<button @click="color = 'red'">red</button>
<button @click="color = 'blue'">blue</button>
<button @click="color = 'green'">green</button>
<son></son>
</template>
<script lang='ts'>
import { defineComponent, ref, provide } from 'vue'
import Son from './components/Son.vue'
export default defineComponent({
name: 'App',
components: {
Son
},
setup () {
const color = ref('red')
provide('color', color)
return {
color
}
}
})
</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>
// Son.vue
<template>
<h3>子组件</h3>
<hr />
<grand-son></grand-son>
</template>
<script lang='ts'>
import { defineComponent } from 'vue'
import GrandSon from './GrandSon.vue'
export default defineComponent({
name: 'Son',
components: {
GrandSon
},
setup () {
return {
//
}
}
})
</script>
// GrandSon.vue
<template>
<h3>子孙组件</h3>
<hr />
<p :style="{color: color}">子孙组件:{{ color }}</p>
<p :style="{color}">子孙组件:{{ color }}</p>
</template>
<script lang='ts'>
import { defineComponent, inject } from 'vue'
export default defineComponent({
name: 'GrandSon',
components: {
},
setup() {
const color = inject('color')
return {
color
}
}
})
</script>
响应式数据的判断
- isRef:检查一个值是否为一个 ref对象
- isReactive:检查一个对象是否是由 reactive创建的响应式代理
- isReadonly:检查一个对象是否是由 readonly创建的只读代理
- isProxy:检查一个对象是否是由 reactive或 readonly方法创建的代理
实例
<template>
<h2>is判断</h2>
<hr />
</template>
<script lang='ts'>
import { defineComponent, ref, reactive, isReactive, isRef, isReadonly, readonly, isProxy } from 'vue'
export default defineComponent({
name: 'App',
components: {
},
setup () {
// isRef:检查一个值是否为一个 ref对象
// isReactive:检查一个对象是否是由 reactive创建的响应式代理
// isReadonly:检查一个对象是否是由 readonly创建的只读代理
// isProxy:检查一个对象是否是由 reactive或者 readonly方法创建的代理
const isRef1 = ref(0)
console.log('isref: ', isRef(isRef1))
const isReactive1 = reactive({})
console.log('isReactive: ', isReactive(isReactive1))
console.log('isReadonly: ', isReadonly(readonly({})))
console.log('isProxy: ', isProxy(isReactive1))
console.log('isProxy: ', isProxy(readonly({})))
return {
//
}
}
})
</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>
其它新组合和 API
新组件
Fragment(片断)
- 在 Vue2中:组件将必须有一个根标签
- 在 Vue3中:组件可以没有根标签,内部会将多个标签包含在一个 Fragment虚拟元素中
- 好处:减少标签层级,节省内存占用
<template>
<h2>aaaa</h2>
<h2>bbbb</h2>
</template>
Teleport(瞬移)
- Teleport提供了一种干净的方法,让组件的 html在父组件界面外的特定标签(很可能是body)下插入显示
// ModalButton.vue
<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>
// App.vue
<template>
<h2>App</h2>
<modal-button></modal-button>
</template>
<script lang="ts">
import ModalButton from './ModalButton.vue'
export default {
setup() {
return {
}
},
components: {
ModalButton
}
}
</script>
Suspense(不确定的)
- 它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
// App.vue
<template>
<h2>Suspense(不确定的):</h2>
<Suspense>
<!-- <template v-slot:default> -->
<template #default>
<!-- 渲染异步组件 -->
<async-com></async-com>
</template>
<template v-slot:fallback>
<!-- 临时展示组件 -->
<h3>LOADING...</h3>
</template>
</Suspense>
</template>
<script lang='ts'>
import { defineAsyncComponent, defineComponent } from 'vue'
// import { defineComponent, reactive, shallowReadonly } from 'vue'
/**
* Vue2中动态引入组件的写法:(在 Vue3中此写法不行)
*/
// const AsyncCom = () => import('./components/AsyncCom.vue')
/**
* Vue3中动态引入组件的写法:defineAsyncComponent()
*/
// const AsyncCom = defineAsyncComponent(() => import('./components/AsyncCom.vue'))
// 静态引入组件
import AsyncCom from './components/AsyncCom.vue'
export default defineComponent({
name: 'App',
components: {
AsyncCom
},
setup () {
return {
//
}
}
})
</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>
// AsyncCom.vue
<template>
<h3>AsyncCom:异步组件</h3>
<h3>{{msg}}</h3>
</template>
<script lang='ts'>
import axios from 'axios'
import { defineComponent } from "vue";
// import { defineComponent, reactive, shallowReadonly } from 'vue'
export default defineComponent({
name: "AsyncCom",
// setup() {
// // return {
// // //
// // }
// 方式一:
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve({
// msg: '一往情深,矢志不渝'
// })
// }, 2000);
// });
// },
// 方式二:推荐
// setup () {
// return axios.get('/data/address.json').then(res => {
// return {
// data: res.data
// }
// })
// },
// 方式三:
async setup () {
const result = await axios.get('/data/address.json')
return {
data: result.data
}
}
});
</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>