10分鐘讓你快速上手Vue3
經(jīng)過了漫長地迭代,Vue 3.0 終于在上 2020-09-18 發(fā)布了,帶了翻天覆地的變化,使用了 Typescript 進(jìn)行了大規(guī)模的重構(gòu),帶來了 Composition API RFC 版本,類似 React Hook 一樣的寫 Vue,可以自定義自己的 hook ,讓使用者更加的靈活,接下來總結(jié)一下 vue 3.0 帶來的部分新特性。
- setup()
- ref()
- reactive()
- isRef()
- toRefs()
- computed()
- watch()
- LifeCycle Hooks(新的生命周期)
- Template refs
- globalProperties
- Suspense
Vue2 與 Vue3 的對比
- 對 TypeScript 支持不友好(所有屬性都放在了 this 對象上,難以推倒組件的數(shù)據(jù)類型)
- 大量的 API 掛載在 Vue 對象的原型上,難以實(shí)現(xiàn) TreeShaking。
- 架構(gòu)層面對跨平臺 dom 渲染開發(fā)支持不友好
- CompositionAPI。愛 ReactHook 啟發(fā)
- 更方便的支持了 jsx
- Vue 3 的 Template 支持多個根標(biāo)簽,Vue 2 不支持
- 對虛擬 DOM 進(jìn)行了重寫、對模板的編譯進(jìn)行了優(yōu)化操作...
一、setup 函數(shù)
setup() 函數(shù)是 vue3 中,專門為組件提供的新屬性。它為我們使用 vue3 的 Composition API 新特性提供了統(tǒng)一的入口, setup 函數(shù)會在 beforeCreate 之后、created 之前執(zhí)行, vue3 也是取消了這兩個鉤子,統(tǒng)一用 setup 代替, 該函數(shù)相當(dāng)于一個生命周期函數(shù),vue 中過去的 data,methods,watch 等全部都用對應(yīng)的新增 api 寫在 setup()函數(shù)中
- setup(props, context) {
- context.attrs
- context.slots
- context.parent
- context.root
- context.emit
- context.refs
- return {
- }
- }
- props: 用來接收 props 數(shù)據(jù)
- context 用來定義上下文, 上下文對象中包含了一些有用的屬性,這些屬性在 vue 2.x 中需要通過 this 才能訪問到, 在 setup() 函數(shù)中無法訪問到 this,是個 undefined
- 返回值: return {}, 返回響應(yīng)式數(shù)據(jù), 模版中需要使用的函數(shù)
二、reactive 函數(shù)
reactive() 函數(shù)接收一個普通對象,返回一個響應(yīng)式的數(shù)據(jù)對象, 想要使用創(chuàng)建的響應(yīng)式數(shù)據(jù)也很簡單,創(chuàng)建出來之后,在 setup 中 return 出去,直接在 template 中調(diào)用即可
- <template>
- {{name}} // test
- <template>
- <script lang="ts">
- import { defineComponent, reactive, ref, toRefs } from 'vue';
- export default defineComponent({
- setup(props, context) {
- let state = reactive({
- name: 'test'
- });
- return state
- }
- });
- </script>
三、ref() 函數(shù)
ref() 函數(shù)用來根據(jù)給定的值創(chuàng)建一個響應(yīng)式的數(shù)據(jù)對象,ref() 函數(shù)調(diào)用的返回值是一個對象,這個對象上只包含一個 value 屬性, 只在 setup 函數(shù)內(nèi)部訪問 ref 函數(shù)需要加.value
- <template>
- <div class="mine">
- {{count}} // 10
- </div>
- </template>
- <script lang="ts">
- import { defineComponent, ref } from 'vue';
- export default defineComponent({
- setup() {
- const count = ref<number>(10)
- // 在js 中獲取ref 中定義的值, 需要通過value屬性
- console.log(count.value);
- return {
- count
- }
- }
- });
- </script>
在 reactive 對象中訪問 ref 創(chuàng)建的響應(yīng)式數(shù)據(jù)
- <template>
- <div class="mine">
- {{count}} -{{t}} // 10 -100
- </div>
- </template>
- <script lang="ts">
- import { defineComponent, reactive, ref, toRefs } from 'vue';
- export default defineComponent({
- setup() {
- const count = ref<number>(10)
- const obj = reactive({
- t: 100,
- count
- })
- // 通過reactive 來獲取ref 的值時,不需要使用.value屬性
- console.log(obj.count);
- return {
- ...toRefs(obj)
- }
- }
- });
- </script>
四、isRef() 函數(shù)
isRef() 用來判斷某個值是否為 ref() 創(chuàng)建出來的對象
- <script lang="ts">
- import { defineComponent, isRef, ref } from 'vue';
- export default defineComponent({
- setup(props, context) {
- const name: string = 'vue'
- const age = ref<number>(18)
- console.log(isRef(age)); // true
- console.log(isRef(name)); // false
- return {
- age,
- name
- }
- }
- });
- </script>
五、toRefs() 函數(shù)
toRefs() 函數(shù)可以將 reactive() 創(chuàng)建出來的響應(yīng)式對象,轉(zhuǎn)換為普通的對象,只不過,這個對象上的每個屬性節(jié)點(diǎn),都是 ref() 類型的響應(yīng)式數(shù)據(jù)
- <template>
- <div class="mine">
- {{name}} // test
- {{age}} // 18
- </div>
- </template>
- <script lang="ts">
- import { defineComponent, reactive, ref, toRefs } from 'vue';
- export default defineComponent({
- setup(props, context) {
- let state = reactive({
- name: 'test'
- });
- const age = ref(18)
- return {
- ...toRefs(state),
- age
- }
- }
- });
- </script>
六、computed()
該函數(shù)用來創(chuàng)造計(jì)算屬性,和過去一樣,它返回的值是一個 ref 對象。里面可以傳方法,或者一個對象,對象中包含 set()、get()方法
6.1 創(chuàng)建只讀的計(jì)算屬性
- import { computed, defineComponent, ref } from 'vue';
- export default defineComponent({
- setup(props, context) {
- const age = ref(18)
- // 根據(jù) age 的值,創(chuàng)建一個響應(yīng)式的計(jì)算屬性 readOnlyAge,它會根據(jù)依賴的 ref 自動計(jì)算并返回一個新的 ref
- const readOnlyAge = computed(() => age.value++) // 19
- return {
- age,
- readOnlyAge
- }
- }
- });
- </script>
6.2 通過 set()、get()方法創(chuàng)建一個可讀可寫的計(jì)算屬性
- <script lang="ts">
- import { computed, defineComponent, ref } from 'vue';
- export default defineComponent({
- setup(props, context) {
- const age = ref<number>(18)
- const computedAge = computed({
- get: () => age.value + 1,
- set: value => age.value + value
- })
- // 為計(jì)算屬性賦值的操作,會觸發(fā) set 函數(shù), 觸發(fā) set 函數(shù)后,age 的值會被更新
- age.value = 100
- return {
- age,
- computedAge
- }
- }
- });
- </script>
七、 watch() 函數(shù)
watch 函數(shù)用來偵聽特定的數(shù)據(jù)源,并在回調(diào)函數(shù)中執(zhí)行副作用。默認(rèn)情況是懶執(zhí)行的,也就是說僅在偵聽的源數(shù)據(jù)變更時才執(zhí)行回調(diào)。
7.1 監(jiān)聽用 reactive 聲明的數(shù)據(jù)源
- <script lang="ts">
- import { computed, defineComponent, reactive, toRefs, watch } from 'vue';
- interface Person {
- name: string,
- age: number
- }
- export default defineComponent({
- setup(props, context) {
- const state = reactive<Person>({ name: 'vue', age: 10 })
- watch(
- () => state.age,
- (age, preAge) => {
- console.log(age); // 100
- console.log(preAge); // 10
- }
- )
- // 修改age 時會觸發(fā)watch 的回調(diào), 打印變更前后的值
- state.age = 100
- return {
- ...toRefs(state)
- }
- }
- });
- </script>
7.2 監(jiān)聽用 ref 聲明的數(shù)據(jù)源
- <script lang="ts">
- import { defineComponent, ref, watch } from 'vue';
- interface Person {
- name: string,
- age: number
- }
- export default defineComponent({
- setup(props, context) {
- const age = ref<number>(10);
- watch(age, () => console.log(age.value)); // 100
- // 修改age 時會觸發(fā)watch 的回調(diào), 打印變更后的值
- age.value = 100
- return {
- age
- }
- }
- });
- </script>
7.3 同時監(jiān)聽多個值
- <script lang="ts">
- import { computed, defineComponent, reactive, toRefs, watch } from 'vue';
- interface Person {
- name: string,
- age: number
- }
- export default defineComponent({
- setup(props, context) {
- const state = reactive<Person>({ name: 'vue', age: 10 })
- watch(
- [() => state.age, () => state.name],
- ([newName, newAge], [oldName, oldAge]) => {
- console.log(newName);
- console.log(newAge);
- console.log(oldName);
- console.log(oldAge);
- }
- )
- // 修改age 時會觸發(fā)watch 的回調(diào), 打印變更前后的值, 此時需要注意, 更改其中一個值, 都會執(zhí)行watch的回調(diào)
- state.age = 100
- state.name = 'vue3'
- return {
- ...toRefs(state)
- }
- }
- });
- </script>
7.4 stop 停止監(jiān)聽
在 setup() 函數(shù)內(nèi)創(chuàng)建的 watch 監(jiān)視,會在當(dāng)前組件被銷毀的時候自動停止。如果想要明確地停止某個監(jiān)視,可以調(diào)用 watch() 函數(shù)的返回值即可,語法如下:
- <script lang="ts">
- import { set } from 'lodash';
- import { computed, defineComponent, reactive, toRefs, watch } from 'vue';
- interface Person {
- name: string,
- age: number
- }
- export default defineComponent({
- setup(props, context) {
- const state = reactive<Person>({ name: 'vue', age: 10 })
- const stop = watch(
- [() => state.age, () => state.name],
- ([newName, newAge], [oldName, oldAge]) => {
- console.log(newName);
- console.log(newAge);
- console.log(oldName);
- console.log(oldAge);
- }
- )
- // 修改age 時會觸發(fā)watch 的回調(diào), 打印變更前后的值, 此時需要注意, 更改其中一個值, 都會執(zhí)行watch的回調(diào)
- state.age = 100
- state.name = 'vue3'
- setTimeout(()=> {
- stop()
- // 此時修改時, 不會觸發(fā)watch 回調(diào)
- state.age = 1000
- state.name = 'vue3-'
- }, 1000) // 1秒之后講取消watch的監(jiān)聽
- return {
- ...toRefs(state)
- }
- }
- });
- </script>
八、LifeCycle Hooks(新的生命后期)
新版的生命周期函數(shù),可以按需導(dǎo)入到組件中,且只能在 setup() 函數(shù)中使用, 但是也可以在 setup 自定義, 在 setup 中使用
- <script lang="ts">
- import { set } from 'lodash';
- import { defineComponent, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onErrorCaptured, onMounted, onUnmounted, onUpdated } from 'vue';
- export default defineComponent({
- setup(props, context) {
- onBeforeMount(()=> {
- console.log('beformounted!')
- })
- onMounted(() => {
- console.log('mounted!')
- })
- onBeforeUpdate(()=> {
- console.log('beforupdated!')
- })
- onUpdated(() => {
- console.log('updated!')
- })
- onBeforeUnmount(()=> {
- console.log('beforunmounted!')
- })
- onUnmounted(() => {
- console.log('unmounted!')
- })
- onErrorCaptured(()=> {
- console.log('errorCaptured!')
- })
- return {}
- }
- });
- </script>
九、Template refs
通過 refs 來回去真實(shí) dom 元素, 這個和 react 的用法一樣,為了獲得對模板內(nèi)元素或組件實(shí)例的引用,我們可以像往常一樣在 setup()中聲明一個 ref 并返回它
- 還是跟往常一樣,在 html 中寫入 ref 的名稱
- 在steup 中定義一個 ref
- steup 中返回 ref的實(shí)例
- onMounted 中可以得到 ref的RefImpl的對象, 通過.value 獲取真實(shí)dom
- <template>
- <!--第一步:還是跟往常一樣,在 html 中寫入 ref 的名稱-->
- <div class="mine" ref="elmRefs">
- <span>1111</span>
- </div>
- </template>
- <script lang="ts">
- import { set } from 'lodash';
- import { defineComponent, onMounted, ref } from 'vue';
- export default defineComponent({
- setup(props, context) {
- // 獲取真實(shí)dom
- const elmRefs = ref<null | HTMLElement>(null);
- onMounted (() => {
- console.log(elmRefs.value); // 得到一個 RefImpl 的對象, 通過 .value 訪問到數(shù)據(jù)
- })
- return {
- elmRefs
- }
- }
- });
- </script>
十、vue 的全局配置
通過 vue 實(shí)例上 config 的配置,包含 Vue 應(yīng)用程序全局配置的對象。您可以在掛載應(yīng)用程序之前修改下面列出的屬性:
- const app = Vue.createApp({})
- app.config = {...}
為組件渲染功能和觀察程序期間的未捕獲錯誤分配處理程序。錯誤和應(yīng)用程序?qū)嵗龑⒄{(diào)用處理程序
- app.config.errorHandler = (err, vm, info) => {}
可以在應(yīng)用程序內(nèi)的任何組件實(shí)例中訪問的全局屬性,組件的屬性將具有優(yōu)先權(quán)。這可以代替 Vue 2.xVue.prototype 擴(kuò)展:
- const app = Vue.createApp({})
- app.config.globalProperties.$http = 'xxxxxxxxs'
可以在組件用通過 getCurrentInstance() 來獲取全局 globalProperties 中配置的信息,getCurrentInstance 方法獲取當(dāng)前組件的實(shí)例,然后通過 ctx 屬性獲得當(dāng)前上下文,這樣我們就能在 setup 中使用 router 和 vuex, 通過這個屬性我們就可以操作變量、全局屬性、組件屬性等等
- setup( ) {
- const { ctx } = getCurrentInstance();
- ctx.$http
- }
十一、Suspense 組件
在開始介紹 Vue 的 Suspense 組件之前,我們有必要先了解一下 React 的 Suspense 組件,因?yàn)樗麄兊墓δ茴愃啤?/p>
React.lazy 接受一個函數(shù),這個函數(shù)需要動態(tài)調(diào)用 import()。它必須返回一個 Promise,該 Promise 需要 resolve 一個 default export 的 React 組件。
- import React, { Suspense } from 'react';
- const myComponent = React.lazy(() => import('./Component'));
- function MyComponent() {
- return (
- <div>
- <Suspense fallback={<div>Loading...</div>}>
- <myComponent />
- </Suspense>
- </div>
- );
- }
Vue3 也新增了 React.lazy 類似功能的 defineAsyncComponent 函數(shù),處理動態(tài)引入(的組件)。defineAsyncComponent 可以接受返回承諾的工廠函數(shù)。當(dāng)您從服務(wù)器檢索到組件定義時,應(yīng)該調(diào)用 Promise 的解析回調(diào)。您還可以調(diào)用 reject(reason)來指示負(fù)載已經(jīng)失敗
- import { defineAsyncComponent } from 'vue'
- const AsyncComp = defineAsyncComponent(() =>
- import('./components/AsyncComponent.vue')
- )
- app.component('async-component', AsyncComp)
Vue3 也新增了 Suspense 組件:
- <template>
- <Suspense>
- <template #default>
- <my-component />
- </template>
- <template #fallback>
- Loading ...
- </template>
- </Suspense>
- </template>
- <script lang='ts'>
- import { defineComponent, defineAsyncComponent } from "vue";
- const MyComponent = defineAsyncComponent(() => import('./Component'));
- export default defineComponent({
- components: {
- MyComponent
- },
- setup() {
- return {}
- }
- })
- </script>
十二、vue 3.x 完整組件模版結(jié)構(gòu)
一個完成的 vue 3.x 完整組件模版結(jié)構(gòu)包含了:組件名稱、 props、components、setup(hooks、computed、watch、methods 等)
- <template>
- <div class="mine" ref="elmRefs">
- <span>{{name}}</span>
- <br>
- <span>{{count}}</span>
- <div>
- <button @click="handleClick">測試按鈕</button>
- </div>
- <ul>
- <li v-for="item in list" :key="item.id">{{item.name}}</li>
- </ul>
- </div>
- </template>
- <script lang="ts">
- import { computed, defineComponent, getCurrentInstance, onMounted, PropType, reactive, ref, toRefs } from 'vue';
- interface IState {
- count: 0,
- name: string,
- list: Array<object>
- }
- export default defineComponent({
- name: 'demo',
- // 父組件傳子組件參數(shù)
- props: {
- name: {
- type: String as PropType<null | ''>,
- default: 'vue3.x'
- },
- list: {
- type: Array as PropType<object[]>,
- default: () => []
- }
- },
- components: {
- /// TODO 組件注冊
- },
- emits: ["emits-name"], // 為了提示作用
- setup (props, context) {
- console.log(props.name)
- console.log(props.list)
- const state = reactive<IState>({
- name: 'vue 3.0 組件',
- count: 0,
- list: [
- {
- name: 'vue',
- id: 1
- },
- {
- name: 'vuex',
- id: 2
- }
- ]
- })
- const a = computed(() => state.name)
- onMounted(() => {
- })
- function handleClick () {
- state.count ++
- // 調(diào)用父組件的方法
- context.emit('emits-name', state.count)
- }
- return {
- ...toRefs(state),
- handleClick
- }
- }
- });
- </script>
- <template>
- <div class="mine" ref="elmRefs">
- <span>{{name}}</span>
- <br>
- <span>{{count}}</span>
- <div>
- <button @click="handleClick">測試按鈕</button>
- </div>
- <ul>
- <li v-for="item in list" :key="item.id">{{item.name}}</li>
- </ul>
- </div>
- </template>
- <script lang="ts">
- import { computed, defineComponent, getCurrentInstance, onMounted, PropType, reactive, ref, toRefs } from 'vue';
- interface IState {
- count: 0,
- name: string,
- list: Array<object>
- }
- export default defineComponent({
- name: 'demo',
- // 父組件傳子組件參數(shù)
- props: {
- name: {
- type: String as PropType<null | ''>,
- default: 'vue3.x'
- },
- list: {
- type: Array as PropType<object[]>,
- default: () => []
- }
- },
- components: {
- /// TODO 組件注冊
- },
- emits: ["emits-name"], // 為了提示作用
- setup (props, context) {
- console.log(props.name)
- console.log(props.list)
- const state = reactive<IState>({
- name: 'vue 3.0 組件',
- count: 0,
- list: [
- {
- name: 'vue',
- id: 1
- },
- {
- name: 'vuex',
- id: 2
- }
- ]
- })
- const a = computed(() => state.name)
- onMounted(() => {
- })
- function handleClick () {
- state.count ++
- // 調(diào)用父組件的方法
- context.emit('emits-name', state.count)
- }
- return {
- ...toRefs(state),
- handleClick
- }
- }
- });
- </script>
vue 3 的生態(tài)
- 官網(wǎng)
- 源碼
- vite 構(gòu)建器
- 腳手架:https://cli.vuejs.org/
- vue-router-next
- vuex4.0
UI 組件庫
- vant2.x
- Ant Design of Vue 2.x
- element-plus
