原文: 本人掘金文章
關注公眾號: 微信搜索 前端工具人
; 收貨更多的干貨
一、 官方中文文檔鏈接
https://vue3js.cn/docs/zh/
二、開篇
- vue3.0 從去年預熱到9月18號晚上,已正式發布
vue3.0 beta
版本; beta
版意味着vue3.0
開業正式投入到項目中了;大家可以開心的學習了(前端新技術你繼續出,我還學得動...因為要生活要吃飯!!!);- 前端技術生態一直不斷的更新換代,許多人都覺得亞歷山大,學不動了;但不是學不動就可以不學了么。。。
0.0
;學不動也得學,不然適應自己的只會是淘汰
三、vue2.0 項目的建議
引用官方文檔作者的話:
提示:
我們仍在開發 Vue 3 的專用遷移版本,該版本的行為與 Vue 2 兼容,運行時警告不兼容。如果你計划遷移一個非常重要的 Vue 2 應用程序,我們強烈建議你等待遷移版本完成以獲得更流暢的體驗
。
目前作者的意思是:對於 vue2.0
的項目強烈不建議
升到 vue3.0
;因為目前的beta
版本以及現有的框架及插件,不是很支持和兼容vue3.0
語法; 所以肯定有很多預想不到的問題;對於躍躍欲試升級vue3.0
的小伙伴們,只能等待官方的兼容版本開發完,在做遷移; 畢竟線上項目不是開玩笑的,出了一個bug
都可能是重大損失。 這個鍋家里沒礦的基本背不動...
四、介紹
Vue3帶來些什么? 參考至: 公眾號:前端早讀課文章
詳細文檔請參考: 官方中文文檔鏈接
- 更快
- 重構了
Virtual DOM
- 標記靜態內容,並區分動態內容
- 更新時只
diff
動態的部分
- 重構了
雙向數據綁定
Object.defineProperty() --> Proxy API
Proxy
對於復雜的數據結構減少了循環遞歸的監聽;初始渲染循環遞歸是非常耗性能的;Proxy
對於數組的變異方法(會修改原數組),不在需要單獨用數組原生方法重寫、處理- 語法也比
defineProperty
簡潔多了,直接監聽某個屬性即可;
- 事件緩存
vue2
中,針對綁定事件,每次觸發都要重新生成全新的function
去更新;Vue3
中,提供了事件緩存對象cacheHandlers
,當cacheHandlers
開啟的時候,編譯會自動生成一個內聯函數,將其變成一個靜態節點,這樣當事件再次觸發時,就無需重新創建函數直接調用緩存的事件回調方法即可
- 重構了
- 更小 (
Tree shaking
支持)- 簡而言之: 不會把所有的都打包進來,只會打包你用到的
api
;大項目你會發現熱加載、初始渲染提升了很多 - 很大程度的減少了開發中的冗余代碼,提升編譯速度
- 簡而言之: 不會把所有的都打包進來,只會打包你用到的
- 更易於維護
Vue3
從Flow
遷移到TypeScript
- 多人協同開發的情況下,用了
TypeScript
之后的酸爽你會吐槽,為什么早不出現TypeScript
- 多人協同開發的情況下,用了
- 代碼目錄結構遵循monorepo
- 核心觀點: 代碼分割到一個個小的模塊中, 開發者大部分只是工作在少數的幾個文件夾,並且也只會編譯自己負責的模塊;而不是整個項目編譯
- 新功能和特性
Composition API
- 不要在意越來越像react-hook;畢竟別人的優點是值得自己學習的;
Composition API
函數式開發,很大程度的提高組件、業務邏輯的復用性;高度解耦;提升代碼質量、開發效率;減少代碼體積
- 提升開發效率
vite
的支持 (當然目前來說vite
功能還不夠強大和穩定, 但尤大把它作為vue3
官方構建工具,那肯定尤大會完善它的; 可自由選擇webpack
還是vite
)vite
在開發環境下基於瀏覽器原生ES imports
開發,在生產環境下基於Rollup
打包- 快速的冷啟動
- 即時的模塊熱更新
- 真正的按需編譯
vue2.0
相信很多小伙伴都是結合webpack
開發; 但是有沒有發現初期項目小的時候很爽運行、編譯、熱加載都很快; 項目一大... 打包、運行、改個功能熱加載的時候... 我們先去上個廁所/接個水;忙的時候挺煩這環節vue3.0
結合vite
作者的介紹是不跟項目體積龐大而影響,開始啥樣現在也啥樣; 當然誇張是誇張了點, 但是相差應該不大;
五、 環境搭建
// 對於 Vue 3,應該 npm 上可用的 Vue CLI v4.5 作為 @vue/cli@next
yarn global add @vue/cli@next
# OR
npm install -g @vue/cli@next
// 創建項目
npm init vite-app <project-name>
# OR
yarn create vite-app <project-name>
// 下載依賴及運行項目, 已 npm 方式為例; 詳細步驟官方文檔的安裝頁都有
cd <project-name>
npm install
npm run dev // 項目就能跑起來並且訪問了
拓展: 項目引入其他插件比如 vue-router4.0、vuex4.0、typescript等請參考 Vue3.0環境搭建
六、 語法介紹
上手之前應該先閱讀一遍 vue3 對於 vue2 的一些變更; 詳情點擊 官網文檔重大變更
vue2
中使用的是Options API
; vue3
中的是Composition API
簡稱純函數式API
6.1、 setup
vue3
組件入口為 setup(){}
函數作為入口, 默認只執行一次;執行順序在 beforeCreate
之后 created
之前;
...
// 使用props和this
setup (props, ctx) {
// props 組件間傳遞的參數;
// ctx 組件的實例的執行上下文(可以理解為 vue2 this)
/* 可執行 下面等操作:例 ctx.$emit()
attrs: Object
emit: ƒ ()
listeners: Object
parent: VueComponent
refs: Object
root: Vue
*/
// 注意 steup 中沒有this了, 拿不到this
}
6.2、 生命周期
我記得早期是說 vue3
中是移除掉了 beforeCreate
、created
兩個生命周期; 但是實踐的時候我發現還是可以寫的; 因為vue2
、 vue3
寫法目前相兼容;
created () { console.log('created') }
setup (props, ctx) {
console.log('setup')
// mounted 新寫法 記住一句話 所有的方式都是以函數的形式呈現
onMounted(() => {})
}
mounted () { console.log('mounted') }
// 執行順序 setup created mounted
雖然兼容但盡量不要這樣寫;向前看齊嘛; 強烈推薦全部都放在steup
函數中
6.3、 reactive、ref、tofefs、isRef
創建響應式對象 reactive、ref、tofefs
用法, 對應 vue2
中的 data
推薦寫法3
// 寫法一:響應式數據一多, return 要很多次; 使用數據的時候要通過state拿到
<template>
<div>
<p>{{state.count}}</p>
</div>
</template>
import {reactive} from 'vue'
...
setup(props, ctx) {
const state = reactive({
count: 0
})
return { state }
}
// 寫法二
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive} from 'vue'
...
setup(props, ctx) {
const state = reactive({
count: 0
})
return {
count: state.count
}
}
// 寫法三:推薦 通過 toRefs 代理對象, 再通過解構的方式取值
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs} from 'vue'
...
setup(props, ctx) {
const state = reactive({
count: 0
})
return {
...toRefs(state)
}
}
// 寫法四:通過 ref() 函數包裝, 返回值是一個對象,對象上只包含一個 value 屬性, 就是要的屬性值
<template>
<div>
<p>{{count}}</p>
<p>{{count1}}</p>
</div>
</template>
import {reactive, toRefs, ref} from 'vue'
...
setup(props, ctx) {
// 父組件傳遞count屬性
// 寫法1
const count = ref(props.count)
console.log(count.value) // 對應props.count的值
// 寫法2
const state = reactive({
count1: ref(props.count)
})
return {
count,
...toRefs(state)
}
}
// isRef 來判斷某個值是否為 ref() 創建出來的對象
import { ref, isRef } from 'vue';
export default {
setup(props, ctx) {
const refCount = ref(0)
const count = isRef(refCount) ? refCount : 1
}
};
6.4、 computed
例子場景:結合 vue-router
根據當前路勁為count
賦值, 也擴展下vue-router
的用法
<template>
<div>
<p>{{count}}</p>
<p>{{count1}}</p>
</div>
</template>
import {reactive, toRefs, computed} from 'vue'
import {useRoute} from 'vue-router'
...
setup(props, ctx) {
const route = useRoute()
const state = reactive({
// 計算屬性 寫法1
count: computed(() => {
return route.path
})
})
// 計算屬性 寫法2
const count1 = computed(() => {
return route.path
})
return {
...toRefs(state),
// 計算屬性不需要通過 toRefs 結構, 因為他就是一個具體的值就是響應式的
count1
}
}
6.5、watch 、watchEffect
例子場景:同 computed 一樣
watchEffect
與 watch
有什么不同:
watchEffect
不需要指定監聽的屬性,他會自動的收集依賴, 只要我們回調中引用到了 響應式的屬性, 那么當這些屬性變更的時候,這個回調都會執行watch
只能監聽指定的屬性watch
可以獲取到新值與舊值,而watchEffect
不行watchEffect
在組件初始化的時候就會執行一次用以收集依賴(與computed
同理),后續收集的依賴發生變化,這個回調才會再次執行
// watch 用法 監聽單個屬性
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs, watch} from 'vue'
import {useRoute} from 'vue-router'
...
setup(props, ctx) {
const route = useRoute()
const state = reactive({
count: 0,
})
// 監聽路由路勁, immediate 是否立即執行一次
watch(() => route.path, (newValue) => {
state.count = newValue
}, { immediate: true })
return {
...toRefs(state),
}
}
// watch 用法 監聽ref數據源
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs, ref, watch} from 'vue'
...
setup(props, ctx) {
// 定義數據源
let count = ref(0);
// 指定要監視的數據源
watch(count, (count, prevCount) => {
console.log(count, prevCount)
})
setInterval(() => {
count.value += 2
}, 2000)
console.log(count.value)
return {
count
}
}
// watch 用法 監聽多個屬性
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs, watch} from 'vue'
...
setup(props, ctx) {
const state = reactive({
name: 'vue',
age: 3
})
watch(
// 監聽name、 age
[() => state.name, () => state.age],
// 如果屬性改變、則執行以下回調
([newName, newAge], [oldname, oldAge]) => {
console.log(oldname, oldname)
console.log(oldAge, oldAge)
},
{ lazy: true} // 在 watch 被創建的時候,不執行回調函數中的代碼
)
setTimeout(() => {
state.name = 'react'
state.age += 1
}, 3000)
return {
...toRefs(state),
}
}
// watchEffect 用法
<template>
<div>
<p>{{count}}</p>
</div>
</template>
import {reactive, toRefs, ref, watchEffect} from 'vue'
import {useRoute} from 'vue-router'
...
setup(props, ctx) {
const route = useRoute()
const state = reactive({
count: 0,
})
// 當 route.path 變化時就會執行打印, 有點類似 react-hook 的 useEffect 第二個參數效果
watchEffect(() => {
count = route.path
console.log(route.path)
})
// watchEffect、 watch 都可以主動停止監聽
const stop = watchEffect(() => {
count = route.path
console.log(route.path)
})
// 在某個時機下 執行 stop() 停止watchEffect監聽
if (...) { stop() }
return {
...toRefs(state),
}
}
七、 Vue3中移除的一些API和方法
7.1 取消KeyboardEvent.keyCode
在Vue2.x
中,綁定鍵盤事件會用到如下代碼:
<!-- keyCode version -->
<input v-on:keyup.13="submit" />
<!-- alias version -->
<input v-on:keyup.enter="submit" />
或者是:
Vue.config.keyCodes = {
f1: 112
}
<!-- keyCode version -->
<input v-on:keyup.112="showHelpText" />
<!-- custom alias version -->
<input v-on:keyup.f1="showHelpText" />
在事件中,給keyup
配置一個指定按鈕的keyCode
(數字)在Vue3
中將不會生效,但是依然可以使用別名,例如:
<input v-on:keyup.delete="confirmDelete" />
7.2 移除 $on,$off 和 $once方法
在Vue2.x
中可以通過EventBus
的方法來實現組件通信:
var EventBus = new Vue()
Vue.prototype.$EventBus = EventBus
...
this.$EventBus.$on() this.$EventBus.$emit()
這種用法在Vue3
中就不行了,在Vue3
中移除了 $on,$off
等方法(參考rfc),而是推薦使用mitt
方案來代替:
import mitt from 'mitt'
const emitter = mitt()
// listen to an event
emitter.on('foo', e => console.log('foo', e) )
// fire an event
emitter.emit('foo', { a: 'b' })
7.3 移除filters
在Vue3
中,移除了組件的filters
項,可以使用methods
的或者computed
來進行替代:
<template>
<p>{{ accountBalance | currencyUSD }}</p>
</template>
<script>
export default {
filters: {
currencyUSD(value) {
return '$' + value
}
}
}
</script>
替換為:
<template>
<p>{{ accountInUSD }}</p>
</template>
<script>
export default {
props: {
accountBalance: {
type: Number,
required: true
}
},
computed: {
accountInUSD() {
return '$' + this.accountBalance
}
}
}
</script>
八、Vue3中改變的API和寫法
8.1 實例初始化
在vue2.x
中通過new Vue()
的方法來初始化:
import App from './App.vue'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
在vue3
中Vue
不再是一個構造函數,通過createApp
方法初始化:
import App from './App.vue'
createApp(App).use(store).mount('#app')
8.2 全局API調用方式改變
在Vue2.x
中,大部分全局API
都是通過Vue.xxx
或者Vue.abc()
方式調用,例如:
import Vue from 'vue'
Vue.mixin()
Vue.use()
而在Vue3
中,這些方式將會改變,取而代之的是如下:
import { createApp } from 'vue'
const app = createApp({})
app.mixin()
app.use()
同時,可以只引入一些需要的API
,不需要的不用引入,這樣也符合Three Shaking
的要求,例如:
import { nextTick,reactive,onMounted } from 'vue'
nextTick(() => {
})
onMounted(() => {
})
由於Vue3
中全局API
都會通過app.xxx
的方法調用,所以之前通過Vue.prototype.xxx
綁定的全局方法和變量將無法使用,可以采用如下方式來代替:
//在main.js中:
app.config.globalProperties.http = function(){}
//在vue組件中:
this.http()
8.3 render方法修改
在Vue2.x
中,有時會自定義render
方法來返回模板內容,如下:
export default {
render(h) {
return h('div')
}
}
在Vue3
中,h
通過vue
來引入,如下:
import { h } from 'vue'
export default {
render() {
return h('div')
}
}
8.4 新的異步組件創建方式
在Vue2.x
中,尤其是在Vue Router
中,會經常使用到異步組件,借助webpack
的打包方式,可以將一個組件的代碼進行異步獲取,例如:
const asyncPage = () => import('./NextPage.vue')
const asyncPage = {
component: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
}
在Vue3
中,提供了defineAsyncComponent()
方法創建異步組件,同時可以返回一個Promise
對象來自己控制加載完成時機,如下:
import { defineAsyncComponent } from 'vue'
const asyncPageWithOptions = defineAsyncComponent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
})
const asyncComponent = defineAsyncComponent(
() =>
new Promise((resolve, reject) => {
/* ... */
})
)
九、暫時性的結尾:
- 寫了大半天,寫的有點啰嗦,新API基本都寫了幾種編碼方式,根據自己的愛好取舍
- 后續慢慢加入自己對一些新API認識及用法
- 本編文章部分內容參考鏈接有:
- 前端早讀課文章: 最全的Vue3.0升級指南
- Vue中文社區文章:一篇文章上手Vue3中新增的API