前端
vue3升级回顾

背景

  • 有组员提出想用vue3重构公司项目(代号p),也是去探索新的技术
  • 我认为这是一个值得去做的事,经过和上级沟通,上级认为既然是重构升级,那就彻底一点,建议直接上typescript(总所周知,vue3是ts写的)
  • 以下梳理升级所涉及的技术变动
区分项旧项目新项目
运行环境node 10.15.0node 12.0.0以上
构建vue-clivite
语言支持js支持typescript
主要依赖项vue2,elementuivue3,element-plus

历时

  • 项目不算大(200+文件,40+路由)
  • 预计总共花4个月(2021.10-2022.02)
  • 前两个月代码,环境改动;后两个月测试
  • 最终经过整个团队努力,大体还是算升级成功的

问题记录

  • 以下为针对几个关键问题的记录

环境冲突

  • 运维对所有vue项目(客户端渲染)做了统一的ci,流程为:下载node10->安装依赖->构建项目 如何做到单独对p项目下载node12,而不影响其他项目的构建?
  • 这里我提出了用nvm来切换node,但这样做得先卸载掉之前下载的node10,运维放弃了这个方案,最终得到的方案是:根据项目名来改变环境变量设置语句:若是项目p,构建前切换环境:
echo '---根据JOB_NAME切换环境---'
if [[ ${JOB_NAME} == 'p' ]] && [[ ${ENV} == 'test' ]]; 
then
    export NODE_HOME=/x/node-v12.22.7
    export PATH=$NODE_HOME/bin:$path
echo '当前node版本为:'
node -v

依赖冲突

  • 升级node12之后,因为网络原因无法安装node-sass@4,所以我们安装了最新的node-sass@6,也由此引入了麻烦
  • 本地切node12之后,有的项目(依赖为node-sass)构建会报错,项目里有大量的/deep/写法,而node-sass@6不支持/deep/写法
  • 这个时候有两个方案去解决: 1. 全局替换【/deep/】为【::v-deep】; 2. 用sass替代node-sass
  • 最终我们选择了方案2,因为方案1会造成另外一个问题:https://github.com/sass/node-sass/releases ,这是node-sass支持的node版本,可以看到版本6是不支持node10的
  • 其中有个nuxtjs项目也根据这篇博客来取代node-sass提前支持node12
// nuxt.config.js
build{
    loaders: {
        scss: {
            implementation: require("sass"),
        }
    }
}
  • 还有一个老项目是用webpack3构建的,webpack3只支持node-sass,所以无法通过sass替换node-sass。 老项目就让它保持原来的构建环境吧🤣

删除路由缓存

  • 一直以来手动清除vue的路由缓存都是一件不容易的事情
  • vue2是遍历$children下vnode,找出data.keepAlive作为命中判断,在parentVnode.componentInstance.cache里删除组合key来删除标记,最终调$destroy来清内存。
  • 然而这种hack实现并不能直接迁移到vue3,keep-alive组件,有位同事找到了相关issue和解决:https://github.com/vuejs/vue-next/issues/2077#issuecomment-887425577,但只能跑在开发环境

最终仰仗于强大的同事,他机智的用rollup build生成了一份vue3版keep-alive(这使得代码很逼真像自己写的一样🤣),再干掉那句环境判断


体验setup

  • 期间重写了一个简单的表格页面,这种逻辑点拆到个个方法的分离方式是真的香(除了没有this不是那么习惯)
import { defineComponent, reactive, ref, Ref, onMounted, onActivated } from 'vue'
import axios from '@/components/common/util/http'
import { useRoute, useRouter } from 'vue-router'
interface ICondition {
    nameMobile: string
    companyId: string
}
interface IParams<T = ICondition> {
    condition: T
}
interface IList {
    id: string
    [propName: string]: any
}
interface IObject<T> {
    [index: string]: T
}
const search1_1 = (dictionary) => {
    const loading = ref(false)
    const remoteMethod = (query: string) => {
        loading.value = true
        axios
            .post('/xxx', { abbrName: query })
            .then((res) => {
                dictionary.list = res.data.data
                loading.value = false
            })
            .catch((e) => {
                loading.value = false
            })
    }
    return {
        loading,
        remoteMethod,
    }
}
const search1_2 = (dictionary) => {
    const route = useRoute()
    const form: IObject<any> = ref()
    const getData = ref(false)
    const condition: ICondition = {
        nameMobile: '',
        companyId: '',
    }
    const params: IParams = reactive({
        condition,
    })
    const reSet = () => {
        form.value.resetFields()
    }
    onMounted(() => {
        // console.log(form)
    })
    onActivated(() => {
        const { id, abbrName } = route.params
        if (id) {
            dictionary.list = [{ id, abbrName }]
            params.condition.companyId = String(id)
            getData.value = true
        }
    })
    return {
        params,
        reSet,
        form,
        getData,
    }
}
const listOper1_1 = () => {
    const router = useRouter()
    return {
        detailHandle(row: IList) {
            router.push({
                path: '/detail?id=' + row.id,
                query: { id: row.id },
            })
        },
        editHandle(row) {
            router.push({
                path: '/edit?id=' + row.id,
                query: { id: row.id },
            })
        },
        addHandle() {
            router.push('/add')
        },
    }
}
export default defineComponent({
    setup() {
        const dictionary = reactive({
            list: [],
        })

const list: Ref&lt;IList[]&gt; = ref([])

/* 
    逻辑点关注分离
    1. 筛选
        1.1 远程下拉搜索
        1.2 查询,清空,路由激活
    2. 列表
        2.1 操作栏+新增
        2.2 分页
*/
return {
    list,
    dictionary,
    ...search1_1(dictionary),
    ...search1_2(dictionary),
    ...listOper1_1(),
}

},

其他陷阱

  • element-plus的ui上是与elementui大同小异的,这个小异需要关注一下,比如 按钮之间的字间距会大些
  • 当前element-plus仍在beta版,一些改动无法避免,比如在@1.2.0-beta.1之后,icon的实现用svg取代font。我们的vue3版项目刚好就在这时间节点升级

总结与展望

  • vite的构建超快,此项目的构建从3mins减到了71s(包括npm install)
  • 经过这次的升级之后,后面能够做的事情就很多了
    比方说在其他项目是不是能够用这份探索去复制升级,哪怕只是ts,vu3,vite构建三者其中的一种;比如vue3还有很多优势:style绑定变量等,值得去探索
  • 升级的过程里,组员做了主要代码改动,我负责上报,补坑,组织分享。
  • 这次的升级我有一个很深的体会:如果大家能一起朝着一个目标走,事情就会进行的比较顺利,这也是我们整个团队的力量所在。希望我们能够持续的保持这份技术热情,继续推着团队前进!




日期:2022-01-09 08:04 | 阅读:188 | 评论:1