Skip to content

Vue 2.7 升级指南

💡 升级策略:考虑到直接升级至 Vue 3 工作量较大,采用渐进式升级方案,先迁移至 Vue 2.7,后续再升级至 Vue 3。

📖 关于 Vue 2.7

Vue 2.7 是 Vue 2 的最新次级版本,从 Vue 3 移植了最重要的特性,让 Vue 2 用户也能享受这些便利:

  • ✨ 组合式 API(Composition API)
  • <script setup> 语法糖
  • ✨ CSS v-bind
  • ✨ 更多特性详见 官方迁移指南

🔧 升级步骤

1️⃣ 升级核心依赖

Vue CLI / Webpack 项目

操作清单

  1. 将本地 @vue/cli-xxx 依赖升级至主版本范围内的最新版本
    • Vue CLI v4 升级至 ~4.5.18
  2. 移除 vue-template-compiler(Vue 2.7 已内置)

package.json 修改示例

package.json
json
{
  "name": "@mgcloud/demo-ui",
  "version": "3.2.0",
  "description": "模板项目",
  "author": "wudi",
  "scripts": {
    "dev": "vue-cli-service serve",
    "test": "vue-cli-service build --mode test",
    "build:prod": "vue-cli-service build",
    "lint": "eslint --ext .js,.vue src",
    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
  },
  "repository": {
    "type": "git",
    "url": "http://39.108.189.115:81/general/frontend/demo-ui.git"
  },
  "dependencies": {
    "axios": "0.18.1",
    "core-js": "3.25.2",
    "element-ui": "2.15.14",
    "moment": "^2.29.1",
    "normalize.css": "7.0.0",
    "nprogress": "0.2.0",
    "vue": "2.6.14", 
    "vue-router": "3.0.2", 
    "vuex": "3.1.0", 
    "vue": "2.7.16", 
    "vue-router": "3.6.5", 
    "vuex": "3.6.2"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "4.4.4", 
    "@vue/cli-plugin-eslint": "4.4.4", 
    "@vue/cli-plugin-unit-jest": "4.4.4", 
    "@vue/cli-service": "4.4.4", 
    "@vue/cli-plugin-babel": "~4.5.18", 
    "@vue/cli-plugin-eslint": "~4.5.18", 
    "@vue/cli-plugin-unit-jest": "~4.5.18", 
    "@vue/cli-service": "~4.5.18", 
    "autoprefixer": "9.5.1",
    "babel-eslint": "10.1.0",
    "babel-jest": "^26.6.1",
    "babel-plugin-dynamic-import-node": "2.3.3",
    "eslint": "6.7.2",
    "eslint-plugin-vue": "6.2.2",
    "html-webpack-plugin": "3.2.0",
    "husky": "1.3.1",
    "lint-staged": "8.1.5",
    "sass": "1.27.0",
    "sass-loader": "^10.1.1",
    "script-ext-html-webpack-plugin": "2.1.3",
    "script-loader": "0.7.2",
    "svg-sprite-loader": "4.1.3",
    "vue-template-compiler": "2.6.14"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ]
}

2️⃣ 代码语法升级

深度选择器语法更新

Vue 2.7 推荐使用 :deep() 伪类替代 ::v-deep

vue
<style scoped lang="scss">
/* ❌ 旧语法 */
::v-deep .app-btn {
  width: 33%;
}

/* ✅ 新语法(推荐) */
:deep(.app-btn) {
  width: 33%;
  padding: 20px 0;
  border-radius: 10px;
  
  &:hover {
    :deep(.txt) {
      color: #1890ff;
    }
  }
}
</style>

3️⃣ 遵循 Vue 3 编码规范

移除 Mixin,改用组合式 API

Mixin 在 Vue 3 中不再推荐使用,建议将逻辑迁移到组件内部。

示例 1:移除 FixiOSBug Mixin

vue
<script>
import AppLink from './Link.vue'

