前言
Vue3
的出现完美的解决了 Vue2
在某些方面的缺陷,接下来让我们一起走进 Vue3
,看它给我们带来了那些变化~
Vue3
Vue3
比 Vue2
更快、更小,体积缩小了 41%
,渲染速度提升 33%
,内存用量下降 54%
。更加灵活的 Composition API
以及完美支持 TypeScript
。通过这些数字和优点,都是非常值得我们把 Vue3
好好 学习一波!下面我们对 Vue3
里常见的 Compsition API
进行详解。
Vue3中文文档
Composition API
Composition API中文文档
- setup
setup
函数是 入口函数 ,在这个函数里我们可以定义变量、函数、生命周期等等,然后进行导出,就可以在页面上进行访问了。具体使用步骤如下:
<template>
<p>数字为:{{num}}</p>
<p>两倍为:{{double}}</p>
<button @click="sum">累加</button>
</template>
<script>
import { ref, onMounted, computed } from "vue"; // 导入Vue3中的核心方法
export default {
..., // 可以定义一些Vue2时的options
setup() {
const num = ref(666); // 定义一个响应式的常量
const sum = (a, b) => a + b; // 定义一个方法
const double = computed(() => num.value+1) // 定义一个计算属性
onMounted(() => {
console.log('页面加载完执行的生命周期钩子函数')
})
// ...等等都可以定义在setup函数中
return { // 导出定义的变量和方法,在模板中使用
num,
sum,
double
}
}
}
</script>
注意:setup
函数可接受两个参数:props
、context
;
props
表示父组件传递过来的属性,且值为响应式的,若修改其值页面将被更新。故,props
不能进行解构,但可以使用toRefs
进行解构,否则它将失去响应式。context
上下文,里面包含attrs
、slots
、emit
等。它不是响应式的,故可以进行解构
当 setup
被执行时,组件实例未被创建,只能访问到以下属性:props
、attrs
、slots
、emit
。不能访问到其他选项:computed
、methods
等。
- ref
<template>
<p v-show="flag">是否显示</p>
<p>我的名字:{{name}}</p>
<p>我的年龄:{{age}}</p>
<p ref="refDom">你好啊</p>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const flag = ref(false); // 定义 Boolean 类型的变量
const age = ref(25); // 定义 Number 类型的变量
const name = ref('tmc'); // 定义 String 类型的变量
const refDom = ref(null); // 可以访问到 Dom 元素
return {
flag,
age,
name,
refDom
}
}
}
</script>
注意:ref
定义的变量,在模板中会被自动展开,故无需使用 .value
;在 setup
函数中使用这些变量则不需要加 .value
- reactive
<template>
<p>我的名字:{{state.name}}</p>
<p>我的年龄:{{state.age}}</p>
</template>
<script>
import { reactive } from "vue";
export default {
setup() {
const state = reactive({
name: 'tmc',
age: 25
})
return {
state
}
}
}
</script>
注意:
reactive
类似于Vue2
中的Vue.observable()
- 若想在模块里使用简单,可使用
toRefs
进行导出 ref(variable)
等价于reactive({value: variable})
- toRefs
<template>
<p>我的名字:{{name}}</p>
<p>我的年龄:{{age}}</p>
</template>
<script>
import { toRefs, reactive } from "vue";
export default {
setup() {
const data = reactive({
name: 'tmc',
age: 25
})
return {
...toRefs(data)
}
}
}
</script>
注意:toRefs
返回时将属性都转换为 ref
;故,解构时也不会丢失其响应性
- toRaw
<template>
<!-- ... -->
</template>
<script>
import { toRaw, reactive, ref } from "vue";
export default {
setup() {
const data1 = reactive({
name: 'tmc',
age: 25
})
const data2 = ref(666)
const changeData1 = toRaw(data1)
const changeData2 = toRaw(data2.value)
return {
...toRefs(data)
}
}
}
</script>
注意:当转换 ref
对象的时,需要传 .value
- provide & inject
<!-- 父组件 -->
<template>
<my-test></my-test>
</template>
<script>
import { provide } from "vue";
import MyTest from './MyTest';
export default {
components: {
MyTest
},
setup() {
provide('name', 'tmc')
// 可提供多个 provide
return {
// ...
}
}
}
</script>
注意:provide
接受两个参数,第一是 key
,另一个是 value
<!-- 子组件 -->
<template>
<p> {{name}} </p>
</template>
<script>
import { inject } from "vue";
export default {
components: {
MyTest
},
setup() {
const name = inject('name');
// inject('name', 'xxx'); // 可指定默认值
return {
name
}
}
}
</script>
注意:inject
第一个参数是 key
,第二个参数可指定默认值(可选)
若想提供和注入响应式的值,可以在提供 provide
时使用 ref
和 reactive
- computed
- watch & watchEffect
-
watch
监听ref
创建的响应式数据<template> <!-- ... --> </template> <script> import { watch, ref } from "vue"; export default { setup() { const name = ref('tmc'); watch(name, (newVal, oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) }) } } </script>
-
watch
监听reactive
创建的响应式数据<template> <!-- ... --> </template> <script> import { watch, reactive } from "vue"; export default { setup() { const data = reactive({ name: 'tmc', age: 25 }); // 监听单个属性 watch(() => data.name, (newVal, oldVal) => { console.log('新值:' + newVal) console.log('旧值:' + oldVal) }) // 监听多个属性 watch(() => [data.name, data.age], ([newName, oldName], [newAge, oldAge]) => { console.log('新值:' + newName) console.log('旧值:' + oldName) console.log('新值:' + newAge) console.log('旧值:' + oldAge) }) } } </script>
watch
、watchEffect
会返回一个用于停止这个监听的函数 stop
,可用户停止监听。
注意:watch
和 watchEffect
的区别?
watch
是需要传入监听的属性,而watchEffect
是自动收集依赖watch
可以看到属性前后变化的值,而watchEffect
没有watch
是属性改变的时候执行,而watchEffect
默认就会执行一次,且属性变动也会执行
- ...
更多 Composition API
的用法和注意事项请参考 Vue 组合式 API
生命周期
<template>
<!-- ... -->
</template>
<script>
import { onMounted, onUpdated, onUnmounted } from "vue";
export default {
setup() {
onMounted(() => {
console.log('mounted!') // 挂载
})
onUpdated(() => {
console.log('updated!') // 更新
})
onUnmounted(() => {
console.log('unmounted!') // 销毁
})
}
}
</script>
beforeCreate-> 使用setup
created-> 使用setup
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount
destroyed
->onUnmounted
errorCaptured
->onErrorCaptured
新增的钩子函数:
- onRenderTracked
- onRenderTriggered
自定义函数 Hooks
// hooks/useLoadMore.ts
import { computed, onMounted, Ref } from 'vue';
import { IGlobalState } from '@/store';
import { Vuex } from 'vuex';
import _ from 'lodash';
export function useLoadMore(refreshEle: Ref<null | HTMLElement>, store: Store<IGlobalState>, type: string) {
let element: HTMLElement; // 需要滚动的元素
function _loadMore() {
let containerHeight = element.clientHeight; // 获取可视区域的高度
let scrollTop = element.scrollTop; // 获取滚动的高度
let scrollHeight = element.scrollHeight; // 获取这个列表的高度
if(containerHeight + scrollTop + 25 >= scrollHeight) {
store.dispatch(type); // 派发action
}
}
onMounted(() => {
element = refreshEle.value as HTMLElement;
element.addEventListener('scroll', _.debounce(_loadMore, 200)); // 防抖
})
// 可更改loading、hasMore等状态
const isLoading = computed(() => {
return store.state.home.homeList.loading
})
const hasMore = computed(() => {
return store.state.home.homeList.hasMore
})
}
在页面中使用
// index.vue
<template>
...
<div ref="refreshEle">
...
<HomeList :xxx="xxx"></HomeList>
...
</div>
...
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { Store, useStore } from 'vuex';
import { IGlobalState } from '@/store';
import { useLoadMore } from '@/hooks/useLoadMore';
export default defineComponent({
...
setup() {
let store = useStore<IGlobalState>();
let refreshEle = ref<null | HTMLElement>(null);
const { isLoading, hasMore } = useLoadMore(refreshEle, store, `home/${types.SET_HOME_LIST}`);
return {
refreshEle,
isLoading,
hasMore
}
}
...
})
</script>
内置组件
Teleport
可以将模块移动到当前DOM
元素之外的其他位置的技术
// index.html
<html>
...
<div id="app"></div>
<div id="teleport-toast"></div>
</html>
// components/index.vue
<button @click="showToast">显示toast</button>
<teleport to="#teleport-toast">
<div v-show="showFlag">
<div>
显示 Toast 内容
</div>
</div>
</teleport>
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const showFlag = ref(false);
const showToast = (() => {
showFlag.value = true;
});
return {
showFlag,
showToast
}
}
})
总结:使用 teleport
组件,通过 to
属性,手动指定该组件渲染的位置与 <div id="app"></div>
同级,但 teleport
的状态 showFlag
又是完全由内部 Vue
组件控制,这就很 Nice~啦!
<Suspense>
<!-- #default可不写 -->
<template #default>
<xxxx>某某组件</xxxx>
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
注意:#default
、#fallback
是采用具名插槽的简写;例:v-slot:tmc
=== #tmc
扩展:React
中也包含 <Suspense>
组件,常与 React.lazy()
连用
// 基本用法
import React, { Suspense } from 'react';
const Page = React.lazy(() => import('./Page'));
<Suspense fallback={<div> Loading... </div>}>
<Page />
</Suspense>
Vue3
中的 defineAsyncComponent
与 React.lazy()
类似
在 Vue2.x
时,组件只能有一个根 DOM
,在 Vue3
中,组件可以存在多个根 DOM
全局 API
- getCurrentInstance
- 注册组件方式的改变
// Vue2 使用 Vue.component 方法
import Vue from 'vue';
import Test from './Test.vue';
Vue.component('Test', Test);
// Vue3 使用 createApp().component 方式
import { createApp, h } from 'vue';
import Test from './Test.vue';
createApp(Test).component('Comp', { render: () => h('div', '自定义组件') })
- 部分
API
的使用方式改变(为了tree-shaking
)
// Vue2
import Vue from 'vue';
Vue.nextTick(() => {})
// Vue3
import { nextTick } from 'vue';
nextTick(() => {})
- 应用实例
import { createApp } from 'vue';
const app = createApp(); // app 为应用实例
Vue2.x全局API | Vue3的应用实例API | Vue.config | app.config | Vue.config.productionTip | 移除 | Vue.use | app.config.isCustomElement | Vue.component | app.use | ... | ... |
---|
注意:
Vue3
移除了$on/$once/$off
和Vue.filter
Vue-Router
Vue-Router 官方地址
基本用法
import { createRouter, createWebHashHistory } from 'vue-router';
import Home from '@/views/home.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue');
}
]
const router = createRouter({
histroy: createWebHashHistory(),
routes
})
export default router;
Vuex
Vuex 官方地址
基础用法
// store
import { createStore } from 'vuex';
export default createStore({
state: {}, // 状态
getters: {}, // 获取状态
mutations: {}, // 同步修改状态
actions: {}, // 异步修改状态
modules: {} // 模块
})
// template
<template>
...
</template>
<script>
import { useStore } from 'vuex';
export default defineComponent({
setup() {
const store = useStore(); // 创建store实例
...
}
})
</script>
完整 Store 的结构设计
在 store
目录下创建四个子目录和一个文件,分别是:
state
:存放项目中使用的状态getters
:将一些状态做一些处理在使用mutations
:同步的修改状态actions
:异步的修改状态index.js
文件:用于整合state
、getters
、mutations
、actions
在 store
目录下创建一个目录和一个文件,分别是:
modules
:存放项目的某一个比较大的模块,如:首页、我的...index.js
文件:用于整合modules
模块
不熟练 Vuex
的小伙伴,可以阅读下这篇文章:掌握工作中Vuex核心API用法以及基本原理
Vue2.x & Vue3
Vue2.0
采用flow
进行编写,而Vue3.0
源码全部采用TypeScript
进行开发,对TS支持友好(所有属性都放在this
对象上,难以推到组件的数据类型)- 源码体积优化:移除部分
api(filter)
,使用tree-shaking
(大量的API
挂载在Vue
对象的原型上,难以实现TreeShaking
) - 数据劫持优化:
Vue3
采用Proxy
,性能大大提升 - 编译优化:
Vue3
实现了静态模板分析、重写diff算法 Composition API
:整合业务代码的逻辑,提取公共逻辑(受React Hooks
启发。Vue2
采用mixin
- 命名冲突&数据来源不清晰)- 自定义渲染器:可以用来创建自定义的渲染器,改写Vue底层渲染逻辑
- 新增
Fragment
、Teleport
、Suspense
等内置组件
总结
学一门新的技术最好的办法就是使用该技术自己写一个项目,慢慢的摸索,不断的爬坑就慢慢的都会了~
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!