vue2與vue3的差異(總結)?

zyp_beier發表於2021-11-19

vue作者尤雨溪在開發 vue3.0 的時候開發的一個基於瀏覽器原生 ES imports 的開發伺服器(開發構建工具)。那麼我們先來了解一下vite

Vite

Vite,一個基於瀏覽器原生 ES imports 的開發伺服器。利用瀏覽器去解析 imports,在伺服器端按需編譯返回,完全跳過了打包這個概念,伺服器隨起隨用。同時不僅有 Vue 檔案支援,還搞定了熱更新,而且熱更新的速度不會隨著模組增多而變慢。針對生產環境則可以把同一份程式碼用 rollup 打。雖然現在還比較粗糙,但這個方向我覺得是有潛力的,做得好可以徹底解決改一行程式碼等半天熱更新的問題。它做到了本地快速開發啟動, 用 vite 文件上的介紹,它具有以下特點:

  • 快速的冷啟動,不需要等待打包操作;
  • 即時的熱模組更新,替換效能和模組數量的解耦讓更新飛起;
  • 真正的按需編譯,不再等待整個應用編譯完成;

使用 npm:

# npm 7+,需要加上額外的雙短橫線
$ npm init vite@latest <project-name> -- --template vue

$ cd <project-name>
$ npm install
$ npm run dev

或者 yarn:

$ yarn create vite <project-name> --template vue
$ cd <project-name>
$ yarn
$ yarn dev

概覽

在這裡插入圖片描述

  • 速度更快
  • 體積減少
  • 更易維護
  • 更接近原生
  • 更易使用
  1. 重寫了虛擬Dom實現
    diff演算法優化
<div>
  <span/>
  <span>{{ msg }}</span>
</div>

被編譯成:

import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("span", null, "static"),
    _createVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ]))
}

首先靜態節點進行提升,會提升到 render 函式外面,這樣一來,這個靜態節點永遠只被建立一次,之後直接在 render 函式中使用就行了。
Vue在執行時會生成number(大於0)值的PatchFlag,用作標記,僅帶有PatchFlag標記的節點會被真正追蹤,無論層級巢狀多深,它的動態節點都直接與Block根節點繫結,無需再去遍歷靜態節點,所以處理的資料量減少,效能得到很大的提升。
在這裡插入圖片描述

  1. 事件監聽快取:cacheHandlers
<div>
  <span @click="onClick">
    {{msg}}
  </span>
</div>

優化前:

import { toDisplayString as _toDisplayString, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("span", { onClick: _ctx.onClick }, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["onClick"])
  ]))
}

onClick會被視為PROPS動態繫結,後續替換點選事件時需要進行更新。
優化後:

import { toDisplayString as _toDisplayString, createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("span", {
      onClick: _cache[1] || (_cache[1] = $event => (_ctx.onClick($event)))
    }, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ]))
}

會自動生成一個行內函數,這個行內函數裡面再去引用當前元件最新的onclick,然後把這個行內函數cache起來,第一次渲染的時候會建立行內函數並且快取,後續的更新就直接從快取裡面讀同一個函式,既然是同一個函式就沒有再更新的必要,就變成了一個靜態節點
3. SSR速度提高
當有大量靜態的內容時,這些內容會被當做純字串推進一個buffer裡面,即使存在動態的繫結,會通過模板 插值嵌入進去,這樣會比通過虛擬dom來渲染的快很多。vue3.0 當靜態檔案大到一定量的時候,會用_ceratStaticVNode方法在客戶端去生成一個static node, 這些靜態node,會被直接innerHtml,就不需要建立物件,然後根據物件渲染

  1. tree-shaking
    在這裡插入圖片描述

tree-shakinng 原理
主要依賴es6的模組化的語法,es6模組依賴關係是確定的,和執行時的狀態無關,可以進行可靠的靜態分析,
分析程式流,判斷哪些變數未被使用、引用,進而刪除對應程式碼
前提是所有的東西都必須用ES6 module的import來寫

