vue3
1.初识vue3:
vue3支持大多数vue2特性
性能提升
打包减少40%、初次渲染快55%、更新渲染快133%、内存减少54%、使用Proxy代替defineProperty实现数据响应式、重写虚拟dom的实现和Tree-shaking
新增特性:
Composition(组和)api
setup:(ref和reactive/computed和watch/新的生命周期/provide和inject)
新组件:Fragment-文档碎片、Teleport-瞬移组件位置、Suspenes-异步加载组件的loading界面
其他api: 全局api的修改、将原来的全局api转移到应用对象、模板语法变化
2.创建vue3项目:
一、使用vue-cli创建项目
1.如果是第一次创建vue项目,那么应该先安装vue脚手架,全局安装一次即可,其命令:
yarn add @vue/cli -g
2.查看是否安装成功,可以通过查看版本号得知是否安装成功,其命令:
vue --version 或 vue -V
3.通过vue-cli脚手架创建vue项目,其命令:
vue create my-test
4.进入项目输入命令启动项目:npm run serve ,可以在浏览器看到项目正常启动了。
二、使用vite创建项目:
vite是一个由原生ESM驱动的web开发构建工具,在开发环境下基于浏览器原生ES imports开发,它做到了本地快速开发启动,在生产下基于rollup打包。快速的冷启动,无需打包操作,及时的热模块更新,替换性能和模块数量的解耦让更新更快,真正的按需编译,不再等待整个应用编译完成。
1.通过vite创建项目的命令:
npm init vite-app vitedemo
2.进入项目先下载依赖包:vite创建的项目,默认是没有下载依赖包的:
cd vitedemo
npm install
3.启动项目,其命令:
npm run dev
3.vue3项目源码分析:
main.ts文件分析:
// 1.createApp方法在vue3中用来创建一个应用
import { createApp } from 'vue'
// 2.App组件为所有组建的父级组件:
import App from './App.vue'
import router from './router'
import store from './store'
// 3.createApp传入App组件创建一个vue实例,并使用链式调用挂载vuex和router并通过mount挂载到#app上面:
createApp(App).use(store).use(router).mount('#app')
App.vue文件分析:
<template>
<!-- 1.vue3中template中可以没有根标签: -->
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view/>
</template>
HomeView.vue组件分析:
<!-- 1.lang="ts" 表示可以写ts: -->
<script lang="ts">
// 2.defineComponent方法用来定义组件:
import { defineComponent } from 'vue'
// 引入一个HelloWorld组件:
import HelloWorld from '@/components/HelloWorld.vue'
// 3.导出这个组件:
export default defineComponent({
// 4.当前组件的名称:
name: 'HomeView',
// 5.给当前组件注册其他组件:
components: {
// 将HelloWorld组件注册到当前HomeView组件中:
HelloWorld
}
})
</script>
vue3引入组件方式和vue2引入组件方式对比:
// 静态引入组件的方式:
// import AboutViewVue from './AboutView.vue'
// vue2中动态引入组件的方式:特别强调,在vue3中不支持此方式引入组件
// const AboutViewVue = () => import('./AboutView.vue')
// vue3中动态引入组件的方式:
const AboutViewVue = defineAsyncComponent(() => import('./AboutView.vue'))
4.setup和ref:
setup是一个新的配置,所有的组合API函数都在此使用,它只在初始化时执行一次,函数如果返回对象,对象中的属性或方法,在模板中可以直接使用,setup可以理解为vue2中data中数据,但是并不是响应式的,如果需要响应式,那么还需要借助ref, setup方法组件每次进入都会执行一次。
ref是用来定义一个基本类型(测试引用型也会响应式被改)的响应式数据的方法,如果想要操作这个响应式数据,可以通过此属性返回对象的value方法对数据值进行操作,而模板中是不需要value的,如:
<template>
<div class="about">
<!-- 3.使用setup返回对象中的属性: -->
<p>{{ sayHello }}</p>
<p>{{ satHi }}</p>
<button @click="changeStringHandle">更新变量的值</button>
</div>
</template>
<script lang='ts'>
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'AboutView',
// 1.setup:组件每次进入都会执行一次:
setup () {
console.log('setup函数执行了')
let str = 'hello'
// 3.ref用来定义一个响应式数据,ref返回一个对象,其属性value可以拿到具体的值,并用来对其值进行操作
const str2 = ref('hi')
function changeStringHandle () {
str = '哈喽'
str2.value = '嗨'
console.log(str) // 点击按钮方法执行了,但是view中sayHello对应的视图并没有发生变化,是因为setup默认返回的数据并非响应式
}
// 3.setup中返回的对象的属性或方法可以直接在template中使用:
return {
sayHello: str,
satHi: str2,
changeStringHandle
}
}
})
</script>
引用型被改:
<template>
<div class="about">
<p>{{ satHi.names }}</p>
<button @click="changeStringHandle">更新变量的值</button>
</div>
</template>
<script lang='ts'>
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'AboutView',
setup () {
const str2 = ref({
names: '小明',
age: 18
})
function changeStringHandle () {
str2.value.names = '嗨'
}
return {
satHi: str2,
changeStringHandle
}
}
})
</script>
5.reactive:
reactive用来定义一个引用型的响应式数据,它返回一个Proxy的代理对象,被代理者就是reactive中的传入对象。
<template>
<div class="about">
<p>{{ user.names }}</p>
<p>{{ user.age }}</p>
<p>{{ user.height }}</p>
<p>{{ user.sex }}</p>
<button @click="updataHandle">更新变量的值</button>
</div>
</template>
<script lang='ts'>
import { defineComponent, reactive } from 'vue'
export default defineComponent({
name: 'AboutView',
setup () {
const obj:any = {
names: '小明',
age: 18
}
const user = reactive(obj)
console.log(user) // 返回一个代理
function updataHandle () {
user.names = '小红'
// user.age = 16
obj.age = 16 // 教程中直接修改对象的方式是不能响应式的,但是经测试直接改变obj的属性,view试图中也是会改变的,不推荐直接修改
obj.height = 180
user.sex = '男'
// delete obj.names // 可以被删除
delete user.names // 可以被删除
}
return {
user,
updataHandle
}
}
})
</script>
6.vue3响应数据的原理:
vue2中数据响应是: 使用 Object.defineProperty 方法添加对象,重写了原有的 get 和 set 方法实现。
vue3中数据响应是通过Proxy代理实现的,它是深度更新的,具体如下:
// 目标对象:给对象设置any类型,表示可以接收任意类型的对象属性值
const obj:any = {
names: '小明',
age: 18
}
// 代理对象:Proxy第一个参数传入目标对象,第二个参数用于操作目标对象
const proxyObj = new Proxy(obj, {
// 1.get方法用于获取某个对象的属性:参数一为目标对象,参数二为对象属性,此方法需要通过返回 Reflect.get才可以在代理对象中拿到属性值
get (target, props) {
console.log('get方法调用了')
return Reflect.get(target, props)
},
// 2.set方法用于设置某个对象的属性值,同时可以给某个对象添加新的属性,同样需要通过返回Reflect.set才可以实现
set (target, props, newValue) {
console.log('set方法调用了')
return Reflect.set(target, props, newValue)
},
// 3.deleteProperty方法用于删除某个对象的属性值,同样需要通过返回Reflect.deleteProperty才可以实现
deleteProperty (target, props) {
console.log('属性被删除了')
return Reflect.deleteProperty(target, props)
}
})
// 通过代理对象获取目标对象中的某个属性值:
console.log(proxyObj.names) // 未返回Reflect.get时打印内容为:undefined;返回Reflect.get时打印内容为:小明
// 通过代理对象设置目标对象中的某个属性值:
proxyObj.age = 22
// 通过代理对象新增某个属性:
proxyObj.height = 150
// 查看目标对象:
console.log(obj) // {names: '小明', age: 22, height: 150}
// 通过代理对象删除某个对象的属性:
delete proxyObj.names
// 查看目标对象:
console.log(obj) // {age: 22, height: 150}
7.setup详细介绍:
父组件:
<template>
<div class="about">
<p>父组件:{{ satHi }}</p>
<p>父组件:{{ num }}</p>
<button @click="changeStringHandle">父组件:更新变量的值</button>
<!-- 1.vue3父子组件通信支持props型 -->
<HelloWorld :msg="satHi" @onChange="onChangeHandle" />
</div>
</template>
<script lang='ts'>
import HelloWorld from '@/components/HelloWorld.vue'
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'AboutView',
// 2.vue3注册组件跟vue2一样
components: {
HelloWorld
},
// 3.vue3通过props接收父组件传递过来的属性值:props接收一个数组或对象
props: [],
// 4.beforeCreate()生命周期函数同样在vue3中支持
beforeCreate () {
console.log('beforeCreate执行了')
},
// 5.setup的执行时机要早于beforeCreate,由此说明setup执行时当前组件还没有被创建,所以在setup中this是无法使用的,也不能通过this去访问data/computed/methods/props
// 6.setup不能是一个async函数,因为async返回一个promise,template中看不到对象的属性了
setup (props, context) {
console.log(props) // props是一个代理对象,其中有父级组件传递给子级组件的数据,并且是在子级组件中使用props接收到的所有属性,也就是通过props点第三步中prpos中的属性是可以拿到父组件传递过来的属性值的
console.log(context) // context是一个函数对象,里面有attrs对象获取当前组件标签上的所有属性的对象、emit方法用于分发事件 、slots插槽
console.log('setup执行了')
const str2 = ref('hi') // ref响应式后,传递给子组件的值,也会跟着父组件的变化而变化
function changeStringHandle () {
str2.value = '嗨'
}
// 7. 监听子组件通过emit分发的事假处理方法:
function onChangeHandle (e: any) {
console.log('子组件触发了onChange事件,父组件监听到并执行了处理:')
str2.value = e
}
return {
satHi: str2,
num: 99,
changeStringHandle,
onChangeHandle
}
},
// 8.setup中返回的对象属性和data函数中返回对象的属性都可以在template模板中使用,setup和data中的对象属性和methods中的方法会合并为组件对象的属性和方法,这里的合并属性名和方法名不能重复,否则报错
data () {
return {
num2: 88
}
},
// 7.vue3同样支持data方法和methods对象,但是推荐在setup中定义属性和方法
methods: {
change2Handle () {
console.log('methods中的方法')
}
},
mounted () {
console.log(this) // 这里this也是一个代理对象,target下属性和方法是setup和data中的所有属性
}
})
</script>
子组件:
<template>
<div class="hello">
<h1>子组件中:{{ msg }}</h1>
<button @click="sendEmitHandle">子组件分发onChange事件</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'HelloWorld',
props: {
msg: String
},
setup (props, context) {
// setup (props, {attrs, emit, expose, slots}) { // 这里直接可以解构出来context的某个属性
console.log(props.msg) // hi
console.log(context)
function sendEmitHandle () {
console.log('子组件分发一个事件:')
context.emit('onChange', '子组件分发传递过来的值')
}
return {
sendEmitHandle
}
}
})
</script>
8.reactive与ref细节:
<template>
<div class="about">
<div>obj1: {{ obj1 }}</div>
<div>obj2: {{ obj2 }}</div>
<button @click="updataHandle">改变值</button>
</div>
</template>
<script lang='ts'>
import { defineComponent, ref, reactive } from 'vue'
export default defineComponent({
name: 'AboutView',
setup (props, context) {
// 1.ref和reactive都可以以传入一个对象使得传入的对象成为响应式的,并且深层次的属性也是可以响应式变化的(因为每个对象都是被代理的),但是ref底层还是通过reactive处理的,
// ref内部通过给value属性添加getter和setter来实现对数据的劫持,reactive内部是通过proxy来实现对对象内部所有数据的劫持,并通过reflect操作对象内部数据
const obj1 = ref({
names: '小明',
age: 20,
info: {
identitys: '职员'
}
})
const obj2 = reactive({
names: '小红',
age: 18,
info: {
identitys: '学生'
}
})
const updataHandle = () => {
// 2.特别注意两种声明的响应式变量在js和template中的访问方式
obj1.value.info.identitys = '网络管理员' // 视图中值被改变了
obj2.info.identitys = '大学生' // 视图中值被改变了
}
return {
obj1,
obj2,
updataHandle
}
}
})
</script>
9.计算属性和监视:
<template>
<div class="hello">
<div>
<span>姓氏:</span>
<input type="text" v-model="firstName">
<p>firstName:{{ firstName }}</p>
</div>
<div>
<span>名字:</span>
<input type="text" v-model="lastName">
<p>lastName:{{ lastName }}</p>
</div>
<div>
<span>fullName:</span>
<input type="text" v-model="fullName">
<p>fullName:{{ fullName }}</p>
</div>
<div>
<span>fullName2:</span>
<input type="text" v-model="fullName2">
<p>compChange:{{ compChange }}</p>
<input type="text" v-model="compChange">
</div>
<span>-----------通过计算属性实现姓名显示:----------</span>
<p>fullName:{{ fullName }}</p>
<p>fullName2:{{ fullName2 }}</p>
<span>----------------watch监听非响应式数据-------------</span>
<div>
<span>user.names</span>
<input type="text" v-model="user.names">
<p>user.names:{{ user.names }}</p>
<input type="text" v-model="compChange">
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, computed, watch, watchEffect } from 'vue'
export default defineComponent({
name: 'HelloWorld',
props: {
msg: String
},
setup (props, context) {
const firstName = ref('')
const lastName = ref('')
const compChange = ref('')
const user = reactive({
names: '小明',
age: 18
})
// 1.vue3中计算属性:使用计算属性时先引入,如果只传入一个参数回调函数,该函数表示get,且计算属性期望返回一个值(该值为ref对象),所有可以直接在计算属性前面定义一个变量用于接收计算后的值
const fullName = computed(() => {
return firstName.value + lastName.value
})
// 2.vue3中计算属性可以传入一个对象,对象中有set和get方法:其中get用于得到一个值并返回给计算属性对应的变量,而set接收计算属性对应的变量在视图中发生变化的值(通过计算属性视图是不能直接驱动数据变化的,也就是说在视图中改变fullName2,但是fullName2值在数据层是没有改变的,而视图中的最新值是在set方法的参数中接收)
const fullName2 = computed({
get () {
console.log('get')
return firstName.value + lastName.value
},
set (val: string) {
console.log(val)
compChange.value = val // 怪异现象,给compChange赋值的话是断断续续的,不能连着,就连console.log(val)中也是断断续续的了,如果注释当前行代码,console.log(val)中的值是连着的,很诡异
// console.log(compChange.value)
}
})
// 3.watch监听器在vue3中同样是需要引入的,该方法第一个参数为要监听的属性(该属性可以是单个值,也可以是一个数组,当为数组时,参数二函数回调参数也是一个数组),第二个参数是一个回调函数,回调函数的参数为最新要监听的属性值,watch可接第三个参数对象,用于配置此监听器{immediate:true}表示默认执行一次监听,因为watch默认是不会执行,只有监听到值变化时才会执行一次,deep: true开启深度监测
watch(firstName, (val) => {
console.log('watch监听到的值:')
console.log(val)
// lastName.value = val
}, { immediate: true, deep: true })
// 4.在这里user是一个响应式数据,但是user.names不是一个响应式数据,watch直接是无法监听非响应式数据的,只能通过回调函数的形式并返回值,如:[user.names, user.age]可以为:[() => user.names, () => user.age]
watch([() => user.names], (val) => {
console.log('watch监听非响应式数据执行了:')
console.log(val)
})
// 5.watchEffect也是一个监听器,无需配置默认监听,它默认会自动监听一次,且它会监听回调函数中主动赋值的属性,如打印某个属性值时,这个属性值被监听了,如将某个属性的值赋值给另一个变量时,这个属性值被监听了
watchEffect(() => {
// compChange.value = lastName.value + 'watchEffect'
console.log(lastName.value)
console.log('watchEffect执行了')
})
return {
firstName,
lastName,
fullName,
fullName2,
compChange,
user
}
}
})
</script>
10.生命周期:
vue3中生命周期和vue2中生命周期基本一样,只有最后销毁前和销毁的不一样,在vue3中分别是beforeUnmount和unmounted;vue2中生命周期函数都是组件的配置对象,而vue3中都是组合式api,当然vue2中的配置对象生命周期在vue3中同样支持,vue2中的生命周期函数在vue3中对应的组合式api分别如下:
beforeCreated -> setup
created -> setup
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy(且vue3中vue2改名为:beforeUnmount) -> onBeforeUnmount
destroyed(且vue3中vue2改名为:unmounted) -> onUnmounted
errorCaptured -> onErrorCaptured
vue3还新增了2个生命周期函数:
onRenderTracked:
onRenderTriggered:
<template>
<div class="hello">
hello组件输出:{{ msg }}
</div>
</template>
<script lang="ts">
import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
export default defineComponent({
neme: 'HelloWorld',
props: {
msg: String
},
beforeCreate () {
console.log('1.vue2中beforeCreate')
},
created () {
console.log('2.vue2中created')
},
beforeMount () {
console.log('3.vue2中beforeMount')
},
mounted () {
console.log('4.vue2中mounted')
},
beforeUpdate () {
console.log('5.vue2中beforeUpdate')
},
updated () {
console.log('6.vue2中updated')
},
beforeUnmount () {
console.log('7.vue2中beforeUnmount')
},
unmounted () {
console.log('8.vue2中unmounted')
},
setup (props, context) {
console.log('1-2.vue3中setup')
onBeforeMount(() => {
console.log('3.vue3中onBeforeMount')
})
onMounted(() => {
console.log('4.vue3中onMounted')
})
onBeforeUpdate(() => {
console.log('5.vue3中onBeforeUpdate')
})
onUpdated(() => {
console.log('6.vue3中onUpdated')
})
onBeforeUnmount(() => {
console.log('7.vue3中onBeforeUnmount')
})
onUnmounted(() => {
console.log('8.vue3中onUnmounted')
})
}
})
</script>
执行结果:
11.hook函数:
vue3中的hook函数类似于vue2中mixin技术,hook函数的优势:很清楚复用功能代码的来源,vue2中mixin是将一段在多个组件中都用到的代码惊醒抽离,然后通过mixin引入到当前组件即可,而vue3中和vue2中类似。
封装hook函数:
// 引入需要用的到组合式api:
import { ref, onMounted, onBeforeUnmount } from 'vue'
// 1.导出一个公共的方法用来获取点击屏幕的坐标:
export const usePointerClick = () => {
const x = ref(0)
const y = ref(0)
const clickHandle = (e: any) => {
x.value = e.pageX
y.value = e.pageY
}
// 页面挂载时给window注册获取坐标事件
onMounted(() => {
window.addEventListener('click', clickHandle)
})
// 页面卸载时注销window获取坐标事件
onBeforeUnmount(() => {
window.removeEventListener('click', clickHandle)
})
// 将值返回出去
return {
x,
y
}
}
组件中使用:
<template>
<div class="home">
<div>home页:</div>
<div>点击的坐标是:{{ x + ',' + y }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
// 2.组件中引入hook函数:
import { usePointerClick } from '@/hook/hookModule'
export default defineComponent({
setup () {
// 3.调用hook出想要的结果并挂载到当前组件中:
const { x, y } = usePointerClick()
return {
x,
y
}
}
})
</script>
12.toRefs的使用:
toRefs可以把一个响应式对象reactive转换成普通对象,该普通对象的每一个属性都是一个ref。
<template>
<div class="home">
{{ age }} <!-- 不可以动态变化,但是通过toRefs将reactive转换后,此属性称为响应式变化的了 -->
{{ obj.age }} <!-- 可以动态变化 -->
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
setup () {
const obj = reactive({
name: '胡子',
age: 18,
height: 100
})
// 1.通过reactive初始化的对象在下面结构后不能动态变化了,此时可以使用toRefs将reactive初始化的对象转换成一个普通的对象,改普通对象的属性是一个ref,此时就可以成为响应式了
const obj2 = toRefs(obj)
setInterval(() => {
obj.age++
obj.height++
}, 1000)
return {
...obj, // 通过结构出来的属性不能响应式变化
obj,
...obj2 // 根据结构语法特点,后面的属性会覆盖前面的属性,因此obj2结构出来的属性是响应式变化的
}
}
})
</script>
13.ref获取元素:
ref还有另一个作用,用于获取页面中的元素,对元素操作需要在onMounted钩子后面,并且需要先判断其value值是否存在,如:
<template>
<div class="home">
<div class="box" ref="box">box盒子</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
export default defineComponent({
setup () {
// 页面加载前这里还获取不到页面元素,所以括号中可以给null,因为最终存的是html标签,所以可以使用泛型在前面做约束,但是刚开始括号中是null,所以泛型也要使用或加上null做判断取值
const box = ref<HTMLElement | null>(null)
onMounted(() => {
console.log(box) // 此时box就是页面中的元素,但是想要操作元素,前面还要通过判断value是否存在,当存在时在操作,如:
box.value && box.value.setAttribute('style', 'color: pink') // js原生操作dom的方式部分不支持,具体遇到问题在百度
})
return {
box
}
}
})
</script>
14.shallowReactive和shallowRef:
shallowReactive只处理对象的外层响应式,它是一个浅Reactive;
shallowRef只处理value的响应式,不进行对象的reactive处理。
经测试:它们都会深度监测,测试代码:
<template>
<div class="home">
<div>ref{{ val1 }}</div>
<div>reactive{{ val2 }}</div>
<div>shallowRef{{ val3 }}</div>
<div>shallowReactive{{ val4 }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, shallowRef, shallowReactive } from 'vue'
export default defineComponent({
setup () {
const val1 = ref({
name: 'ref',
son: {
name: 'sonRef'
}
})
const val2 = reactive({
name: 'reactive',
son: {
name: 'sonReactive'
}
})
const val3 = shallowRef({
name: 'shallowRef',
son: {
name: 'sonShallowRef'
}
})
const val4 = shallowReactive({
name: 'shallowReactive',
son: {
name: 'sonShallowReactive'
}
})
setInterval(() => {
val1.value.name += '*'
val2.name += '*'
val3.value.name += '*'
val4.name += '*'
val1.value.son.name += '+'
val2.son.name += '+'
val3.value.son.name += '+'
val4.son.name += '+'
}, 2000)
return {
val1,
val2,
val3,
val4
}
}
})
</script>
15.readonly与shallowReadonly:
readonly和shallowReadonly表示只读,用此方法创建的变量是不能被修改的,readonly和shallowReadonly区别在于:readonly是深度监测,而shallowReadonly是浅监测。
<template>
<div class="home">
<div>{{ obj }}</div>
<button @click="changeHandle">改变</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, readonly, shallowReadonly } from 'vue'
export default defineComponent({
setup () {
const obj = reactive({
name: '小明',
age: 18,
info: {
height: 50,
phone: 11000000000
}
})
// const obj2 = readonly(obj)
const obj2 = shallowReadonly(obj)
const changeHandle = () => {
// 1.readonly和shallowReadonly创建的的变量是不能被修改的,它是一个只读的,readonly和shallowReadonly区别在于:readonly深度监听只读,而shallowReadonly只是监听了最外层属性不可变,里层是可以变化的
// obj2.age = 12 // 报错
// obj2.info.phone = 1555555555 // 在readonly是报错的,而在shallowReadonly是不会报错的
}
return {
obj,
obj2,
changeHandle
}
}
})
</script>
16.toRaw与markRaw:
toRaw将一个代理对象变成普通对象,但是还是会被变回去;markRaw也是将一个代理对象变化为普通对象,而不能在再被变换为代理对象。经测试,两个方法处理的变量依然可以监听到值变化,并且可以驱动视图变化:
<template>
<div class="home">
<div>{{ obj }}</div>
<div>{{ toRowObj }}</div>
<div>{{ markRowObj }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRaw, markRaw } from 'vue'
export default defineComponent({
setup () {
const obj = reactive({
name: '小明',
age: 18,
info: {
height: 50,
phone: 11000000000
}
})
const toRowObj = toRaw(obj)
const markRowObj = markRaw(obj)
setInterval(() => {
toRowObj.name += '5'
markRowObj.name += '5'
toRowObj.info.height += 5
markRowObj.info.height += 5
}, 2000)
return {
obj,
toRowObj,
markRowObj
}
}
})
</script>
17.toRef的特点:
toRef为源响应式对象上的某个属性创建一个ref对象,二者内部操作的是同一个数据值,更新时二者是同步的;和ref不同,ref是拷贝了一份数据单独操作,更新时互不影响,toRef在将某个prop的ref传递给复合函数时很有用。
<template>
<div class="home">
<div>{{ obj }}</div>
<div>{{ age }}</div>
<div>{{ name }}</div>
<div>=============组件:====================</div>
<HelloWorld :ageVal="age"></HelloWorld>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRef, ref } from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'
export default defineComponent({
components: {
HelloWorld
},
setup () {
const obj = reactive({
name: '小明',
age: 18,
info: {
height: 50,
phone: 11000000000
}
})
// 把响应式对象中某个属性变成ref对象:这里改变的是源数据,obj中的age和age都发生变化了,即使传递给子组件,那么子组件中的age也是会变化的,有时候子组件中的js也会使用到这个值,但是在子组件中js部分使用这个props接收的值是通过setup参数拿到的,此时并不是一个ref对象,此时还封装了一个方法,需要传递ref对象,此时就可以借助toRef将传递过去的值进行处理
const age = toRef(obj, 'age')
// 把响应式对象中的某个属性使用ref包装成了一个ref对象,这里是拷贝,和源数据互不影响:
const name = ref(obj.name)
setInterval(() => {
obj.age += 5 // obj中age和age都变化了
age.value += 2 // obj中age和age都变化了
name.value += '+' // 只有外面的name变化了,obj中的并没有变化
}, 5000)
return {
obj,
age,
name
}
}
})
</script>
18.customRef:
customRef用于自定义一个ref,可以利用此api做防抖等场景,如:
<template>
<div class="home">
<input type="text" v-model="keyValue">
<div>{{ keyValue}}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, customRef } from 'vue'
// 定义一个函数:
function useTestHandle <T> (value: T, time: 300) {
let timeId: number
// 1.customRef接收一个回调函数:函数中第一个参数track表示追踪数据,第二个参数trigger表示触发视图更新
return customRef((track, trigger) => {
return {
// 获取数据:
get () {
// 追踪数据:
track()
return value
},
// 设置数据:set函数参数为最新值
set (newVal: T) {
clearTimeout(timeId)
timeId = setTimeout(() => {
value = newVal // 这里可以对值进行处理,比如值最终需要拼接一些字符串等,这个案例只是做了个简单的防抖
// 再去更新界面:
trigger()
}, time)
}
}
})
}
export default defineComponent({
setup () {
const keyValue = useTestHandle('初始值', 300)
return {
keyValue
}
}
})
</script>
19.provide和inject:
provide 和 inject 提供依赖注入,功能类似 2.x 的 provide/inject,实现跨层级组件(祖孙)间通信,类似于&on和&emit,通过provide(key, vale)传递数据,通过inject(key)接收数据。不同的是,provide和inject在组件加载时就会执行,而并非等待只有值变化的时候才执行,当然值变化的时候provide和inject在值变化的时候也只会执行的。
20.响应式数据的判断:
isRef: 监测一个值是否为ref对象
isReactive:监测一个对象是否由reactive创建的响应式对象
isReadonly: 监测一个对象是否由readonly创建的只读代理
isProxy: 监测一个对象是否由reactive或者readonly方法创建的代理
21.新组件:
Fragment(片断)
在Vue2中: 组件必须有一个根标签
在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中,好处: 减少标签层级,减小内存占用
Teleport(瞬移)
Teleport 提供了一种干净的方法,让组件的html在父组件界面外的特定标签(很可能是body)下插入显示,说白了就是将一个组件瞬间移动到非父组件所在的标签下显示
<!-- 将Teleport中的内容放到了body下,使用Teleport后,里面的内容不会在组件中显示,而是被瞬间移动到了body下 -->
<Teleport to='body'>
<div>obj1: {{ obj1 }}</div>
<div>obj2: {{ obj2 }}</div>
<button @click="updataHandle">改变值</button>
</Teleport>
Suspense(不确定的)
它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验,意思就是如果页面中有一个异步组件,在组件加载时,空白的页面可以使用Suspense组件显示一些别的东西,例如loading等。
<div>子级组件:</div>
<Suspense>
<template #default>
<!-- 异步组件: -->
<AboutViewVue></AboutViewVue>
</template>
<template v-slot:fallback>
<!-- loading部分 -->
<div>loading...</div>
</template>
</Suspense>
</div>
/ 定义一个函数:
function useTestHandle (value: T, time: 300) {
let timeId: number
// 1.customRef接收一个回调函数:函数中第一个参数track表示追踪数据,第二个参数trigger表示触发视图更新
return customRef((track, trigger) => {
return {
// 获取数据:
get () {
// 追踪数据:
track()
return value
},
// 设置数据:set函数参数为最新值
set (newVal: T) {
clearTimeout(timeId)
timeId = setTimeout(() => {
value = newVal // 这里可以对值进行处理,比如值最终需要拼接一些字符串等,这个案例只是做了个简单的防抖
// 再去更新界面:
trigger()
}, time)
}
}
})
}
export default defineComponent({
setup () {
const keyValue = useTestHandle(‘初始值’, 300)
return {
keyValue
}
}
})
#### 19.provide和inject:
provide 和 inject 提供依赖注入,功能类似 2.x 的 provide/inject,实现跨层级组件(祖孙)间通信,类似于&on和&emit,通过provide(key, vale)传递数据,通过inject(key)接收数据。不同的是,provide和inject在组件加载时就会执行,而并非等待只有值变化的时候才执行,当然值变化的时候provide和inject在值变化的时候也只会执行的。
#### 20.响应式数据的判断:
isRef: 监测一个值是否为ref对象
isReactive:监测一个对象是否由reactive创建的响应式对象
isReadonly: 监测一个对象是否由readonly创建的只读代理
isProxy: 监测一个对象是否由reactive或者readonly方法创建的代理
#### 21.新组件:
**Fragment(片断)**
在Vue2中: 组件必须有一个根标签
在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中,好处: 减少标签层级,减小内存占用
**Teleport(瞬移)**
Teleport 提供了一种干净的方法,让组件的html在父组件界面外的特定标签(很可能是body)下插入显示,说白了就是将一个组件瞬间移动到非父组件所在的标签下显示
```html
<!-- 将Teleport中的内容放到了body下,使用Teleport后,里面的内容不会在组件中显示,而是被瞬间移动到了body下 -->
<Teleport to='body'>
<div>obj1: {{ obj1 }}</div>
<div>obj2: {{ obj2 }}</div>
<button @click="updataHandle">改变值</button>
</Teleport>
Suspense(不确定的)
它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验,意思就是如果页面中有一个异步组件,在组件加载时,空白的页面可以使用Suspense组件显示一些别的东西,例如loading等。
<div>子级组件:</div>
<Suspense>
<template #default>
<!-- 异步组件: -->
<AboutViewVue></AboutViewVue>
</template>
<template v-slot:fallback>
<!-- loading部分 -->
<div>loading...</div>
</template>
</Suspense>
</div>
提示:本文图片等素材来源于网络,若有侵权,请发邮件至邮箱:[email protected]联系笔者 删除。
笔者:苦海123
其它问题可通过以下方式联系本人咨询:
QQ:810665436
微信:ConstancyMan