Topic:Vue3的設計與改變
Vue3發展歷程
- 2018/09/30:尤雨溪在medium個人博客上發布了 Vue 3.0 的開發路線
- 2019年初:采用了RFC(征求意見)流程
- 2019/10/05:Vue 3 源碼開放(pre-alpha狀態)
- 2020/04/17:Vue 3.0 beta
- 2020/05/28:發表文章「Vue 3 設計過程」
- 2020/07/18:Vue 3.0 RC
- 2020/09/18:Vue 3.0 “One Piece"
Vue3的核心設計與提升
響應式核心原理:Object.defineProperty 切換到 Proxy
- 可檢測到新的屬性添加
- 支持 Map、Set、WeakMap 和 WeakSet
- 提供更好的性能
- Proxy 作為瀏覽器的新標準,性能上是一定會得到廠商的大力優化的
- 不需要像Vue2那樣遞歸對所有的子數據進行響應式定義,而是再獲取到深層數據的時候再去利用reactive進一步定義響應式,這對于大量數據的初始化場景來說收益會非常大
Vue Composition API
- 旨在解決大規模應用場景中的痛點
- 更靈活的邏輯組織模式(如邏輯提取與復用)
- 更為可靠的類型推斷能力
Vue3代碼庫完全使用typescript編寫
- 已支持對模板表達式和 props 的類型檢查
- 已全面支持 TSX
內部模塊解耦(采用monorepo)
模塊之間的依賴關系更加明確, 降低項目貢獻壁壘并提高其長期可維護性
內部包有自己的單獨API,類型定義和測試
解鎖高級用法
編譯器支持自定義AST轉換,用于在構建時自定義(如,在構建時進行i18n操作)
核心運行時提供了一系列 API,用于針對不同渲染目標(如native moile、WebGL或終端)的自定義容器。默認的 DOM 渲染器也使用這系列 API 構建。
@vue/reactivity 模塊——Vue響應式系統;可作為獨立包進行使用。也可以與其他模塊解決方案配對使用(如 lit-html),甚至是在非 UI 場景中使用。
性能提升
- bundle包大小方面(tree-shaking 減少了 41% 的體積)
- 初始渲染速度方面(快了 55%)
- 更新速度方面(快了 133%)
- 內存占用方面(減少了 54%)
實驗特性
-
<script setup>
在 SFC 內使用 Composition API 的語法糖
<template>
<button @click="inc">{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue'
export const count = ref(0)
export const inc = () => count.value++
</script>
-
<style vars>
:在 SFC 中支持將狀態作為 CSS 變量注入到樣式中 -
<Suspense>
組件 - 異步組件或帶有 async setup() 組件
SFC: Singe-File Components(單文件組件,.vue文件)
Vue3的性能優化
減小包的尺寸:
- 將大多數全局API和內部幫助程序移至ES模塊導出(如
@vue/reactivity
、@vue/runtime-core
等) - 利用Tree Shaking,只打包需要使用的代碼
渲染策略優化
vue渲染策略:
- HTML的模板 => (編譯) => 虛擬DOM樹的渲染函數
- 通過遞歸遍歷兩個虛擬DOM樹,并比較每個節點上的每個屬性來確定實際DOM的哪些部分需要更新
瓶頸:
任何一點改變卻仍然需要遞歸整個虛擬DOM樹,以了解發生了什么變化,尤其在查看包含大量靜態內容且只有少量動態綁定(整個虛擬DOM)的模板時,效率低下。
最終目的:
克服虛擬DOM的瓶頸,最好的方法是消除不必要的虛擬DOM樹遍歷和屬性比較
優化工作:
編譯器分析模板并生成帶有優化提示的代碼,而運行時將拾取提示,并在可能的情況下采用快速路徑:
將模版分為動態的和靜態的
“塊”
,對動態塊
進行了扁平化處理
,減少運行時的遍歷開銷:在樹的層面上,我們注意到,節點結構在沒有模板指令的時候是完全靜態的(例如,v-if和v-for)。如果我們將模板分為動態的和靜態的“塊”,每個塊內的節點結構再次變得完全靜態。當我們更新一個塊內的節點時,我們不再需要遞歸遍歷樹,因為我們可以在平面數組中跟蹤該塊內的動態綁定。通過將我們需要執行的樹遍歷量減少一個數量級,從而節約了虛擬DOM的大部分開銷。靜態樹提升與靜態prop提升:編譯器會主動檢測模板中的靜態節點,子樹甚至數據對象,并將其提升到生成代碼中的render函數之外。這樣可以避免在每個渲染上重新創建這些對象,從而大大提高了內存使用率并減少了垃圾回收的頻率。
生成編譯器的優化提示:在元素級別,編譯器還會根據需要執行的更新類型為具有動態綁定的每個元素生成一個優化標志。例如,具有動態類綁定和許多靜態屬性的元素將收到一個標志,指示僅需要進行類檢查。運行時將獲取這些提示并采用專用的快速路徑。
響應式原理:Proxy
Vue會使用帶有getter和setter的處理程序遍歷其所有property并將其轉換為Proxy;
這個Proxy使Vue能夠在property被訪問或修改時執行依賴項跟蹤和更改通知。
const handler = {
get(target, prop, receiver) {
// 追蹤函數:依賴追蹤
track(target, prop)
const value = Reflect.get(...arguments)
if (isObject(value)) {
return reactive(value)
} else {
return value
}
},
set(target, key, value, receiver) {
// 觸發函數:更改通知
trigger(target, key)
return Reflect.set(...arguments)
}
}
// 簡化版
function track(target: object, type: TrackOpTypes, key: unknown) {
const depsMap = targetMap.get(target);
// 收集依賴時 通過 key 建立一個 set
let dep = new Set()
targetMap.set(ITERATE_KEY, dep)
// effect可以先理解為更新函數,存放在 dep 里
dep.add(effect)
}
// 簡化版
function trigger(target: object, type: TriggerOpTypes, key?: unknown,) {
// 是通過key找到所有更新函數 依次執行
const dep = targetMap.get(target)
dep.get(key).forEach(effect => effect())
}
Vue Composition API(簡稱VCA)
什么是Composition API
Composition API是對于現有Option API的補充,構建于響應式API基礎之上
setup函數
新的組件選項,VCA的入口點
export default {
props: {
title: String
},
setup(props, context) {
console.log(props.title)
// Attribute (非響應式對象)
console.log(context.attrs)
// 插槽 (非響應式對象)
console.log(context.slots)
// 觸發事件 (方法)
console.log(context.emit)
return {}
}
}
- 執行時機:在生命周期鉤子beforeCreate鉤子之前被調用,且執行一次
- 參數:第一個參數:props,第二個參數context(屬性有attrs、slots與emit等)
- 返回一個對象:返回的對象屬性將會被合并到組件模板的渲染上下文(this)
注意點:
- 參數props不能解構,會失去響應性
- this在 setup() 中不可用
- 在setup函數中,不能訪問組件選項data、computed與methods
響應式API
ref
接受一個參數值并返回一個響應式且可改變的 ref 對象,通過.value獲取屬性值
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
reactive
接收一個普通對象然后返回該普通對象的響應式代理。等同于 2.x 的 Vue.observable()
const state = reactive({ count: 0 })
computed
傳入一個 getter 函數,返回一個默認不可手動修改的 ref 對象。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 錯誤!
或者傳入一個擁有 get 和 set 函數的對象,創建一個可手動修改的計算狀態。
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
},
})
plusOne.value = 1
console.log(count.value) // 0
readonly
傳入一個對象(響應式或普通)或 ref,返回一個原始對象的只讀代理
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依賴追蹤
console.log(copy.count)
})
// original 上的修改會觸發 copy 上的偵聽
original.count++
// 無法修改 copy 并會被警告
copy.count++ // warning!
watchEffect
立即執行傳入的一個函數,并響應式追蹤其依賴,并在其依賴變更時重新運行該函數
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 打印出 0
setTimeout(() => {
count.value++
// -> 打印出 1
}, 100)
停止偵聽
const stop = watchEffect(() => {
/* ... */
})
// 之后
stop()
清除副作用
watchEffect((onInvalidate) => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id 改變時 或 停止偵聽時
// 取消之前的異步操作
token.cancel()
})
})
watch
watch API 完全等效于 2.x this.$watch
偵聽單個數據
const count = ref(0)
// 偵聽單個數據
watch(count, (count, prevCount) => {
/* ... */
})
// 偵聽多個數據
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
<template>
<button @click="increase">increase</button><span> count is: {{count}}</span>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, readonly, ref, toRefs, watch, watchEffect } from 'vue'
export default defineComponent({
setup() {
// 響應式對象
const count = ref(0)
const state = reactive({
count,
// 響應式計算對象
double: computed(() => count.value)
})
// 與vue2.x的watch選項等效:監聽響應式對象count
watch(count, (count, prevCount) => {
/** do something */
})
// 副作用函數:監聽響應式對象count
const stop = watchEffect(() => console.log(count.value))
if (count.value > 5) {
// 停止偵聽
stop()
}
// 只讀響應式對象
const copy = readonly(count)
// 無法修改 copy 并會被警告
copy.value++ // warning!
const increase = () => count.value++
// ES6解構,注意;需通過toRefs轉換為響應式對象,否則property的響應式都會丟失
const { double } = toRefs(state)
return {
count,
double,
increase
}
},
})
</script>
生命周期鉤子
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
新增鉤子(用于調試)
onRenderTracked
onRenderTriggered
import {
defineComponent, ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered
} from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
// 在掛載開始之前被調用調用
onBeforeMount(() => {})
// 在掛載后被調用調用
onMounted(() => {})
// 組件更新前調用
onBeforeUpdate(() => {})
// 組件更新后調用
onUpdated(() => {})
// 組件卸載前調用
onBeforeUnmount(() => {})
// 組件卸載后調用
onUnmounted(() => {})
// 當捕獲一個來自子孫組件的錯誤時被調用
onErrorCaptured(() => {})
onRenderTracked((DebuggerEvent) => {
debugger
// 檢查哪個依賴性被組件追蹤
})
onRenderTriggered((DebuggerEvent) => {
debugger
// 檢查哪個依賴性導致組件重新渲染
})
return { count }
},
})
設計動機
更友好的類型推斷(defineComponent結合typescript)
vue2.x對typescript的類型推斷不友好:
- vue2.x通過this來暴露property,所有選項(如methords)的this都是指向組件實例,而不是指向mehordes對象。
- vue2.x使用vue-class-component將組件編寫為TypeScript class (借助 decorator,不穩定的 stage 2 提,存在不確定性)
2. 實現邏輯提取與復用
// 追蹤鼠標位置的例子
import { ref, onMounted, onUnmounted } from 'vue'
export default function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update)})
return { x, y }
}
import { useMousePosition } from './useMousePosition'
export default {
setup() {
const { x, y } = useMousePosition()
// 其他邏輯...
return { x, y }
},
}
-
更加靈活的代碼組織模式,不需要總是通過選項來組織代碼:
Option API:通過選項(如methords、computed、watch等)來組織代碼,邏輯關注點分散,這種碎片化使得復雜的組件難以維護
Composition API:通過邏輯來組織代碼,邏輯關注點內聚,便于復用與維護
image.png
對比Mixins、高階組件和無渲染組件
弊端:
- 渲染上下文中暴露的 property 來源不清晰。例如在閱讀一個運用了多個 mixin 的模板時,很難看出某個 property 是從哪一個 mixin 中注入的。
- 命名空間沖突。Mixin 之間的 property 和方法可能有沖突,同時高階組件也可能和預期的 prop 有命名沖突。
- 性能方面,高階組件和無渲染組件需要額外的有狀態的組件實例,從而使得性能有所損耗。
相比而言,組合式 API:
- 暴露給模板的 property 來源十分清晰,因為它們都是被組合邏輯函數返回的值。
- 不存在命名空間沖突,可以通過解構任意命名
- 不再需要僅為邏輯復用而創建的組件實例。
缺點與槽點
缺點
- 新概念ref的心智負擔:
- 不斷地區分響應式引用、基礎類型值和對象;
- 讀寫ref的操作更冗余,需要訪問.value;
- 需要理解ref和reactive的區別和使用場景。
- setup()返回語句可能會冗長
槽點
- 所有邏輯都堆在setup函數實現,代碼可能會過于臃腫
- 擔心VCA會讓沒有經驗的人編寫出意大利面條式代碼(維護性差)——需約束
- 抄襲 React Hook
// 尤雨溪 原話
其實真的用過并且懂 React hooks 的人看到這個都會意識到 Vue Composition API (VCA)跟 hooks 本質上的區別。VCA 在實現上也其實只是把 Vue 本身就有的響應式系統更顯式地暴露出來而已。真要說像的話,VCA 跟 MobX 還更像一點。
但對于不懂 React hooks 的人來說,長的像就是一樣了,懶得解釋。
關于Class API的提議 (opens new window)[Abandoned]
首先,使用Class API(裝飾器方案)來支持更好的類型推斷;但Class API的合理性存疑
不確定性:裝飾器對第二階段規范的依賴,存在很多不確定性,尤其是當TypeScript的當前實現與TC39提案完全不同步
props的類型推斷
- 尷尬的雙重聲明:
interface Props {
message: string
}
class App extends Component<Props> {
@prop message: string
}
- 無法將裝飾器聲明的props類型暴露給this.$props,這會破壞TSX的支持
- 關于"reserved" methods的命名空間:解決方案不完美
- 實現復雜:
- 涉及許多邊緣情況,需要引入其他內部代碼;
- 為了使用Proxy this做響應式追蹤,this在構造器中的實現會與在其他的地方不同;
- 更多代碼:包含類組件與對象組件的申明的轉換代碼
- 收益不大:除了提供更好的TypeScript集成之外沒有提供任何其他功能。
與React Hook對比
React Hook設計動機:
- 在組件之間復用狀態邏輯很難:復用不同組件之間的狀態邏輯
- 復雜組件變得難以理解(每個生命周期常常包含一些不相關的邏輯,且邏輯分散)
- 難以理解的 class
- 理解this的工作方式;
- 不能忘記綁定事件處理器;
- class 組件會無意中鼓勵開發者使用一些讓優化措施無效的方案:不好壓縮、熱重載不穩定。
Vue Composition API與React Hook的用法對比
// Vue Composition API
import { ref, onMounted, onUnmounted } from 'vue'
export default function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update)})
return { x, y }
}
// React Hook
import { useState, useEffect } from 'react';
export default function usePosition() {
const [ position, setPosition ] = useState({ x: 0, y: 0 })
function update(e) {
setPosition({
x: e.pageX,
y: e.pageY
})
}
useEffect(() => {
window.addEventListener('mousemove', update)
return () => {
window.removeEventListener('mousemove', update)
}
}, [])
return position
}
React Hook的規則(限制,增加心智負擔)
- 不要在循環,條件或嵌套函數中調用Hook;
- 只能React函數的最頂層調用(
遵守這條規則,你就能確保Hook在每一次渲染中都按照同樣的順序被調用。這讓React能夠在多次的useState和useEffect調用之間保持hook狀態的正確
); - 只能在React的函數組件中調用Hook。
Vue Composition API的優勢
- 與 React Hook不同,setup 函數僅被調用一次,這在性能上比較占優。
- 對調用順序沒什么要求,每次渲染中不會反復調用Hook函數,產生的的 GC 壓力較小。
- 不必考慮總是需要useCallback的問題,以防止傳遞函數prop給子組件的引用變化,導致無必要的重新渲染。
- React Hook 里的「依賴」是需要你去手動聲明的;Vue會自動追蹤依賴。
- React Hook有臭名昭著的閉包陷阱問題,如果用戶忘記傳遞正確的依賴項數組,useEffect 和 useMemo 可能會捕獲過時的變量,這不受此問題的影響。Vue 的自動依賴關系跟蹤確保觀察者和計算值始終正確無誤。
// React Demo
import React, { useState, useEffect, useCallback } from 'react';
// state變化時,重新執行
function Example() {
const [ count, setCount ] = useState(0)
const [ val, setVal ] = useState('')
// count 或 val變化時都會執行
useEffect(() => console.log('count -', count, 'val -', val))
// 申明依賴count:只有count變化時執行,val變化時不執行
useEffect(() => console.log('count -', count), [ count ])
// 執行一次
useEffect(() => {
console.log('mounted!')
return () => console.log('unmounted!')
}, [])
// count變化時,updateVal的引用不變,子組件Child不會重新渲染
const updateVal = useCallback((val) => setVal(val), [ val ])
// count變化時,updateVal2的引用改變,子組件Child重新渲染
const updateVal2 = (val) => setVal(val)
return (
<div>
<button onClick={() => setCount(count + 1)}>Increase</button>
count is {count}
<Child val={val} updateVal={updateVal} />
<Child val={val} updateVal={updateVal2} />
</div>
);
}
<template>
<button @click="increase">increase</button><span> count is: {{count}}</span>
<input type="text" v-model="val">
<!-- 只有val變化時, HelloWorld組建更新,但不會重載 -->
<HelloWorld :msg="val" />
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref, watchEffect } from 'vue'
import HelloWorld from './HelloWorld.vue'
export default defineComponent({
components: { HelloWorld },
// 只執行一次
setup() {
const count = ref(0)
const val = ref('')
// 只有count變化才執行
watchEffect(() => console.log('count -', count.value))
// 只有val變化才執行
watchEffect(() => console.log('val -', val.value))
// 組建掛載時執行
onMounted(() => console.log('<mounted>'))
// 組件卸載時執行
onUnmounted(() => console.log('<unmount>'))
const increase = () => count.value += 1
return {
val, count,
increase
}
},
})
</script>
如何在Vue2中使用Composition API
# 安裝插件
npm install @vue/composition-api
// 入口文件中
import VueCompositionAPI from '@vue/composition-api'
// 掛載Composition API
Vue.use(VueCompositionAPI)
// vue組件
import { computed, ref, reactive, watchEffect, onMounted } from '@vue/composition-api'
export default {
setup(props, ctx) {
// 創建響應式數據對象
const count = ref(0)
const state = reactive({
count
double: computed(() => count * 2)
})
watchEffect(() => console.log(state.count))
onMounted(() => console.log('mounted'))
return {
state,
increment() { state.count += 1 }
}
}
}
注意點:
- Vue2與Vue3響應式對象的核心原理不同;vue2數據觀測仍存在缺陷:
- 無法檢測屬性的添加和刪除;
- 無法檢測數組索引和長度的變更
- 隨著提案的更新,它也可能會做一些不兼容的變更,所以我們不建議這個階段在生產環境中使用它。
相關鏈接
兼容性
與Vue2兼容,但有重大更改與小改變:
全局API
全局API已更改為使用應用程序實例
2.x 全局 API | 3.x 實例 API (app) |
---|---|
Vue.config | app.config |
Vue.config.productionTip | removed |
Vue.config.ignoredElements | app.config.isCustomElement |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
// vue2
import Vue from 'vue'
import App from './App.vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
// vue3
import { createApp } from 'vue'
import App from './App.vue'
import HelloWorld from './components/HelloWorld.vue';
import Counter from './components/Counter.vue';
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [ ... ]
})
const app = createApp(App)
app.use(router)
app.mount('#app')
全局和內部 API 已經被重構為可 tree-shakable:
Vue 2.x 中的這些全局 API 受此更改的影響:
Vue.nextTick
Vue.observable (用 Vue.reactive 替換)
Vue.version
Vue.compile (僅全構建)
Vue.set (僅兼容構建)
Vue.delete (僅兼容構建)
// vue2
import Vue from 'vue'
Vue.nextTick(() => {
// 一些和DOM有關的東西
})
// vue3
import { nextTick } from 'vue'
nextTick(() => {
// 一些和DOM有關的東西
})
模版指令
- 組件上 v-model 用法已更改
-
<template v-for>
和非 - v-for 節點上 key 用法已更改 - 在同一元素上使用的 v-if 和 v-for優先級已更改(v-if優先)
- v-bind="object" 現在排序敏感
- v-for 中的 ref 不再注冊 ref 數組
組件
- 只能使用普通函數創建功能組件
- functional 屬性在單文件組件 (SFC)
<template>
和 functional 組件選項被拋棄 - 異步組件現在需要 defineAsyncComponent 方法來創建
渲染函數
- 渲染函數 API 改變
-
slots 作為函數暴露
- 自定義指令 API 已更改為與組件生命周期一致
- 一些轉換 class 被重命名了:
- v-enter -> v-enter-from
- v-leave -> v-leave-from
- 組件 watch 選項和實例方法 $watch 不再支持點分隔字符串路徑,請改用計算函數作為參數
- 在 Vue 2.x 中,應用根容器的 outerHTML 將替換為根組件模板 (如果根組件沒有模板/渲染選項,則最終編譯為模板)。VUE3.x 現在使用應用程序容器的 innerHTML。
移除API
- keyCode 支持作為 v-on 的修飾符
-
off 和 $once 實例方法
- 過濾
- 內聯模板 attribute
- $destroy 實例方法。用戶不應再手動管理單個 Vue 組件的生命周期。
腳手架
vite
- 一個基于瀏覽器原生 ES imports 的開發服務器。利用瀏覽器去解析
<script module>
,在服務器端按需編譯返回,完全跳過了打包這個概念,服務器隨起隨用(冷啟動快) - 不僅有 Vue 文件支持,還搞定了熱更新,而且熱更新的速度不會隨著模塊增多而變慢(即時的模塊熱更新)。
- 針對生產環境則可以把同一份代碼用 rollup 打包(按需編譯)。
缺點: 支持轉譯ts文件,但不執行類型檢查
<!--Vite App入口文件-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
createApp(App).mount('#app')
# 創建Vite App項目
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
vue-cli
一個基于 Vue.js 進行快速開發的完整系統
# 安裝
npm install -g @vue/cli
# 創建vue3項目
vue create <project-name>
# 選擇`vue3 preview`
? Please pick a preset: (Use arrow keys)
? Default ([Vue 2] babel, eslint)
Default (Vue 3 Preview) ([Vue 3] babel, eslint)
Manually select features
# 添加typescript插件
vue add typescript
Vue插件
Vue-router
安裝
npm install vue-router@next --save
Vue3中的使用方式
// 1. Define route components.
const Home = { template: '<div>Home</div>' }
const About = { template: '<div>About</div>' }
// 2. Define some routes
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
]
// 3. Create the router instance and pass the `routes` option
const router = VueRouter.createRouter({
// 4. Provide the history implementation to use.
history: VueRouter.createWebHashHistory(),
routes,
})
// 5. Create and mount the root instance.
const app = Vue.createApp({})
app.use(router)
app.mount('#app')
Vuex
安裝
npm install vuex@next --save
vue3中的使用方式
import { createApp } from 'vue'
import { createStore } from 'vuex'
const app = createApp({ ... })
const store = createStore({ ... })
app.use(store)