Vue2迁移到Vue3.0.5,<srcipt setup>开发到部署避坑指南__Vue.js
发布于 3 年前 作者 banyungong 1398 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

开始

Vue3出来也一段时间了,最近公司有个项目需要重构,用Vue3重构了一遍,开发体验上来讲个人觉得改变其实不是特别大,但是必须吹一下vite是真的快,在开发过程中遇到的问题也不少,特别是部署的时候,没仔细看文档就要GG了,废话不多说,直接上正题。

项目架构

  • 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这些基础知识的文档有很多,可以看下其他大佬的相关文档大概十分钟就可以上手了。

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文档参考

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: duqingyu 原文链接:https://juejin.im/post/6959458564012425252

回到顶部