按照作者的原話解釋,Tree-shaking其實就是:把無用的模組進行“剪枝”,很多沒有用到的API就不會打包到最後的包裡
在Vue2中,全域性 API 如 Vue.nextTick() 是不支援 tree-shake 的,不管它們實際是否被使用,都會被包含在最終的打包產物中。
而Vue3原始碼引入tree shaking特性,將全域性 API 進行分塊。如果你不使用其某些功能,它們將不會包含在你的基礎包中
5. compositon Api
在這裡插入圖片描述

沒有Composition API之前vue相關業務的程式碼需要配置到option的特定的區域,中小型專案是沒有問題的,但是在大型專案中會導致後期的維護性比較複雜,同時程式碼可複用性不高
compositon api提供了以下幾個函式:

  • setup (入口函式,接收兩個引數(props,context))

  • ref (將一個原始資料型別轉換成一個帶有響應式特性)

  • reactive (reactive 用來定義響應式的物件)

  • watchEffect

  • watch

  • computed

  • toRefs (解構響應式物件資料)

  • 生命週期的hooks

    如果用ref處理物件或陣列,內部會自動將物件/陣列轉換為reactive的代理物件
    ref內部:通過給value屬性新增getter/setter來實現對資料的劫持
    reactive內部:通過使用proxy來實現對物件內部所有資料的劫持,並通過Reflect反射操作物件內部資料
    ref的資料操作:在js中使用ref物件.value獲取資料,在模板中可直接使用

import { useRouter } from 'vue-router'
import { reactive, onMounted, toRefs } from 'vue'

// setup在beforeCreate 鉤子之前被呼叫
// setup() 內部,this是undefined,因為 setup() 是在解析其它元件選項之前被呼叫的,所以 setup() 內部的 this 的行為與其它選項中的 this 完全不同。這在和其它選項式 API 一起使用 setup() 時可能會導致混淆
// props 是響應式的,當傳入新的 prop 時,它將被更新(因為props是響應式的,所以不能使用 ES6 解構,因為它會消除 prop 的響應性。)

// props引數:包含元件props配置宣告且傳入了的所有props的物件
// attrs引數:包含沒有在props配置中宣告的屬性物件,相當於this.$attrs
// slots引數:包含所有傳入的插槽內容的物件,相當於this.$slots
// emit引數:可以用來分發一個自定義事件,相當於this.$emit
setup (props, {attrs, slots, emit}) {
  const state = reactive({
    userInfo: {}
  })

  const getUserInfo = async () => {
    state.userInfo = await GET_USER_INFO(props.id)
  }

  onMounted(getUserInfo) // 在 `mounted` 時呼叫 `getUserInfo`

// setup的返回值

// 一般都是返回一個物件,為模板提供資料,就是模板中可以直接使用此物件中所有屬性/方法
// 返回物件中的屬性會與data函式返回物件的屬性合併成為元件物件的屬性
// 返回物件中的方法會與methods中的方法合併成元件物件的方法
// 若有重名,setup優先
  return {
    ...toRefs(state),
    getUserInfo
  }
}

靈活的邏輯組合與複用
可與現有的Options API一起使用
與選項API最大的區別的是邏輯的關注點
選項API這種碎片化使得理解和維護複雜元件變得困難,在處理單個邏輯關注點時,我們必須不斷地上下翻找相關程式碼的選項塊。
compositon API將同一個邏輯關注點相關程式碼收集在一起
6. Fragment(碎片)
在這裡插入圖片描述

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

Vue 3不再限於模板中的單個根節點,它正式支援了多根節點的元件,可純文字,多節點,v-for等
render 函式也可以返回陣列
7. Teleport(傳送門)
在這裡插入圖片描述

這個元件的作用主要用來將模板內的 DOM 元素移動到其他位置。
允許我們控制在 DOM 中哪個父節點下渲染了 HTML

<teleport to="body">
  <div v-if="modalOpen" class="modal">
    <div>
      I'm a teleported modal!
      (My parent is "body")
      <button @click="modalOpen = false">
        Close
      </button>
    </div>
  </div>
