Vue 升级小记

WilliamsGeh 4年前
   <p style="text-align: center;"><img alt="Vue 升级小记" src="https://simg.open-open.com/show/420d3721b42d28497cafaa3ff64e408f.jpg"></p>    <h2><strong>Vue 升级小记</strong></h2>    <p>最近接手了一个 Vue 1.0 的陈年老项目,需要将其升级到Vue 2.0。下面记录一下升级过程:</p>    <h2><strong>安装迁移工具</strong></h2>    <p>首先需要安装 <a href="/misc/goto?guid=4959755677627867970" rel="nofollow,noindex">vue-migration-helper </a>CLI 工具:</p>    <ol>     <li>控制台运行命令: <code>npm install --global vue-migration-helper</code> CLI 工具来帮助项目从Vue 1.x 迁移到 2.x。 它扫描文件以查找特定于 Vue 的代码,并对需要升级的代码提供详细的警告。 <code>vue-migration-helper</code> 的介绍说明告诉我们它大概能捕获 80% 的升级帮助信息,而不是全部。所以终端输出的帮助信息并不是完全正确的,在升级时不要盲目 <code>copy & paste</code> ,还是要根据实际情况去改写。</li>     <li>进入当前的项目:运行: <code>vue-migration-helper</code></li>    </ol>    <p>工具识别出了108 个需要升级的点:</p>    <p><img alt="Vue 升级小记" src="https://simg.open-open.com/show/29e298437ca24406358b2fe03c8ecfae.jpg"></p>    <p>由于这个古董项目不是用 <code>Vue-CLI</code> 构建的,为了避免在升级依赖上出错,我直接新起了一个 <code>Vue-CLI</code> 项目,将老项目中的业务部分进行迁移,这个是最快最能避免踩坑的解决方案(当然,这么做是因为踩坑了,所以爬起来了=_=)与当前最新的 <code>Vue-CLI</code> 的依赖版本做了对比:</p>    <ul>     <li><code>"vue": "^1.0.24"</code> to <code>"vue": "^2.5.2"</code></li>     <li><code>"vue-router": "^0.7.13"</code> to <code>"vue-router": "^3.0.1"</code></li>     <li><code>"vuex": "^2.4.0"</code> to <code>"vuex": "^3.0.1"</code></li>     <li><code>"webpack": "^2.4.1"</code> to <code>"webpack": "^3.6.0"</code></li>     <li><code>"vue-loader": "^8.5.2"</code> to <code>"vue-loader": "^13.3.0"</code></li>    </ul>    <h2><strong>升级代码</strong></h2>    <p>对于108 个需要升级的点来说,花费的时间远比想象的要多,除了根据官方文档进行迁移升级之外,运行项目还是会有很多报错,下面总结了一下改动比较多的地方。</p>    <h2><strong>1. 过滤器</strong></h2>    <p><strong>移除内置过滤器</strong></p>    <p>Vue 2.0 不再提供内置过滤器。可以创建自己的过滤器或者引入外部库如 <code>moment.js</code> , <code>accounting.js</code> 来对时间和货币等进行格式化。之前项目中用到的 <code>orderBy</code> 方法已经被弃用了,根据升级指南的建议,直接引入了 <a href="/misc/goto?guid=4959755677719910502" rel="nofollow,noindex">lodash </a>工具库,并使用计算属性重构。</p>    <pre>  <code class="language-javascript">// Vue 1.x  <div v-for="tag in productTags | orderBy 'location'">    {{ tag.tagTitle }}  </div>  // Vue 2.x  <div v-for="tag in productTags">    {{ tag.tagTitle }}  </div>    import orderBy from 'lodash/orderBy'  ...  ...  ...  computed: {    productTags: function () {      return orderBy(this.tags, 'location')    }  }  ...</code></pre>    <p><strong>过滤器参数符号变更</strong></p>    <pre>  <code class="language-javascript">// Vue 1.x  <div class="time">{{ item.appDate | date 'yyyy-MM-dd' }}</div>    // Vue 2.x  <div class="time">{{ item.appDate | date('yyyy-MM-dd') }}</div></code></pre>    <h2><strong>2. Vue Router</strong></h2>    <p>vue-router 的改动是相对来说非常大的,大部分都可以参考 <a href="/misc/goto?guid=4959755677806112491" rel="nofollow,noindex">官方文档 </a>去修改,如:</p>    <ul>     <li><code>router.go()</code> 改成了 <code>router.push()</code></li>     <li><code>router.map()</code> 被废弃,使用 <code>routes</code> 选项数组</li>     <li>使用 <code>router-link</code> 替换了 <code>v-link</code></li>     <li><code>route.refresh</code> 改成了 <code>route.meta.refresh</code></li>    </ul>    <p>对于 <code>beforeEach</code> 来说现在是异步工作的,并且携带一个 <code>next</code> 函数作为其第三个参数, <code>beforeEach</code> 经常用来设置页面的 <code>title</code> ,而 Vue 2.0 <code>to</code> 函数的使用也有一些改变,如下:</p>    <pre>  <code class="language-javascript">// Vue 1.x  router.beforeEach(({ to, next }) => {    if (to.title) {      ...    }    next()  })  // Vue 2.x  router.beforeEach((to, from, next) => {    if (to.meta.title) {     ...    }    next()  })</code></pre>    <p>对于路由挂载根实例的改动,Vue 2.0 不再会有一个特殊的 API 用来初始化包含 <code>Vue Router</code>的 <code>app</code> ,而只需要传一个路由属性给 Vue 实例,如下:</p>    <pre>  <code class="language-javascript">// Vue 1.x  router.map(routes)  router.start(Vue.extend({    store,    components: {      app: require('./app.vue')    }  }), 'body')  // Vue 2.x  new Vue({    el: '#app',    router,    store,    template: '<App/>',    components: { App }  })</code></pre>    <h2><strong>3. 生命周期</strong></h2>    <p>生命周期钩子也是这次升级中比较大的改进点,对照 <a href="/misc/goto?guid=4959755677892621995" rel="nofollow,noindex">vue1.0文档 </a>和 <a href="/misc/goto?guid=4959755677982243728" rel="nofollow,noindex">vue2.0 文档 </a>,异同如下表格:</p>    <p><img alt="Vue 升级小记" src="https://simg.open-open.com/show/e57e9af6ad67c0163bc00b68a776a0d4.jpg"></p>    <p>因此主要的改动点是:使用 <code>mounted</code> 钩子函数替换 <code>ready</code> 钩子函数</p>    <h2><strong>4. transition</strong></h2>    <p>Vue 2.0 对动画做了非常大的更新,原来的 <code>transition</code> 属性已经被彻底废弃掉了,而使用 <code>transition</code> 或 <code><transition-group></code> 来包裹元素去实现过渡效果,项目中关于动画的代码都要重新写,包括 CSS、 HTML ,还有 Javascript 钩子函数的改变。</p>    <p>在要升级的这个项目中,关于 <code>transition</code> 的升级差不多有10多个,其中大部分的改动都可以按照迁移工具去 <code>copy & paste</code> , 但是还是要清楚其中的异同,再去做修改。不然就会出现,错误提示消失了,但是动画不生效的情况。</p>    <p><strong>过渡CSS 变化</strong></p>    <p><img alt="Vue 升级小记" src="https://simg.open-open.com/show/7178a16264a552686ab420cdd412f3d2.jpg"></p>    <p>举个栗子:</p>    <pre>  <code class="language-javascript">// Vue 1.x :  <div v-if="isShow" transition="info-fade">    <span> hello world ! </span>  </div>    .info-fade-transition {    transition: all .3s ease;  }  .info-fade-enter, info-fade-leave {    opacity: 0;  }  // Vue 2.x  <transition name="info-fade">    <span v-if="isShow"> hello world ! </span>  </transition>    .info-fade-enter-active, info-fade-leave-active {    transition: all .3s ease;  }  .info-fade-enter, info-fade-leave {    opacity: 0;  }</code></pre>    <p><strong>Javascript 钩子</strong></p>    <p>Vue 2.0 <code>transitions</code> 能够通过组件应用,它们不再只是一种单独类型,因此全局的 <code>Vue.transition()</code> 方法和 <code>transition</code> 配置都被丢弃。现在可以通过组件的属性和方法配置内嵌的过渡:</p>    <pre>  <code class="language-javascript">// Vue 1.x  Vue.transition('expand', {      beforeEnter: function (el) {      el.textContent = 'beforeEnter'    },    enter: function (el) {      el.textContent = 'enter'    },   ...  })  // Vue 2.x  methods: {    // 过渡进入    // 设置过渡进入之前的组件状态    beforeEnter: function (el) {      // ...    },    // 设置过渡进入完成时的组件状态    enter: function (el, done) {      // ...      done()    },    ...  }</code></pre>    <h2><strong>5. Class 与 Style 绑定</strong></h2>    <pre>  <code class="language-javascript">// Vue 1.x  <div class="qa-item-question {{item.viewStatus === 1 ? 'isread' : ''}}"> </div></code></pre>    <p>如果使用 <code>vue-migration-helper</code> 工具,它会提示你将老的代码替换成这样:</p>    <pre>  <code class="language-javascript">// Vue 2.x  <div v-bind:class="'qa-item-question ' + item.viewStatus === 1 ? 'isread' : ''"></div></code></pre>    <p>然而由于运算符优先级问题,最后的结果可能会是 :</p>    <pre>  <code class="language-javascript"><div class=""></div></code></pre>    <p>因此,应该注意的不能完全依赖升级工具的提醒去直接 <code>copy & paste</code> 。</p>    <h2><strong>6. 双向数据绑定</strong></h2>    <pre>  <code class="language-javascript">Replace :visible.sync="xxx" with :visible="xxx", then $emit an event from the child component to trigger an update to xxx in the parent</code></pre>    <p>Vue 2.x 中,为了规范数据流动,砍掉了 <code>.sync</code> ,用来阻止子组件影响父组件所绑定的值,因为 <code>.sync</code> 破坏了单向数据流。但是很多情况下还是会需要双向绑定的,比如 <code>dialog</code> 弹窗,当关闭时,将此状态返回给父组件。</p>    <p>而 Vue 2.x 中,子组件只能被动接收父组件传递过来的数据,并在子组件内不能修改由父组件传递过来的 <code>props</code> 数据。每次父组件更新时,子组件的所有 <code>prop</code> 都会更新为最新值,因此不应该在子组件内部改变 <code>prop</code> ,如果我们尝试直接修改 <code>prop</code> 属性的值,就会有警告提示:</p>    <pre>  <code class="language-javascript">[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated:   "xxx" (found in component )</code></pre>    <p>Vue2.0 的官方文档也给出了解决方案:</p>    <p>定义一个局部变量,并用 prop 的值初始化它</p>    <p>而对于 <code>.sync</code> ,Vue 2.3 + <code>v-bind</code> 指令中将其引进,成为一枚语法糖,它会被扩展为一个自动更新父组件属性的 <code>v-on</code> 监听器。</p>    <h2><strong>7. $loadingRouteData</strong></h2>    <p>Vue 2.0 移除了 <code>$loadingRouteData</code> 钩子。如果我们需要判断页面数据是否加载完成,需要自定义一个属性(例如: isLoading)</p>    <h2><strong>8. 其他简单的改动</strong></h2>    <ul>     <li>v-for      <ul>       <li><code>track-by</code> to <code>:key</code></li>       <li>废弃了 <code>$index</code></li>      </ul> </li>    </ul>    <p> </p>    <ul>     <li>HTML 的计算插值 <code>{{{ foo }}}</code> 已经移除,取代的是 <code>v-html</code> 指令</li>     <li><code>v-el</code> 和 <code>v-ref</code> 合并成 <code>ref</code> 属性</li>     <li>在 Vue 的实例中不能使用 <code>Vue.set</code> 和 <code>Vue.delete</code></li>     <li>使用 <code>@click.native</code> 监听根元素的原生事件, <code>@click</code> 传的只是一个方法</li>     <li>废弃了 <code>Array.prototype.$set/$remove</code> ,用 <code>Vue.set</code> 或者 <code>Array.prototype.splice</code> 取代</li>    </ul>    <h2><strong>9. 其他报错</strong></h2>    <p>浏览器报错:</p>    <pre>  <code class="language-javascript">[Vue warn]: Do not use built-in or reserved HTML elements as component id: dialog</code></pre>    <p>因为 <code>dialog</code> 在 HTML5 里面是个原生的标签解决方法:重命名 <code>components</code> 里面组件的名称</p>    <h2><strong>总结</strong></h2>    <p>上面只是简单梳理了一下该项目在升级时遇到的一些问题,但并不是所有。单单是升级 <code>webpack</code> ,其实要修改的点就有很多,但是对于陈年的经过 N 手的老项目来说,并不适合直接升级,可能会造成项目更加混乱,因此这里走了一点捷径,绕过了很多升级 <code>webpack</code> 会遇到的坑。升级遇到的大部分问题官方文档都有详细的描述,遇到问题,沉着冷静别惊慌,我们的目标是:远离 bug,不受伤。。。</p>    <p>来自:<a href="https://zhuanlan.zhihu.com/p/31436018?utm_source=tuicool&utm_medium=referral">https://zhuanlan.zhihu.com/p/31436018</a></p>    <p> </p>