vue1升级vue2踩坑指南
升级原因
15年的老项目,还是使用的fis3来构建项目。去年底换到mac开发后在macos12环境下无法使用热更新,每次需要保存后重新暂停服务再启动服务很费时间。。。。且fis3官方已经不再维护相关组件也已经很兼容了,,,寻找解决方案无果后长痛不如短痛,直接换webpack吧
版本选择
最终vue版本为2.1.0此版本刚好可以使用vue-loader10.0.1版本其他的似乎都会报错。vue-template-compiler和vue版本对应即可
老的package.json:
{
"name": "faq",
"version": "1.0.0",
"description": "description",
"main": "main.js",
"dependencies": {
"normalize.css": "^4.0.0",
"v-viewer": "^1.6.3",
"viewerjs": "^1.10.2",
"vue": "^1.0.16",
"vue-i18n": "^4.7.1",
"vue-image-swipe": "^1.0.5",
"vue-resource": "^0.7.4",
"vue-router": "^0.7.11",
"vue-toasted": "^1.1.28",
"vue-touch": "^1.0.2",
"vue-validator": "^2.0.0",
"vueify-insert-css": "^1.0.0"
},
"devDependencies": {
"babel-plugin-add-module-exports": "^0.1.2",
"babel-plugin-transform-es3-member-expression-literals": "^6.8.0",
"babel-plugin-transform-es3-property-literals": "^6.8.0",
"babel-plugin-transform-runtime": "^6.1.2",
"babel-preset-es2015": "^6.5.0",
"buffer": "^4.5.1",
"fis-parser-babel": "xiangshouding/fis-parser-babel",
"fis-postprocessor-autoprefixer": "0.0.5",
"fis3-command-inspect": "^1.0.3",
"fis3-deploy-replace": "^1.0.2",
"fis3-deploy-skip-packed": "0.0.3",
"fis3-hook-commonjs": "^0.1.10",
"fis3-hook-node_modules": "^1.0.11",
"fis3-hook-npm": "^1.0.1",
"fis3-hook-relative": "^1.0.6",
"fis3-parser-vue": "^0.1.0",
"fis3-postpackager-loader": "^1.3.13",
"is-buffer": "^1.1.3",
"process": "^0.11.2",
"vue-hot-reload-api": "^1.3.2"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "fis3 release -wL",
"build-test": "rm -rf ./dist/test && fis3 release test",
"build": "rm -rf ./dist/prod && fis3 release prod"
},
"author": "author"
}
新的package.json:
{
"name": "faq",
"version": "1.0.1",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@babel/preset-env": "^7.18.2",
"babel-eslint": "^10.1.0",
"core-js": "^3.8.3",
"fastclick": "^1.0.6",
"jquery": "^3.6.0",
"normalize.css": "^4.0.0",
"url-loader": "^2.3.0",
"v-viewer": "^1.6.4",
"vee-validate": "^2.2.15",
"vue": "^2.1.0",
"vue-i18n": "^5.0.3",
"vue-loader": "^10.0.1",
"vue-resource": "^0.7.4",
"vue-router": "^3.5.0",
"vue-touch": "^2.0.0-beta.4"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.1.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
vue官方说明
vue升级指南
从官方的说明中可以看出变化其实还是(不多的) 很多的…
语法/组件使用变化
这里只赘述我遇到的问题了,我没遇到的我就不写了
ready钩子
ready钩子移除了 可以替换为mounted钩子 效果不变
$index & $key
<!-- vue1写法 -->
<div v-for="item in items">
{{$index}}
<div>
<!-- vue2写法 -->
<div v-for="(item, index) in items">
{{index}}
<div>
limitBy
<!-- vue1写法 -->
<p v-for="item in items | limitBy 10">{{ item }}</p>
<!-- vue2写法 也可以选择官方那种computed写法-->
<p v-for="item in items.slice(0, 10)">{{ item }}</p>
v-html
<!-- vue1写法 -->
<p>{{{ data }}}</p>
<!-- vue2写法 -->
<p v-html="data" />
如果之前有使用过滤器的话 也要改一下写法
vue2似乎对html的模板也有要求,之前是可以在template渲染的,现在必须为真实渲染的标签div,span等
<!-- vue1写法 -->
<p>{{{ data | highlight keyword}}}</p>
<!-- vue2写法 -->
<p v-html="$options.filters.highlight(data, keyword)" />
events
events钩子移出了,我选择使用eventbus去注册监听事件 使用eventbus时要注意及时销毁,不然会重复注册
#main.js
window.eventHub = new Vue()
#faq.vue
mounted(){
eventHub.$on('search', function(keyword){
....
})
},
beforeDestroy(){
eventHub.$off('search', function(){ })
}
#search.vue
eventHub.$emit('search', keyword)
vue-router
vue-router我选择直接使用3版本了,和2的变化不大反正都是从0.7升。。
redirect
router.redirect废弃了,要在注册路由时直接声明
{
path: '/',
redirect: '/faq'
}
注册路由&动态配置路由
我们项目本来使用了很多动态修改redirect的地方,本来想着可以通过router.addRoute动态去修改但是事实并不是像文档所说 而是会报一个重复定义路由的错误
所以这里我采用了(伪)动态路由的方法去实现,思路就是需要改变redirect时重置路由再赋值即可 虽说性能不好,但是实现功能还是没问题的!
import Vue from 'vue'
import VueRouter from 'vue-router'
import hotspotView from '../view/hotspot.vue'
import faqView from '../view/faq.vue'
import chatView from '../view/chat.vue'
import chooseIssueTypeView from '../view/choose-type.vue'
import createIssueView from '../view/create-issue.vue'
import messageListView from '../view/message-list.vue'
import messageView from '../view/message.vue'
import coinHistoryView from '../view/coin-history.vue'
import esHistoryView from '../view/es-history.vue'
import questionListView from '../view/question-list.vue'
import faqQuestionListView from '../view/faq-all-question.vue'
import cateTypeView from '../view/cate-type.vue'
import cateListView from '../view/cate-list.vue'
import cateDetailView from '../view/cate-detail.vue'
import helpView from '../view/help.vue'
import elementsView from '../view/elements.vue'
import elementsDetailView from '../view/elements-detail.vue'
import texiaoView from '../view/texiao.vue'
import texiaoDetailView from '../view/texiao-detail.vue'
import daojuView from '../view/daoju.vue'
import daojuDetailView from '../view/daoju-detail.vue'
import elegroup from '../view/elegroup'
import esTransferPlatform from '../view/es-transfer-platform.vue'
import defaultRealname from '../view/default-realname.vue'
const routerView = {
template: '<router-view keep-alive />'
}
const routerViewWithoutKeepAlive = {
template: '<router-view/>'
}
Vue.use(VueRouter)
const baseRoutes = [
{
path: '/faq',
component: routerView,
children: [
{
path: '',
name: 'faq',
component: faqView
}, {
path: 'faq-all-question',
name: 'faq-all-question',
component: faqQuestionListView
}
]
},
{
path: '/robot',
name: 'chat',
component: chatView
},
{
path: '/help',
name: 'help',
component: helpView
}
]
window.asyncRoutes = [
{
path: '/hotspot',
component: routerView,
children: [
{
path: '',
name: 'hotspot',
component: hotspotView
},
{
path: 'question-list/:index',
name: 'question-list',
component: questionListView
}
]
},
{
path: '/',
name: 'home',
redirect: '/faq'
},
{
path: '/create-issue',
component: routerViewWithoutKeepAlive,
children: [
{
path: '',
name: 'issue-type',
component: chooseIssueTypeView
}, {
path: ':issueType',
component: createIssueView
}
]
}
]
const createRouter = () => new VueRouter({
routes: baseRoutes
})
export const router = createRouter()
// 先执行一遍添加 后续每次重置即可
for (let i = 0; i < window.asyncRoutes.length; i++) {
router.addRoute(window.asyncRoutes[i])
}
// 这地方为了提高可用性参数要传数组
export function addFaqRouter(datas) {
datas.forEach(data => {
for (let ii = 0; ii < window.asyncRoutes.length; ii++) {
var item = window.asyncRoutes[ii]
if (item && item.name && item.name == data.name) {
Object.keys(data).forEach(k => {
window.asyncRoutes[ii][k] = data[k]
})
} else if (item.children) {
for (let jj = 0; jj < item.children.length; jj++) {
var itemjj = item.children[jj]
if (itemjj && itemjj.name && itemjj.name == data.name) {
Object.keys(data).forEach(k => {
window.asyncRoutes[ii]['children'][jj][k] = data[k]
})
}
}
}
}
})
const newRouter = createRouter()
router.matcher = newRouter.matcher
for (let i = 0; i < window.asyncRoutes.length; i++) {
router.addRoute(window.asyncRoutes[i])
}
}
重置路由操作
import { addFaqRouter } from '@/router'
addFaqRouter([{
path: '/',
name: 'home',
redirect: '/create-issue/default'
}, {
path: '/create-issue',
name: 'create-issue',
redirect: '/create-issue/default'
}])
v-link
<!-- vue1写法 -->
<a class="faq" v-link="{ name: 'faq'}">{{$t('link.faq')}}</a>
<!-- vue2写法 -->
<router-link :to="{name:'faq'}" class="faq">
{{ $t('link.faq') }}
</router-link>
监听路由
# data和deactivate移除掉了
<!-- vue1写法 -->
route: {
data({ to: { query: { index } } }) {
console.log(index)
},
deactivate() {
console.log("deactivate")
}
}
<!-- vue2写法 -->
mounted(){
let index = this.$route.query.index;
},
beforeDestroy() {
console.log("deactivate")
}
vue-validator
vue-validator神坑。。直接不支持vue2了。各个版本几乎都试过了在vue2上无论如何都跑不起来不是这错就是那错,最后选择使用vee-validate来替代了,使用后感觉相见恨晚啊。比vue-validator要好用不少!
全局注册绑定vee-validate & 报错信息支持多语言
main.js
import VeeValidate from 'vee-validate'
Vue.use(VeeValidate, { events: 'blur|change|input' })
validate.js
const dictionary = {
en: {
messages: {
required: () => _faq.$t('form.requiredError'),
min: (_, params) => _faq.$t('form.contentRequiredError', { count: params[0] }),
phone: (_, params) => _faq.$t('form.phoneError'),
decimal: (_, params) => _faq.$t('form.numberError')
}
}
}
const veeExtends = {
phone: {
validate: (value) => {
return /^1[3456789][0-9]\d{8}$/.test(value)
},
getMessage: () => _faq.$t('form.phoneError')
}
}
export default { dictionary, veeExtends }
由于要多语言支持,所以需要在vm加载后调用不然会报$t的错误,我目前在app.vue执行
import Vue from 'vue'
import vee from '@/assets/js/app/validate'
import { Validator } from 'vee-validate'
created(){
window._faq = this
Validator.localize(vee.dictionary)
for (const extend in vee.veeExtends) {
Validator.extend(extend, vee.veeExtends[extend])
}
}
vue-validator和vee-validate的简单使用对比
vue-validator
<span>{{ $validation['test'] && $validation['test'].errors | firstError }}</span>
<validator name="validation">
<input type="text" :placeholder="placeholder"
v-model="content.test" field="test" v-validate="{ minlength: { rule: 6,
message: $t('form.contentRequiredError', { count: 6 }) },
required: true
}"
>
</validator>
vee-validate
{{ errors.first('test')}}
<input v-model="content.test" v-validate="required|min:6" type="text"
:placeholder="placeholder" name="test" >
是不是vee-validate更简单一点,且不需要重复定义错误信息初始化一次即可。
vue-i18n
如果之前使用的5.0.3之前的版本就建议切换到5.0.3此版本刚好支持到vue2.2。注册方式不变,Vue.local和vue.config.lang也可以正常使用!强烈推荐!!
最后
虽说vue本身的变化并不多,但是项目使用的组件适配性以及他们相关的变化加起来还是有很客观的工作量。。本来我还认为我们这是个小项目,现在我不认为了