</teleport>
  1. 更好的Typescript支援
    vue3是基於typescipt編寫的,可以享受到自動的型別定義提示

  2. 自定義渲染 API
    在這裡插入圖片描述

    vue官方實現的 createApp 會給我們的 template 對映生成 html 程式碼,但是要是你不想渲染生成到 html ,而是要渲染生成到 canvas 之類的不是html的程式碼的時候,那就需要用到 Custom Renderer API 來定義自己的 render 渲染生成函式了。
    意味著以後可以通過 vue, Dom 程式設計的方式來進行canvas、webgl 程式設計
    預設的目標渲染平臺
    在這裡插入圖片描述

    自定義目標渲染平臺
    在這裡插入圖片描述

  3. 響應原理的變化
    vue2物件響應化:遍歷每個key,通過 Object.defineProperty API定義getter,setter 進而觸發一些檢視更新
    陣列響應化:覆蓋陣列的原型方法,增加通知變更的邏輯
    vue2響應式痛點
    遞迴,消耗大
    新增/刪除屬性,需要額外實現單獨的API
    陣列,需要額外實現
    Map Set Class等資料型別,無法響應式
    修改語法有限制
    vue3響應式方案: 使用ES6的Proxy進行資料響應化,解決上述vue2所有痛點,Proxy可以在目標物件上加一層攔截/代理,外界對目標物件的操作,都會經過這層攔截。Proxy可以在目標物件上加一層攔截/代理,外界對目標物件的操作,都會經過這層攔截,相比 Object.defineProperty ,Proxy支援的物件操作十分全面

一, 全域性api

1. 全域性 Vue API 已更改為使用應用程式例項

vue2使用全域性api 如 Vue.component, Vue.mixin, Vue.use等,缺點是會導致所建立的根例項將共享相同的全域性配置(從相同的 Vue 建構函式建立的每個根例項都共享同一套全域性環境。這樣就導致一個問題,只要某一個根例項對 全域性 API 和 全域性配置做了變動,就會影響由相同 Vue 建構函式建立的其他根例項。)
vue3 新增了createApp,呼叫createApp返回一個應用例項,擁有全域性API的一個子集,任何全域性改變 Vue 行為的 API 現在都會移動到應用例項上
在這裡插入圖片描述

2. 元件掛載

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

createApp初始化後會返回一個app物件,裡面包含一個mount函式
mount函式是被重寫過的

  1. 處理傳入的容器並生成節點;
  2. 判斷傳入的元件是不是函式元件,元件裡有沒有render函式,template屬性,沒有就用容器的innerHTML作為元件的template;
  3. 清空容器內容
  4. 執行快取的mount函式實現掛載元件;

二, 模板指令

  • 元件上 v-model 用法更改,替換 v-bind.sync
    vue2預設會利用名為 value 的 prop 和名為 input 的事件
// ParentComponent
<ChildComponent v-model="pageTitle" />

<!-- 是以下的簡寫: -->

<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

 // ChildComponent

<input type="text" :value="value" @input="$emit('input', $event.target.value)">

如果想要更改 prop 或事件名稱,則需要在元件中新增 model 選項:
model選項,允許元件自定義用於 v-model 的 prop 和事件

// ChildComponent
<input type="text" :value="title" @input="$emit('change', $event.target.value)">
export default {
  model: {
    prop: 'title',
    event: 'change'
  },
  props: {
    title: String
  }
}

使用 title 代替 value 作為 model 的 prop

vue2.3 新增.sync (對某一個 prop 進行“雙向繫結”,是update:title 事件的簡寫)

// ParentComponent
<ChildComponent :title.sync="name" />

<!-- 是以下的簡寫 -->

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
 // ChildComponent
 <input type="text" :value="title" @input="$emit('update:title', $event.target.value)">

在 3.x 中,自定義元件上的 v-model 相當於傳遞了 modelValue prop 並接收丟擲的 update:modelValue 事件
prop:value -> modelValue;
event:input -> update:modelValue
v-bind 的 .sync 修飾符和元件的 model 選項已移除,可用 v-model加引數 作為代替
vue3 可以將一個 argument 傳遞給 v-model:
<ChildComponent v-model:title="pageTitle" />
等價於
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
可使用多個model
在這裡插入圖片描述

  • 可以在template元素上新增 key
