
带你入门体验 Vue3
本文同步发布在我的Github 个人博客
前言
一直都是 React 进行开发,虽然 Vue 是接触最早的,但现在实际工作也不怎么有机会用,Vue3 都出了。其中新写法有点像 React Hook,于是,这段时间迅速对 Vue3 进行了基本知识入门,体验下 Vue3。
Vue2.x 有哪些痛点
首先 Vue3 出现之前,让我们来理理 Vue2.x 有什么痛点,需要 Vue3 补上的。
Vue2.x 对数组对象的深层监听无法实现。因为组件每次渲染都是将 data 里的数据通过 defineProperty 进行响应式或者双向绑定上,之前没有后加的属性是不会被绑定上,也就不会触发更新渲染。 代码的可读性随着组件变大而变差 每一种代码复用的方式,都存在缺点 vue2.x 对 typescript 支持不太友好,需要使用一堆装饰器语法Options API 到 Composition API 的转变
Options API 即是通过定义 methods,computed,watch,data 等属性与方法,共同处理页面逻辑。
可以看到 Options 代码编写方式,如果是组件状态,则写在 data 属性上,如果是方法,则写在 methods 属性上
当组件变得复杂时,会导致对应属性的列表也会增长,这可能会导致组件难以阅读和理解。
下面表示组件是一个大型组件(不同颜色表示不同逻辑),会看到同个功能逻辑的代码被分散到各处,这种碎片化使得理解和维护复杂组件变得困难,需要不断"跳"到相关代码处。
而 Composition API 是根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合)
用 Composition API 来表示一个大型组件与上面 Options API 的对比,可以直观地感受到 Composition API 在逻辑组织方面的优势,以后修改一个属性功能的时候,只需要跳到控制该属性的方法中即可,在逻辑组织和逻辑复用方面更好。
Options API 是面向对象的思想,Composition API 是函数式编程的思想。
Vue3 将 Vue2.x 里面的 Options API,细粒度拆分成细小的函数,然后统一放到 setup 这一个入口函数中调用。
这样做的好处:
保留了 Vue2.x 中各个函数的功能,做到兼容 同时以小的函数形式被调用,更加灵活,更好维护,更好 tree-shakingvue3 有哪些优化
打包大小减少 41%,初次渲染快 55%,更新快 133%,内存使用减少 54%(官方数据) 采用 Typescript 开发,更好支持 Typescript 使用 Proxy 代替 vue2.x 中的 defineProperty,能够深层监听数组对象的变化。 Options API 到 Composition API,使复杂组件逻辑复用变得更容易和维护。Composition API
字面意思就是组合式 API,也就是将原来的很多底层的方法拆分开,暴露出来让大家去使用。
setup
setup 是 Composition API 的核心,它在props、data、computed、methods、生命周期函数之前运行的。
它返回一个对象,该对象上的属性将合并到组件模板的渲染上下文中,还可以返回一个 render 函数。
<template>
<div>setup</div>
</template>
<script>
export default {
setup() {
// Composition API 逻辑组织的入口
},
}
</script>
它将 Vue2.x 中的 beforeCreate 和 created 代替了,以一个 setup 函数的形式灵活组织代码;还可以 return 数据或者 template,相当于把 data 和 render 也一并代替了。
<template>
<div>{{ foo }}</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
const foo = 2 // 普通变量
return {
foo, // 必须 return 出去才能在模板使用
}
},
})
</script>
顺带一提,通常为了在 TypeScript 下能让组件有正确的参数类型推断、IDE 有更好的提示效果,会在export default处再包一层函数defineComponent(该函数只返回传递给它的对象)
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
// Composition API 逻辑组织的入口
},
})
</script>
setup函数使用需要注意的点:
setup函数的时候,还没有执行
created 生命周期方法,所以在
setup 函数中,无法使用
data和
methods 的变量和方法
setup函数只能是同步的不能是异步的
由于 setup 是一个入口函数,本质是面向函数编程,而 this 是面向对象的一种体现,这里其实相当于取消this了,在 Vue3setup 函数里的this为 undefined。
而取消了this,取而代之的是setup增加了 2 个参数:
props:组件参数
context:上下文信息(如attrs、slots、emit)
export default defineComponent({
props: {
title: String,
},
setup(props, context) {
const foo = 2
console.log(props.title, context)
return {
foo,
}
},
})
</script>
ref
接受一个内部值并返回一个响应式且可变的 ref 对象(响应式对象),ref()创建的数据会触发模版更新。ref 对象具有指向内部值的单个 property .value。
<template>
<div>
{{ count }}
<button @click="handleClick">加1</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
// 被 ref 方法包裹后的元素就变成了一个代理对象
const count = ref(0)
const handleClick = () => {
count.value++ // 在setup里面使用ref代理对象需要 `.value`,模板中使用时由于vue帮我们做了自动解析所以不用 `.value`
}
return {
count,
handleClick,
}
},
})
</script>
reactive
reactive 也是实现响应式的一种方法,它接收一个普通对象然后返回该普通对象的响应式代理,相当于 Vue2.x 中的 Vue.observable()
一般约定 reactive 的参数是一个对象,而ref的参数通常是原始数据类型,虽然反过来也可以。
ref的本质还是 reactive 系统会自动根据ref()函数的入参将其转换成ref(x)即reactive({ value:x })
<template>
<div>
{{ obj1.count1 }}
{{ count2 }}
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
setup() {
const obj1 = reactive({
count1: 10,
})
const obj2 = reactive({
count2: 20,
})
return {
obj1,
...toRefs(obj2),
}
},
})
</script>
上面代码可以看到,直接 return obj1,在模板使用时需要obj.count1,那为什么不直接...obj1呢,那是因为 obj1 是proxy代理对象,整个对象都是响应式,所以不能使用剩余运算符。
如果觉得麻烦,可以使用 ...toRefs(obj2),这样就可以在模板直接使用定义的数据了。
toRefs函数是将reactive创建的响应式对象,转化成为普通的对象,并且这个对象上的每个节点,都是ref类型的响应式数据
toRef
可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
export default defineComponent({
setup() {
const state = reactive({
foo: 1,
bar: 2,
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
},
})
toRef()接收两个参数,第一个为对象,第二个为对象中的某个属性。它是对原数据的一个引用,当值改变时会影响到原始值;创建的响应式数据并不会触发 vue 模版更新。
toRefs
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。
toRefs()接收一个对象作为参数,并遍历对象身上的所有属性,然后逐个调用toRef()执行。以此,将响应式对象转化为普通对象,便于在模版中可以直接使用属性。
通常用来将一组的响应式对象拆成单个的响应式对象,如将一个 reactive 代理对象打平,转换为 ref 代理对象,使得对象的属性可以直接在 template 上使用。
<template>
<div>
{{ foo }}
{{ bar }}
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
setup() {
// 创建一个响应式对象state
const state = reactive({
foo: 1,
bar: 2,
})
const stateAsRefs = toRefs(state) // 将响应式的对象变为普通对象结构, 且能使用ES6的扩展运算符,也仍具有响应性
// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
return {
...stateAsRefs,
}
},
})
</script>
computed
接受一个 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。
setup() {
const count = ref(1)
const plusOne = computed(() => count.value + 1) // 结果是一个 ref 代理对象,取值需要 .value
console.log(plusOne.value) // 2
},
与 Vue2.x 中的作用类似,获取一个计算结果。当 computed 参数使用 object 对象书写时,不仅支持取值 get(默认),还支持赋值 set。
setup() {
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
},
})
plusOne.value = 1
console.log(count.value) // 0
},
watch
watch API 与选项式 API this.$watch (以及相应的 watch 选项) 完全等效。watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在侦听源发生更改时被调用。
<template>
<div>
{{ count }}
<button @click="changeCount">count加1</button>
</div>
</template>
<script lang="ts">
import { defineComponent, watch, ref } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
const changeCount = () => {
count.value++
}
watch(count, (newCount, prevCount) => {
console.log('newCount:', newCount, ' oldCount:', prevCount)
})
return {
count,
changeCount,
}
},
})
</script>
也可以使用数组同时侦听多个源。
watchEffect
在响应式地跟踪其依赖项时立即执行传入的一个函数,并在更改依赖项时重新运行它。
当组件的 setup()或者生命周期钩子被调用时,watchEffect 会被链接到该组件的生命周期,并在组件卸载时自动停止。
<template>
<div>
{{ count }}
<button @click="changeCount">count加1</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
const changeCount = () => {
count.value++
}
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
return {
count,
changeCount,
}
},
})
</script>
watch 与 watchEffect 比较,watch 允许我们
惰性地执行副作用; 更具体地说明应触发侦听器重新运行的状态; 访问被侦听状态的先前值和当前值。readonly
接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。
setup() {
const original = reactive({ count: 0 })
// 返回的 readonly 对象,一旦修改就会在 console 有 warning 警告。程序还是会照常运行,不会报错。
const copy = readonly(original)
watchEffect(() => {
// 只要有数据变化,这个函数都会执行
console.log(copy.count)
})
// 这里会触发 watchEffect
original.count++
// 这里不会触发上方的 watchEffect,因为是 readonly。
copy.count++ // warning!
},
Fragments Template
Vue3.x 中,可以不用唯一根节点。
<template>
<div></div>
<div></div>
</template>
Teleport 组件
Teleport 在国内翻译成了瞬间移动组件或者独立组件,它可以把你写的组件挂载到任何你想挂载的 DOM 上。
<template>
<div class="user-card">
<b> {{ name }} </b>
<button @click="isModalOpen = true">删除用户</button>
<!-- 注意这一块代码 -->
<Teleport to="#modal">
<div v-show="isModalOpen">
<p>确定删除?</p>
<button @click="removeUser">确定</button>
<button @click="isModalOpen = false">取消</button>
</div>
</Teleport>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue'
export default defineComponent({
setup() {
const isModalOpen = ref(false)
const user = reactive({
name: 'jacky',
})
const removeUser = () => {
console.log('removeUser')
}
return {
isModalOpen,
removeUser,
...toRefs(user),
}
},
})
</script>
运行这段代码之前,需要去./public/index.html加上个 id 为modal元素供挂载,当然也可以到组件里面添加。
<div id="app"></div>
<div id="modal"></div>
Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下渲染了 HTML,而不必求助于全局状态或将其拆分为两个组件。
Suspense 组件
Suspense 提供两个 template 的位置,一个是没有请求未完成或失败显示的内容,一个是全部请求完毕的内容。这样进行异步内容的渲染就会非常简单。
下面 Demo 我就模拟一下用法,不写真实请求了。
新建AsyncShow.vue组件
<template>
<div>
<h1>{{ result }}</h1>
</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'AsyncShow',
setup() {
return new Promise((resolve) => {
setTimeout(() => {
return resolve({
result: '这是请求结果',
})
}, 3000)
})
},
})
</script>
新建Suspense.vue组件
<template>
<div>
<Suspense>
<template #default>
<!-- 请求成功展示 -->
<async-show />
</template>
<template #fallback>
<!-- 请求中和失败展示 -->
<h2>Promise Loading...(请求中)</h2>
</template>
</Suspense>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import AsyncShow from './AsyncShow.vue'
export default defineComponent({
components: {
AsyncShow,
},
setup() {
//
},
})
</script>
Vue3.x 生命周期变化
被替换
beforeCreate -> setup() created -> setup()重命名
生命周期方法前面都统一加了on;destroy被改名成Unmount更贴切也对应Mount
新增
新增的以下 2 个方便调试 debug 的回调钩子:
onRenderTracked:状态跟踪,它会跟踪页面上所有响应式变量和方法的状态,也就是我们用 return 返回去的值,他都会跟踪。只要页面有 update 的情况,他就会跟踪,然后生成一个 event 对象,我们通过 event 对象来查找程序的问题所在。 onRenderTriggered:状态触发,它不会跟踪每一个值,而是给你变化值的信息,并且新值和旧值都会给你明确的展示出来。新旧生命周期谁先运行?
在 Vue2.x 中通过补丁形式引入 Composition API,进行 Vue2.x 和 Vue3.x 的回调函数混用时:Vue2.x 的回调函数会相对先执行,比如:mounted 优先于 onMounted。 在 Vue3.x 中,为了兼容 Vue2.x 的语法,所有旧的生命周期函数得到保留(除了 beforeDestroy 和 destroyed)。当生命周期混合使用时:Vue3.x 的生命周期相对优先于 Vue2.x 的执行,比如:onMounted 比 mounted 先执行。结语
暂时就写这么多了,并不完全,但也能大概对 Vue3 有个初步了解了。
本文案例代码:vue3-tutorial
参考
vue2.x/React/vue3.x 简单横评(4) vue3 composition api Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?JackySummer
微信公众号【前端精神时光屋】
JackySummer
微信公众号【前端精神时光屋】
宣传栏
目录
本文同步发布在我的Github 个人博客
前言
一直都是 React 进行开发,虽然 Vue 是接触最早的,但现在实际工作也不怎么有机会用,Vue3 都出了。其中新写法有点像 React Hook,于是,这段时间迅速对 Vue3 进行了基本知识入门,体验下 Vue3。
Vue2.x 有哪些痛点
首先 Vue3 出现之前,让我们来理理 Vue2.x 有什么痛点,需要 Vue3 补上的。
Vue2.x 对数组对象的深层监听无法实现。因为组件每次渲染都是将 data 里的数据通过 defineProperty 进行响应式或者双向绑定上,之前没有后加的属性是不会被绑定上,也就不会触发更新渲染。 代码的可读性随着组件变大而变差 每一种代码复用的方式,都存在缺点 vue2.x 对 typescript 支持不太友好,需要使用一堆装饰器语法Options API 到 Composition API 的转变
Options API 即是通过定义 methods,computed,watch,data 等属性与方法,共同处理页面逻辑。
可以看到 Options 代码编写方式,如果是组件状态,则写在 data 属性上,如果是方法,则写在 methods 属性上
当组件变得复杂时,会导致对应属性的列表也会增长,这可能会导致组件难以阅读和理解。
下面表示组件是一个大型组件(不同颜色表示不同逻辑),会看到同个功能逻辑的代码被分散到各处,这种碎片化使得理解和维护复杂组件变得困难,需要不断"跳"到相关代码处。
而 Composition API 是根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合)
用 Composition API 来表示一个大型组件与上面 Options API 的对比,可以直观地感受到 Composition API 在逻辑组织方面的优势,以后修改一个属性功能的时候,只需要跳到控制该属性的方法中即可,在逻辑组织和逻辑复用方面更好。
Options API 是面向对象的思想,Composition API 是函数式编程的思想。
Vue3 将 Vue2.x 里面的 Options API,细粒度拆分成细小的函数,然后统一放到 setup 这一个入口函数中调用。
这样做的好处:
保留了 Vue2.x 中各个函数的功能,做到兼容 同时以小的函数形式被调用,更加灵活,更好维护,更好 tree-shakingvue3 有哪些优化
打包大小减少 41%,初次渲染快 55%,更新快 133%,内存使用减少 54%(官方数据) 采用 Typescript 开发,更好支持 Typescript 使用 Proxy 代替 vue2.x 中的 defineProperty,能够深层监听数组对象的变化。 Options API 到 Composition API,使复杂组件逻辑复用变得更容易和维护。Composition API
字面意思就是组合式 API,也就是将原来的很多底层的方法拆分开,暴露出来让大家去使用。
setup
setup 是 Composition API 的核心,它在props、data、computed、methods、生命周期函数之前运行的。
它返回一个对象,该对象上的属性将合并到组件模板的渲染上下文中,还可以返回一个 render 函数。
<template>
<div>setup</div>
</template>
<script>
export default {
setup() {
// Composition API 逻辑组织的入口
},
}
</script>
它将 Vue2.x 中的 beforeCreate 和 created 代替了,以一个 setup 函数的形式灵活组织代码;还可以 return 数据或者 template,相当于把 data 和 render 也一并代替了。
<template>
<div>{{ foo }}</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
const foo = 2 // 普通变量
return {
foo, // 必须 return 出去才能在模板使用
}
},
})
</script>
顺带一提,通常为了在 TypeScript 下能让组件有正确的参数类型推断、IDE 有更好的提示效果,会在export default处再包一层函数defineComponent(该函数只返回传递给它的对象)
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
// Composition API 逻辑组织的入口
},
})
</script>
setup函数使用需要注意的点:
setup函数的时候,还没有执行
created 生命周期方法,所以在
setup 函数中,无法使用
data和
methods 的变量和方法
setup函数只能是同步的不能是异步的
由于 setup 是一个入口函数,本质是面向函数编程,而 this 是面向对象的一种体现,这里其实相当于取消this了,在 Vue3setup 函数里的this为 undefined。
而取消了this,取而代之的是setup增加了 2 个参数:
props:组件参数
context:上下文信息(如attrs、slots、emit)
export default defineComponent({
props: {
title: String,
},
setup(props, context) {
const foo = 2
console.log(props.title, context)
return {
foo,
}
},
})
</script>
ref
接受一个内部值并返回一个响应式且可变的 ref 对象(响应式对象),ref()创建的数据会触发模版更新。ref 对象具有指向内部值的单个 property .value。
<template>
<div>
{{ count }}
<button @click="handleClick">加1</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
// 被 ref 方法包裹后的元素就变成了一个代理对象
const count = ref(0)
const handleClick = () => {
count.value++ // 在setup里面使用ref代理对象需要 `.value`,模板中使用时由于vue帮我们做了自动解析所以不用 `.value`
}
return {
count,
handleClick,
}
},
})
</script>
reactive
reactive 也是实现响应式的一种方法,它接收一个普通对象然后返回该普通对象的响应式代理,相当于 Vue2.x 中的 Vue.observable()
一般约定 reactive 的参数是一个对象,而ref的参数通常是原始数据类型,虽然反过来也可以。
ref的本质还是 reactive 系统会自动根据ref()函数的入参将其转换成ref(x)即reactive({ value:x })
<template>
<div>
{{ obj1.count1 }}
{{ count2 }}
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
setup() {
const obj1 = reactive({
count1: 10,
})
const obj2 = reactive({
count2: 20,
})
return {
obj1,
...toRefs(obj2),
}
},
})
</script>
上面代码可以看到,直接 return obj1,在模板使用时需要obj.count1,那为什么不直接...obj1呢,那是因为 obj1 是proxy代理对象,整个对象都是响应式,所以不能使用剩余运算符。
如果觉得麻烦,可以使用 ...toRefs(obj2),这样就可以在模板直接使用定义的数据了。
toRefs函数是将reactive创建的响应式对象,转化成为普通的对象,并且这个对象上的每个节点,都是ref类型的响应式数据
toRef
可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
export default defineComponent({
setup() {
const state = reactive({
foo: 1,
bar: 2,
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
},
})
toRef()接收两个参数,第一个为对象,第二个为对象中的某个属性。它是对原数据的一个引用,当值改变时会影响到原始值;创建的响应式数据并不会触发 vue 模版更新。
toRefs
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。
toRefs()接收一个对象作为参数,并遍历对象身上的所有属性,然后逐个调用toRef()执行。以此,将响应式对象转化为普通对象,便于在模版中可以直接使用属性。
通常用来将一组的响应式对象拆成单个的响应式对象,如将一个 reactive 代理对象打平,转换为 ref 代理对象,使得对象的属性可以直接在 template 上使用。
<template>
<div>
{{ foo }}
{{ bar }}
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
setup() {
// 创建一个响应式对象state
const state = reactive({
foo: 1,
bar: 2,
})
const stateAsRefs = toRefs(state) // 将响应式的对象变为普通对象结构, 且能使用ES6的扩展运算符,也仍具有响应性
// ref 和原始 property 已经“链接”起来了
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
return {
...stateAsRefs,
}
},
})
</script>
computed
接受一个 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象。
setup() {
const count = ref(1)
const plusOne = computed(() => count.value + 1) // 结果是一个 ref 代理对象,取值需要 .value
console.log(plusOne.value) // 2
},
与 Vue2.x 中的作用类似,获取一个计算结果。当 computed 参数使用 object 对象书写时,不仅支持取值 get(默认),还支持赋值 set。
setup() {
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
},
})
plusOne.value = 1
console.log(count.value) // 0
},
watch
watch API 与选项式 API this.$watch (以及相应的 watch 选项) 完全等效。watch 需要侦听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在侦听源发生更改时被调用。
<template>
<div>
{{ count }}
<button @click="changeCount">count加1</button>
</div>
</template>
<script lang="ts">
import { defineComponent, watch, ref } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
const changeCount = () => {
count.value++
}
watch(count, (newCount, prevCount) => {
console.log('newCount:', newCount, ' oldCount:', prevCount)
})
return {
count,
changeCount,
}
},
})
</script>
也可以使用数组同时侦听多个源。
watchEffect
在响应式地跟踪其依赖项时立即执行传入的一个函数,并在更改依赖项时重新运行它。
当组件的 setup()或者生命周期钩子被调用时,watchEffect 会被链接到该组件的生命周期,并在组件卸载时自动停止。
<template>
<div>
{{ count }}
<button @click="changeCount">count加1</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
const changeCount = () => {
count.value++
}
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
return {
count,
changeCount,
}
},
})
</script>
watch 与 watchEffect 比较,watch 允许我们
惰性地执行副作用; 更具体地说明应触发侦听器重新运行的状态; 访问被侦听状态的先前值和当前值。readonly
接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。
setup() {
const original = reactive({ count: 0 })
// 返回的 readonly 对象,一旦修改就会在 console 有 warning 警告。程序还是会照常运行,不会报错。
const copy = readonly(original)
watchEffect(() => {
// 只要有数据变化,这个函数都会执行
console.log(copy.count)
})
// 这里会触发 watchEffect
original.count++
// 这里不会触发上方的 watchEffect,因为是 readonly。
copy.count++ // warning!
},
Fragments Template
Vue3.x 中,可以不用唯一根节点。
<template>
<div></div>
<div></div>
</template>
Teleport 组件
Teleport 在国内翻译成了瞬间移动组件或者独立组件,它可以把你写的组件挂载到任何你想挂载的 DOM 上。
<template>
<div class="user-card">
<b> {{ name }} </b>
<button @click="isModalOpen = true">删除用户</button>
<!-- 注意这一块代码 -->
<Teleport to="#modal">
<div v-show="isModalOpen">
<p>确定删除?</p>
<button @click="removeUser">确定</button>
<button @click="isModalOpen = false">取消</button>
</div>
</Teleport>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue'
export default defineComponent({
setup() {
const isModalOpen = ref(false)
const user = reactive({
name: 'jacky',
})
const removeUser = () => {
console.log('removeUser')
}
return {
isModalOpen,
removeUser,
...toRefs(user),
}
},
})
</script>
运行这段代码之前,需要去./public/index.html加上个 id 为modal元素供挂载,当然也可以到组件里面添加。
<div id="app"></div>
<div id="modal"></div>
Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下渲染了 HTML,而不必求助于全局状态或将其拆分为两个组件。
Suspense 组件
Suspense 提供两个 template 的位置,一个是没有请求未完成或失败显示的内容,一个是全部请求完毕的内容。这样进行异步内容的渲染就会非常简单。
下面 Demo 我就模拟一下用法,不写真实请求了。
新建AsyncShow.vue组件
<template>
<div>
<h1>{{ result }}</h1>
</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'AsyncShow',
setup() {
return new Promise((resolve) => {
setTimeout(() => {
return resolve({
result: '这是请求结果',
})
}, 3000)
})
},
})
</script>
新建Suspense.vue组件
<template>
<div>
<Suspense>
<template #default>
<!-- 请求成功展示 -->
<async-show />
</template>
<template #fallback>
<!-- 请求中和失败展示 -->
<h2>Promise Loading...(请求中)</h2>
</template>
</Suspense>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import AsyncShow from './AsyncShow.vue'
export default defineComponent({
components: {
AsyncShow,
},
setup() {
//
},
})
</script>
Vue3.x 生命周期变化
被替换
beforeCreate -> setup() created -> setup()重命名
生命周期方法前面都统一加了on;destroy被改名成Unmount更贴切也对应Mount
新增
新增的以下 2 个方便调试 debug 的回调钩子:
onRenderTracked:状态跟踪,它会跟踪页面上所有响应式变量和方法的状态,也就是我们用 return 返回去的值,他都会跟踪。只要页面有 update 的情况,他就会跟踪,然后生成一个 event 对象,我们通过 event 对象来查找程序的问题所在。 onRenderTriggered:状态触发,它不会跟踪每一个值,而是给你变化值的信息,并且新值和旧值都会给你明确的展示出来。新旧生命周期谁先运行?
在 Vue2.x 中通过补丁形式引入 Composition API,进行 Vue2.x 和 Vue3.x 的回调函数混用时:Vue2.x 的回调函数会相对先执行,比如:mounted 优先于 onMounted。 在 Vue3.x 中,为了兼容 Vue2.x 的语法,所有旧的生命周期函数得到保留(除了 beforeDestroy 和 destroyed)。当生命周期混合使用时:Vue3.x 的生命周期相对优先于 Vue2.x 的执行,比如:onMounted 比 mounted 先执行。结语
暂时就写这么多了,并不完全,但也能大概对 Vue3 有个初步了解了。
本文案例代码:vue3-tutorial