export default {
  name: 'SidebarItem',
  components: { AppLink },
  props: {
    item: {
      type: Object,
      required: true,
    },
    isNest: {
      type: Boolean,
      default: false,
    },
    basePath: {
      type: String,
      default: '',
    },
  },
  data() {
    this.onlyOneChild = null
    return {}
  },
  computed: {
    // ✅ 将 mixin 中的计算属性迁移到组件内
    device() {
      return this.$store.state.uiSetting.device
    },
  },
  mounted() {
    // ✅ 将 mixin 中的生命周期逻辑迁移到组件内
    this.fixBugIniOS()
  },
  methods: {
    // ✅ 修复 iOS 设备菜单点击触发 mouseleave 的 bug
    fixBugIniOS() {
      const $subMenu = this.$refs.subMenu
      if ($subMenu) {
        const handleMouseleave = $subMenu.handleMouseleave
        $subMenu.handleMouseleave = (e) => {
          if (this.device === 'mobile') {
            return
          }
          handleMouseleave(e)
        }
      }
    },
    hasOneShowingChild(children = [], parent) {
      const showingChildren = children.filter((item) => {
        if (item.hidden) {
          return false
        } else {
          this.onlyOneChild = item
          return true
        }
      })

      if (showingChildren.length === 1) {
        return true
      }

      if (showingChildren.length === 0) {
        this.onlyOneChild = { ...parent, path: '', noShowingChildren: true }
        return true
      }

      return false
    },
    resolvePath(routePath) {
      if (isExternal(routePath)) {
        return routePath
      }
      if (isExternal(this.basePath)) {
        return this.basePath
      }
      return getNormalPath(`${this.basePath}/${routePath}`)
    },
  },
}
</script>

示例 2:移除 ResizeHandler Mixin

vue
<script>
import { mapState } from 'vuex'
import { AppMain, Navbar, Sidebar, TagsView } from './components'
import store from '@/store'

const { body } = document
const WIDTH = 992 // Bootstrap 响应式断点

export default {
  name: 'Layout',
  components: {
    AppMain,
    Navbar,
    Sidebar,
    TagsView,
  },
  computed: {
    ...mapState({
      sidebar: state => state.uiSetting.sidebar,
      device: state => state.uiSetting.device,
      needTagsView: state => state.uiSetting.tagsView,
    }),
    classObj() {
      return {
        hideSidebar: !this.sidebar.opened,
        openSidebar: this.sidebar.opened,
        withoutAnimation: this.sidebar.withoutAnimation,
        mobile: this.device === 'mobile',
      }
    },
  },
  watch: {
    // ✅ 监听路由变化,移动端自动关闭侧边栏
    $route(_route) {
      if (this.device === 'mobile' && this.sidebar.opened) {
        store.dispatch('uiSetting/closeSideBar', { withoutAnimation: false })
      }
    },
  },
  beforeMount() {
    // ✅ 注册窗口大小变化监听
    window.addEventListener('resize', this.$_resizeHandler)
  },
  beforeDestroy() {
    // ✅ 清理事件监听
    window.removeEventListener('resize', this.$_resizeHandler)
  },
  methods: {
    // ✅ 判断是否为移动设备
    $_isMobile() {
      const rect = body.getBoundingClientRect()
      return rect.width - 1 < WIDTH
    },
    // ✅ 处理窗口大小变化
    $_resizeHandler() {
      if (!document.hidden) {
        const isMobile = this.$_isMobile()
        store.dispatch('uiSetting/toggleDevice', isMobile ? 'mobile' : 'desktop')

        if (isMobile) {
          store.dispatch('uiSetting/closeSideBar', { withoutAnimation: true })
        }
      }
    },
    handleClickOutside() {
      this.$store.dispatch('uiSetting/closeSideBar', {
        withoutAnimation: false,
      })
    },
  },
}
</script>

<template>
  <div :class="classObj" class="app-wrapper">
    <div
      v-if="device === 'mobile' && sidebar.opened"
      class="drawer-bg"
      @click="handleClickOutside"
    />
    <Sidebar class="sidebar-container" />
    <div :class="{ hasTagsView: needTagsView }" class="main-container">
      <div>
        <Navbar />
        <TagsView v-if="needTagsView" />
      </div>
      <AppMain />
    </div>
  </div>
</template>

✅ 升级检查清单

完成以下检查项确保升级成功:

  • [ ] 核心依赖已升级至指定版本
  • [ ] 已移除 vue-template-compiler
  • [ ] ::v-deep 已替换为 :deep()
  • [ ] Mixin 已迁移为组件内部逻辑
  • [ ] 项目构建通过
  • [ ] 关键功能测试通过

📚 参考资源

⚠️ 注意事项

  1. 渐进式升级:优先升级核心依赖和语法,业务逻辑可逐步迁移
  2. 兼容性测试:重点测试移动端响应式和侧边栏交互
  3. 后续规划:待 Vue 2.7 稳定运行后,制定 Vue 3 升级计划