Vue2遷移到Vue3.0.5, 開發到部署避坑指南

開始

Vue3出來也一段時間了,最近公司有個項目須要重構,用Vue3重構了一遍,開發體驗上來說我的以爲改變其實不是特別大,可是必須吹一下vite是真的快,在開發過程當中遇到的問題也很多,特別是部署的時候,沒仔細看文檔就要GG了,廢話很少說,直接上正題。javascript

項目架構

  • vue3.0.5
  • axios 0.21.1
  • element-plus 1.0.2-beta.35
  • vue-router 4.5.5
  • vuex: 4.0.0

說明

本文主要講Vue2轉到Vue3的一些相關對比,關於好比ref,reactive這些基礎知識的文檔有不少,能夠看下其餘大佬的相關文檔大概十分鐘就能夠上手了。html

Vue3新的語法糖script setup

<template>
  <panel class="size-wrapper"> 這是主頁 <template #footer class="dialog-footer"> <el-button type="primary" @click="onConfirmClick()">知道了</el-button> </template> </panel>
</template>
<script setup> import { ref, reactive, watch } from 'vue' import Panel from '@/components/Panel.vue' </srcipt>
複製代碼
  • 直接給script標籤添加setup屬性,不要像舊的語法那樣在底部return一堆屬性出去
  • 組件import後直接在template使用,不要註冊
  • 該語法感受代碼量少了很多,可是文件大起來可能會有雜亂無章的感受,我是比較喜歡這種寫法,看項目見仁見智吧。
  • script setup 相關文檔

data屬性,methods

// vue2
data() {
    return {
      loading: false,
      tableData: []
    }
},
methods:{
    getList(){
        this.loading = true
        this.tableData = []
        this.loading = false
    }
}
複製代碼
// vue3
<script setup>
import { ref, reactive } from 'vue'
let state = reactive({
    loading: false,
    tableData: []
})

function getList(){
    state.loading = true
    state.tableData = []
    state.loading = false
}
</srcipt>
複製代碼
  • 組合式Api沒有this了,直接就是變量,更加靈活

watch、computed

// vue2
watch: {
    tableData (val) {
      this.tableData2 = []
    }
},
computed: {
    price () {
      return this.price1 * 20
    }
}
複製代碼
// vue3
<script setup>
import { ref, reactive,watch,computed,watchEffect } from 'vue'

let price = ref(10) 
let price1 = ref(20)
let state = reactive({
    price1: 10,
    price2: 20,
    price3: 0
})

// 監聽ref屬性
watch(price1, (newVal,oldVal) => {
    console.log(newVal,oldVal)
})
// 監聽多個ref屬性
watch([price, price1], ([newPrice, newPrice1], [prevPrice, prevPrice1]) => {
  console.log(newPrice, newPrice1)
})
// 監聽reactive屬性
watch(() => state.price1, (newVal,oldVal) => {
    console.log(newVal,oldVal)
})
watch(() => state, (newVal,oldVal) => {
    console.log(newVal,oldVal)
})

const price = computed(() => price1 * 20)
const price2 = computed(() => state.price2 * 20)

// watchEffect
// 官方描述:在響應式地跟蹤其依賴項時當即運行一個函數,並在更改依賴項時從新運行它
watchEffect(() => {
    const {price1,price2} = state
    state.price3 = price1 + price2
})

</srcipt>
複製代碼
  • watch能夠監聽一個或者多個數據源
  • watch監聽reactive屬性須要傳入一個回調函數
  • watchEffect會自動收集依賴,因此不用指定屬性,那意味着它會在第一次的時候自動執行一次該函數去自動收集依賴

生命週期