<template v-for="item in list" :key="item.id">
  <div>...</div>
</template>
  • 同一節點v-if 比 v-for 優先順序更高
  • v-bind="object" 現在排序敏感(繫結相同property,vue2單獨的 property 總是會覆蓋 object 中的繫結。vue3按順序決定如何合併)
<div id="red" v-bind="{ id: 'blue' }" ></div>
// vue2 id="red"
// vue3 id="blue"
  • 移除 v-on.native 修飾符
    Vue 2 如果想要在一個元件的根元素上直接監聽一個原生事件,需要使用v-on 的 .native 修飾符
    Vue3 現在將所有未在元件emits 選項中定義的事件作為原生事件新增到子元件的根元素中(除非子元件選項中設定了 inheritAttrs: false)。
    (強烈建議元件中使用的所有通過emit觸發的event都在emits中宣告)
<my-component @close="handleComponentEvent" @click="handleNativeClickEvent"/>
// mycomponent
<template>
  <div>
    <button @click="$emit('click')">click</button>
    <button @click="$emit('close')">close</button>
  </div>
</template>
<script>
  export default {
    emits: ['close']
  }
</script>
  • v-for 中的 ref 不再註冊 ref 陣列
    vue2在 v-for 語句中使用ref屬性時,會生成refs陣列插入$refs屬性中。由於當存在巢狀的v-for時,這種處理方式會變得複雜且低效。
    vue3在 v-for 語句中使用ref屬性 將不再會自動在$refs中建立陣列。而是,將 ref 繫結到一個 function 中,在 function 中可以靈活處理ref。
<div v-for="item in list" :ref="setItemRef"></div>

export default {
 setup() {
   let itemRefs = []
   const setItemRef = el => {
     itemRefs.push(el)
   }
   return {
     setItemRef
   }
 }
}

三, 元件

  • 函式式元件
    在 Vue 2 中,函式式元件有兩個主要應用場景:
    作為效能優化,因為它們的初始化速度比有狀態元件快得多
    返回多個根節點
    然而Vue 3對有狀態元件的效能進行了提升,與函式式元件的效能相差無幾。此外,有狀態元件現在還包括返回多個根節點的能力。所以,建議只使用有狀態元件。

    結合<template>的函式式元件:

  1. functional 移除
  2. 將 props 的所有引用重新命名為 $props,attrs 重新命名為 $attrs。
<template>
  <component :is=`h${$props.level}`  v-bind='$attrs' />
</template>

<script>
  export default {
    props: ['level']
  }
</script>

函式寫法:
相較於 Vue 2.x 有三點變化:

  1. 所有的函式式元件都是用普通函式建立的,換句話說,不需要定義 { functional: true } 元件選項。
  2. export default匯出的是一個函式,函式有兩個引數:
    props
    context(上下文):context是一個物件,包含attrs、slot、emit屬性
  3. h函式需要全域性匯入
import { h } from 'vue'

const DynamicHeading = (props, context) => {
  return h(`h${props.level}`, context.attrs, context.slots)
}

DynamicHeading.props = ['level']

export default DynamicHeading
  • 非同步元件需要 defineAsyncComponent 方法來建立
    非同步元件的匯入需要使用輔助函式defineAsyncComponent來進行顯式宣告
import { defineAsyncComponent } from 'vue'
const child = defineAsyncComponent(() => import('@/components/async-component-child.vue'))

帶選項非同步元件,component 選項重新命名為 loader

const asyncPageWithOptions  = defineAsyncComponent({
  loader: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  error: ErrorComponent,
  loading: LoadingComponent
})
  • (新增)元件事件需要在 emits 選項中宣告()
    強烈建議使用 emits 記錄每個元件所觸發的所有事件。
    因為移除了 v-on.native 修飾符。任何未宣告 emits 的事件監聽器都會被算入元件的 $attrs 並繫結在元件的根節點上。
    如果emit的是原生的事件(如,click),就會存在兩次觸發。
    一次來自於$emit的觸發;
    一次來自於根元素原生事件監聽器的觸發;
    (emits 1.更好的記錄已發出的事件,2.驗證丟擲的事件)
 export default {
    props: ['text'],
    emits: ['accepted']
  }