// vue2
<script>
beforeCreate(){}
create(){}
mounted(){}
destory(){}
updated(){}
...
</srcipt>
複製代碼
// vue3
<script setup>
import { onMounted,onUpdated,onUnmounted  } from 'vue'
onMounted(() => {
    console.log('onMounted')
})
onUpdated(() => {
    console.log('onUpdated')
})
console.log(created)
</srcipt>
輸出 created、onMounted
複製代碼
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeUnmount -> onBeforeUnmount
  • unmounted -> onUnmounted
  • errorCaptured -> onErrorCaptured
  • renderTracked -> onRenderTracked
  • renderTriggered -> onRenderTriggered
  • vue3去除了create相關的鉤子,setup能夠理解爲create和beforeCreate

ref

// vue2
<template>
  <panel class="size-wrapper" ref="panel"> 這是主頁 </panel>
</template>
<script>
mounted(){
    console.log(this.refs.panel)
}

</srcipt>
複製代碼
// vue3 單個ref
<template>
  <panel class="size-wrapper" ref="panel"> 這是主頁 </panel>
</template>
<script setup> import { ref, reactive, onMounted } from 'vue' import Panel from '@/components/Panel.vue' // ref傳null自動綁定 let panel = ref(null) onMounted(() => { // 因爲是ref,須要.value獲取 const panelRef = panel.value console.log(panelRef) }) </srcipt>
複製代碼
// vue3 多個ref
<template>
  <panel1 class="size-wrapper" :ref="el => setRefs(el, 'panel1')"> 這是主頁 </panel1>
  <panel2 class="size-wrapper" :ref="el => setRefs(el, 'panel2')"> 這是主頁 </panel2>
  <panel3 class="size-wrapper" :ref="el => setRefs(el, 'panel3')"> 這是主頁 </panel3>
</template>
<script setup> import { ref, reactive, onMounted } from 'vue' import Panel from '@/components/Panel.vue' import Pane2 from '@/components/Pane2.vue' import Pane3 from '@/components/Pane3.vue' // 相似react傳回調函數 let refs = ref({}) let setRefs = (el, name) => (refs.value[name] = el) onMounted(() => { // 因爲是ref,須要.value獲取 const panel1Ref = refs.value.panel1 const panel2Ref = refs.value.panel2 const panel3Ref = refs.value.panel3 console.log(panel1Ref,panel2Ref,panel3Ref) }) </srcipt>
複製代碼
  • 若是在組件中指定了ref,在script裏邊必定要收集它,在本文的版本里若是指定了好比ref="form",而在script中沒有生命ref去接收,會致使vue自動收集不到報錯form is undefined
  • ref若是須要獲取子組件內的方法,須要子組件暴露相關方法(下面組件說怎麼暴露),不然獲取不到

組件

// 父組件
<template>
  <panel class="size-wrapper" ref="panel"> 這是主頁 <DescDialog v-model:visible="state.visible" :data="state.data"> </DescDialog> </panel>
</template>
<script setup> import { reactive, onMounted } from 'vue' import Panel from '@/components/Panel.vue' let state = reactive({ visible: false, data: [] }) </srcipt>
複製代碼
// 子組件
<template>
  <div> <el-dialog title="描述詳情" v-model="visible" :show-close="false"> <div v-html="sizeData"></div> <template #footer class="dialog-footer"> <el-button type="primary" @click="onConfirmClick()">知道了</el-button> </template> </el-dialog> </div>
</template>

<script setup> import { defineProps, defineEmit, reactive, useContext } from 'vue' // 用於暴露組件的方法,必定程度上讓組件更加清晰把 const { expose } = useContext() defineProps({ visible: { type: Boolean, default: () => false }, data: { type: Array, default: () => [] } }) const emit = defineEmit(['update:visible']) const onConfirmClick = () => { emit('update:visible', false) } expose({ onConfirmClick }) </script>

<style lang="less"> </style>

複製代碼
  • vue2的:loading.sync => v-modle:visbile
  • vue2的props => defineProps
  • vue2的$emit => defineEmit,emit須要指定聲明

vuex

  • 聲明引入階段和vue2沒什麼變化,同樣引入同樣使用,好比聲明modules下的user.js(這裏按模塊區分)
import * as userApi from '@/server/api/user'

export default {
  state: {
    userInfo: {}
  },
  mutations: {
    setUserInfo(state, data) {
      state.userInfo = data
    },
    clearUserInfo(state, data) {
      state.userInfo = {}
    },
  },
  actions: {
    async getUserInfo({ commit, state }) {
      const res = await userApi.getUserInfo()
      commit('setUserInfo', res.data)
    }
  }
}

複製代碼
  • 組件獲取階段
<script setup>
import { isRef } from 'vue'
import { useStore,computed,isRef } from 'vuex'

const store = useStore()

const userInfo = computed(() => store.state.user.userInfo)
const getUserInfo = () => store.dispatch('getUserInfo')
const setUserInfo = () => store.commit('setUserInfo')

const is = isRef(userInfo)
console.log(is)
// 輸出true,注意若是在script中使用該屬性的話須要.value,在模板使用可忽略

</script>
複製代碼
  • userStore獲取vuex相關信息

路由

// vue2
<script>
mounted(){
    console.log(this.$route.query)
    console.log(this.$route.params)
    // this.$router.push({path: /index})
}
</script>
複製代碼
// vue3
<script setup>
import { onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

onMounted(() => {
    console.log(route.query)
    console.log(route.params)
    // router.push({path: /index})
})

</script>
複製代碼
  • router沒太多改變,和vuex差很少

組件實例

  • 不少時候第三方UI框架或者自身封裝的axios請求等東西會注入到全局根實例,注入以後直接在全部組件能夠使用
// vue2
import Vue from 'vue'

Vue.prototype.$message = message
Vue.prototype.$notification = notification
Vue.prototype.$info = Modal.info
Vue.prototype.$success = Modal.success
Vue.prototype.$error = Modal.error
Vue.prototype.$warning = Modal.warning

// 使用
<template>
  <panel class="size-wrapper" ref="panel"> 這是主頁 </panel>
</template>
<script>
mounted(){
    this.$loading().show()
    this.$loading().close()
}
</srcipt>

複製代碼
// vue3
import App from '@/App.vue'
const app = createApp(App)

app.config.globalProperties.$message = message
app.config.globalProperties.$notification = notification
app.config.globalProperties.$info = Modal.info
app.config.globalProperties.$success = Modal.success
app.config.globalProperties.$error = Modal.error
app.config.globalProperties.$warning = Modal.warning

// 使用
<script setup>
import { getCurrentInstance } from 'vue'

// const { ctx } = getCurrentInstance()

// 使用proxy代替ctx
const { proxy: ctx } = getCurrentInstance()

onMounted(() => {
    const loading = ctx.$loading()
    loading.closed()
})

</script>


複製代碼
  • 經過getCurrentInstance能夠獲取當前的組件實例
  • 注意ctx屬性只在開發階段有效,在生產環境會直接GG,使用建議不要使用ctx,用proxy代替ctx,博主因爲沒仔細看文檔,致使部署的時候GG過一次。。
  • 看下ctx源碼,若是是生產環境,結果就是一個_。。

1620375167(1).png

最後來個vite.config.js最簡單的配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()], // 插件
  outDir: 'dist', // 打包目錄
  // 服務器配置
  server: {
    port: 3000,
    open: true,
    https: false,
    ssr: false,
    // 設置代理
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:8461/api',
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, '')
      }
    }
  },
  // @重寫命令路徑
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  }
})

複製代碼

總結

  • vue3用新語法糖寫起來我的感受比以前代碼量少了一些
  • 組合式Api開發起來更加靈活,邏輯複用較好
  • 新版devtools彷佛好不是很完善,使用起來通常般,有時數據刷新不是特別實時
  • 開發階段錯誤提示有時候彷佛還不是很具體
  • 記得部署的時候千萬不要有getCurrentInstance().ctx屬性,它只在開發階段生效
  • vue3文檔參考
相關文章
相關標籤/搜索