emits: {
    click: null,
    submit: payload => {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload!`)
        return false
      }
    }
  }

四, 渲染函式

  • 渲染函式API
    h是全域性匯入,而不是作為引數傳遞給渲染函式
    在 2.x 中,render 函式會自動接收 h 函式作為引數
    在 3.x 中,h 函式需要全域性匯入。由於 render 函式不再接收任何引數,它將主要在 setup() 函式內部使用。可以訪問在作用域中宣告的響應式狀態和函式,以及傳遞給 setup() 的引數
import { h, reactive } from 'vue'

export default {
  setup(props, { slots, attrs, emit }) {
    const state = reactive({
      count: 0
    })

    function increment() {
      state.count++
    }

    // 返回render函式
    return () =>
      h(
        'div',
        {
          onClick: increment
        },
        state.count
      )
  }
}
  • 移除$listeners整合到 $attrs
    包含了父作用域中的(不含emits的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部元件
{{$attrs}}
<grand-son v-bind="$attrs"></grand-son>
  • $attrs包含class&style
    在vue2中,關於父元件使用子元件有這樣一個原則:

預設情況下父作用域的不被認作 props 的 attribute 繫結 (attribute bindings) 將會“回退”且作為普通的 HTML attribute 應用在子元件的根元素上

這句話的意思是,父元件呼叫子元件時,給子元件錨點標籤新增的屬性中,除了在子元件的props中宣告的屬性,其他屬性會自動新增到子元件根元素上。
為此,vue新增了inheritAttrs = false,這些預設行為將會被去掉,通過例項 property $attrs 可以讓這些 attribute 生效,且可以通過 v-bind 顯性的繫結到非根元素上。

五, 自定義元素

  • 自定義元素檢測在編譯時執行
    自定義元素互動
    Vue 2中,通過 Vue.config.ignoredElements 配置自定義元素
Vue.config.ignoredElements = ['plastic-button']

Vue 3 通過app.config.isCustomElement

const app = Vue.createApp({})
app.config.isCustomElement = tag => tag === 'plastic-button'
  • Vue 3.x 對 is做了新的限制
    當在 Vue 保留的 component標籤上使用is時,它的行為將與 Vue 2.x 中的一致
    當在不同元件標籤上使用is時,is會被當做一個不同的prop;
    當在普通的 HTML 元素上使用is,is將會被當做元素的屬性。
    新增了v-is,專門來實現在普通的 HTML 元素渲染元件。

六, 其他

  • destroyed 生命週期選項被重新命名為 unmounted
  • beforeDestroy 生命週期選項被重新命名為 beforeUnmount
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-awWIzouv-1637307558259)(assets/vue3/img.png)]
    整體來看其實變化不大,使用setup代替了之前的beforeCreate和created,其他生命週期名字有些變化,功能都是沒有變化的
  • Props 的預設值函式不能訪問this
    替代方案:
    把元件接收到的原始 prop 作為引數傳遞給預設函式;
    inject API 可以在預設函式中使用。
import { inject } from 'vue'

export default {
  props: {
    theme: {
      default (props) {
        // `props` 是傳遞給元件的原始值。
        // 也可以使用 `inject` 來訪問注入的屬性
        return inject('theme', 'default-theme')
      }
    }
  }
}
  • 自定義指令 API 與元件生命週期一致
const MyDirective = {
  created(el, binding, vnode, prevVnode) {}, // 新增
  beforeMount() {},
  mounted() {},
  beforeUpdate() {}, // 新增
  updated() {},
  beforeUnmount() {}, // 新增
  unmounted() {}
}

繫結元件的例項從 Vue 2.x 的vnode.context移到了binding.instance中

  • data 選項應始終被宣告為一個函式
    data 元件選項宣告不再接收 js 物件,只接受函式形式的宣告。
<script>
  import { createApp } from 'vue'

  createApp({
    data() {
      return {
        apiKey: 'a1b2c3'
      }
    }
  }).mount('#app')
</script>

當合並來自 mixin 或 extend 的多個 data 返回值時,data現在變為淺拷貝形式(只合並根級屬性)。

const Mixin = {
  data() {
    return {
      user: {
        name: 'Jack',
        id: 1
      }
    }
  }
}

const CompA = {
  mixins: [Mixin],
  data() {
    return {
      user: {
        id: 2
      }
    }
  }
}

vue2

{
  "user": {
    "id": 2,
    "name": "Jack"
  }
}

vue3

{
  "user": {
    "id": 2
  }
}
  • 過渡的 class 名更改(過渡類名 v-enter 修改為 v-enter-from、過渡類名 v-leave 修改為 v-leave-from。)
  • transition-group 不再需要設定根元素( 不再預設渲染根元素,但仍可以使用 tag prop建立一個根元素。)
  • 偵聽陣列(當偵聽一個陣列時,只有當陣列被替換時才會觸發回撥。如果你需要在陣列改變時觸發回撥,必須指定 deep 選項。)
  • 已掛載的應用不會取代它所掛載的元素(在vue2中,當掛載一個具有 template 的應用時,被渲染的內容會替換我們要掛載的目標元素。在 Vue 3.x 中,被渲染的應用會作為子元素插入,從而替換目標元素的 innerHTML)
  • 生命週期 hook: 事件字首改為 vnode-(監聽子元件和第三方元件的生命週期)

移除API

  • 不再支援使用數字 (即鍵碼) 作為 v-on 修飾符,vue3建議使用按鍵alias(別名)作為v-on的修飾符。
<input v-on:keyup.delete="confirmDelete" />
  • vue3將移除且不再支援 filters,如果需要實現過濾功能,建議通過method或computed屬性來實現(如果需要使用全域性過濾器vue3提供了globalProperties。我們可以藉助globalProperties來註冊全域性過濾, 全域性過濾器裡面定義的只能是method。)
const app = createApp(App)

app.config.globalProperties.$filters = {
  currencyUSD(value) {
    return '$' + value
  }
}
<template>
  <p>{{ $filters.currencyUSD(accountBalance) }}</p>
</template>
  • 內聯模板 (inline-template attribute移除)
  • $children(如果需要訪問子元件例項,建議使用 $refs)
  • propsData 選項之前用於在建立 Vue 例項的過程中傳入 prop,現在它被移除了。如果想為 Vue 3 應用的根元件傳入 prop,使用 createApp 的第二個引數。
  • 全域性函式 set 和 delete 以及例項方法 $set 和 $delete。基於代理的變化檢測不再需要它們了。

用於遷移的構建版本

@vue/compat (即“遷移構建版本”) 是一個 Vue 3 的構建版本,提供了可配置的相容 Vue 2 的行為。

該構建版本預設執行在 Vue 2 的模式下——大部分公有 API 的行為和 Vue 2 一致,僅有一小部分例外。使用在 Vue 3 中發生改變或被廢棄的特性時會丟擲執行時警告。一個特性的相容性也可以基於單個元件進行開啟或禁用。

已知的限制:

  • 基於vue2內部API或文件中未記載行為的依賴。最常見的情況就是使用 VNodes 上的私有 property。如果你的專案依賴諸如 Vuetify、Quasar 或 Element UI 等元件庫,那麼最好等待一下它們的 Vue 3 相容版本。

  • 對IE11的支援:Vue 3 已經官方放棄對 IE11 的支援。如果仍然需要支援 IE11 或更低版本,那你仍需繼續使用 Vue 2。

  • 服務端渲染:該遷移構建版本可以被用於服務端渲染,但是遷移一個自定義的服務端渲染設定有更多工作要做。大致的思路是將 vue-server-renderer 替換為 @vue/server-renderer。Vue 3 不再提供一個包渲染器,推薦使用 Vite 以支援 Vue 3 服務端渲染。

相關文章