1、动态添加class或style
有时候希望根据某个状态值来决定是否添加某个类,可以这样:

  1. // 数组语法
  2. <div :class="['a',value==true?'b':'']"> //类a总是存在,当value为真时类b存在
  3. // 对象语法
  4. <div :class="{'a':true,'b':value}"> //类a,b都由value控制
  5. <div :style="{background:'url('+value.url+')'}"> //对于style,最好用对象语法,属性值取自当前vue实例

2、绑定事件函数

有两种方式:

  1. // 方式1
  2. <div @click='handle'></div> //handle函数的this指向当前vue实例,并且会将event对象传入handle作第一个参数,通常可用event.target获取被点击的DOM元素
  3. // 方式2
  4. <div @click='handle("param1",$event)'></div> //handle函数的this指向当前实例,也可将$event传入handle,作用同上,一般用来获取被点击的DOM元素

3、关于箭头函数

vue的一些配置项,如mounted,beforeRouteEnter,beforeRouteLeave等,本身是一个函数,若使用箭头函数语法,则其内的this对象不是当前vue实例。因此这三个配置项不要用箭头函数,其它的配置项,如methods,computed,watch等接受对象作为配置的,其内的函数最好用箭头函数,另外setTimeout(fn,time)中的fn最好也用箭头函数。

4、数据传递

1、父子组件(prop down,event up)

  • 一般而言,父组件通过prop传递属性给子组件,子组件事先需定义props属性,如果没有定义props,那这个传递过来的值会自动添加到子组件的根元素上;
  • 子组件中一般通过触发某种自定义事件向父元素通信,因此一般在父元素中使用子组件的地方监听该事件名,该事件名是自定义的,因此要与子组件的一致。
  1. <div id="counter-event-example">
  2. <p>{{ total }}</p>
  3. <button-counter v-on:increment="incrementTotal"></button-counter>
  4. <button-counter v-on:increment="incrementTotal"></button-counter>
  5. </div>
  1. Vue.component('button-counter', {
  2. template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
  3. data: function () {
  4. return {
  5. counter: 0
  6. }
  7. },
  8. methods: {
  9. incrementCounter: function () {
  10. this.counter += 1
  11. this.$emit('increment')
  12. }
  13. },
  14. })
  15. new Vue({
  16. el: '#counter-event-example',
  17. data: {
  18. total: 0
  19. },
  20. methods: {
  21. incrementTotal: function () {
  22. this.total += 1
  23. }
  24. }
  25. })
  • 如果要在父元素中使用子组件的地方监听原生事件,可用@click.native
  • 普通html标签上用@click是原生事件,自定义组件中用@click是自定义事件

2、非父子组件
有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:

  1. var bus = new Vue()
  2. // 触发组件 A 中的事件
  3. bus.$emit('id-selected', 1)
  4. / 在组件 B 创建的钩子中监听事件
  5. bus.$on('id-selected', function (id) {
  6. // ...
  7. }.bind(this))//绑定作用域

5、不能触发视图更新的操作

  • 数组
  1. // 无法触发
  2. vm.items[indexOfItem] = newValue
  3. vm.items.length = newLength
  4. // 可以触发
  5. Vue.set(example1.items, indexOfItem, newValue)
  6. example1.items.splice(newLength)
  • 对象
  1. var vm = new Vue({
  2. data: {
  3. a: 1
  4. }
  5. })
  6. // `vm.a` 现在是响应式的
  7. vm.b = 2
  8. // `vm.b` 不是响应式的
  9. var vm = new Vue({
  10. data: {
  11. userProfile: {
  12. name: 'Anika'
  13. }
  14. }
  15. })
  16. Vue.set(vm.userProfile, 'age', 27) //响应式

3、watch对象变化:

  1. 'messages': { // messages是一个数组,监听数组或对象的变化
  2. handler: function(newValue, old) {
  3. if (newValue.length == 0) {
  4. setTimeout(() => {
  5. history.back();
  6. }, 1000)
  7. }
  8. },
  9. deep: true
  10. }

6、关于beforeRouteEnter和beforeRouteLeave

  • beforeRouteEnter(to,from,next) 无法访问当前vue实例,一般用于从localStorage中读取数据恢复页面,比如从页面a到b,在从b回到a,若a有数据变化(ajax拉取)就更新页面,否则不更新;或者还原当前滚动条位置
  • beforeRouteLeave(to,from,next) 可访问当前vue实例,一般用于页面离开时将某些状态保存在localStorage中,例如当前滚动条位置
  • 二者都必须显示调用next函数

7、keep-alive和router-view混合使用

  1. // 这是目前用的比较多的方式
  2. <keep-alive>
  3. <router-view v-if="$route.meta.keepAlive"></router-view>
  4. </keep-alive>
  5. <router-view v-if="!$route.meta.keepAlive"></router-view>
  6. ...
  7. routes: [
  8. { path: '/', redirect: '/index', component: Index, meta: { keepAlive: true }},
  9. {
  10. path: '/common',
  11. component: TestParent,
  12. children: [
  13. { path: '/test2', component: Test2, meta: { keepAlive: true } }
  14. ]
  15. }
  16. ....
  17. // 表示index和test2都使用keep-alive

若有三个页面A->B->C,希望A->B时B拉新数据;C返回B时B不拉新数据,且保存B到C之前的位置,该如何做?
1、给B设置keepAlive:true;
2、在B的beforeRouteEnter中做获取数据的操作,该钩子的三个参数可以判断B的上一个页面,从而决定是否拉数据;
在B的beforeRouteLeave里用localStorage记录位置,并清除和页面展现相关的数据状态;
3、在接口回调里最好用EventCenter这种观察者模式来触发数据获取成功的事件
4、在B的mounted钩子里接受数据获取成功的事件,就可以拿到数据
5、在B的activated钩子里取localStorage里的位置

8、router-link常用方式

  1. <!-- 命名的路由 -->
  2. <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
  3. <!-- 带查询参数,下面的结果为 /register?plan=private -->
  4. <router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>
  5. //对应js
  6. this.$route.params.userId
  7. this.$route.query.plan

9、过度动画

  • 单元素过渡
    transition包一个含有v-if或v-show指令的元素就行
    Vue项目经验 - 图1
  • 多元素过渡
    transition下只能有一个根元素,所以多元素时必须用v-if,v-else来保证只有一个,此外,多个相同标签时,最好给每个标签设置key
  • 多组件过渡
    用动态组件即可
  • 列表过渡
    需用transition-group组件包裹,被包裹元素必须设置key,其他和单元素一样

10、数据拉取

  • 数据获取操作最早可以放在每个组件的beforeRouteEnter里,然后利用观察者模式把数据传到beforeMount钩子函数里做逻辑操作
  1. beforeRouteEnter(to, from, next) {
  2. let feed_id = to.params.id;
  3. if (from.name != 'CommentDetail' && feed_id) {
  4. getArticleDetail({
  5. feed_id: feed_id
  6. }).then(res => EventCenter.$emit('renderArticle', res));
  7. getComment({
  8. feed_id: feed_id
  9. }).then(res => EventCenter.$emit('renderComment', res));
  10. }
  11. next();
  12. }
  13. beforeMount(){
  14. // 1、渲染文章详情
  15. EventCenter.$on('renderArticle', res => {
  16. });
  17. // 2、渲染文章评论
  18. EventCenter.$on('renderComment', res => {
  19. })
  20. }

11、插件 or 组件

对于全局使用频率较高的最好写成插件,而不是组件。例如loading,toast之类,若写成组件,则需要在每个父组件中引入该组件,比较麻烦,写成插件就可以直接调用

  1. import $ from 'n-zepto';
  2. import Vue from 'vue';
  3. export default {
  4. install() {
  5. var timer = null;
  6. Vue.prototype.$alerTip = function(text, delay, options) {
  7. var defaultCssObject = {
  8. position: 'fixed',
  9. display: 'flex',
  10. alignItems: 'center',
  11. justifyContent: 'center',
  12. zIndex: 10000,
  13. minWidth: '1.5rem',
  14. margin: '0 auto',
  15. left: 'calc((100% - 1.5rem) / 2)',
  16. bottom: '1.4rem',
  17. borderRadius: '.1rem',
  18. height: '.5rem',
  19. lineHeight: '.5rem',
  20. background: '#000',
  21. opacity: 0.85,
  22. color: 'white',
  23. };
  24. var cssObject = options || defaultCssObject;
  25. var temp = `<div class="v-toast">${text}</div>`;
  26. var $div = $(temp);
  27. $div.css(cssObject);
  28. $('body').append($div);
  29. clearTimeout(timer);
  30. timer = setTimeout(() => {
  31. $('.v-toast').remove();
  32. timer = null;
  33. }, delay);
  34. }
  35. }
  36. }

12、vue父子组件生命周期执行顺序问题

父子组件钩子执行顺序:
father created
father before mounted
son created
son before mount
son mounted
father mounted
所以,你在父组件 beforeMounted 中已初始化的值一定可以通过props下发到子组件

13、vue-router切换页面滚动条位置问题

注意:scrollBehavior只有在history.pushState可用时才有效,而该方法仅兼容到IE10(移动端可放心使用)

  1. const router = new VueRouter({
  2. routes,
  3. scrollBehavior (to, from, savedPosition) {
  4. if (savedPosition) {
  5. return savedPosition
  6. } else {
  7. if (from.meta.keepAlive) { // 通过meta属性精细控制
  8. from.meta.savedPosition = document.body.scrollTop;
  9. }
  10. return { x: 0, y: to.meta.savedPosition || 0 }
  11. }
  12. }
  13. })

14、vue库的引入问题

vue提供众多版本库文件,在使用*.vue单文件组件的开发模式时,只需引入vue.runtime.min.js即可,该库体积最小,不过要注意初始化vue实例时,不能用template选项,而应该换为render,其他组件中的template会被vue-loader转化为类似h=>h(Component)这种形式,本质上是将模板编译的时机从运行时转移到了编译时(只有初始化时不能用template选项,因为vue-loader 和 vue 都无法识别)

  1. new Vue({
  2. el: '#app',
  3. router,
  4. render: h => h(App)
  5. })
  6. // webpack对应修改:
  7. alias: {
  8. 'vue$': 'vue/dist/vue.runtime.min.js'
  9. }

15、三种路由钩子执行顺序问题


全局:beforeEach,beforeResolve,afterEach
组件:beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave
路由:beforeEnter
调用要离开路由的组件守卫beforeRouteLeave
调用全局前置守卫:beforeEach
在重用的组件里调用 beforeRouteUpdate
调用路由独享守卫 beforeEnter。
解析异步路由组件。
在将要进入的路由组件中调用beforeRouteEnter
调用全局解析守卫 beforeResolve
导航被确认。
调用全局后置钩子的 afterEach 钩子。


16、mixins 的合并策略


语法是:mixins:[Component1,Component2],其中每个Component的选项和正常的Vue选项一致
1、对于 data 选项,采取合并的策略,同名属性以组件为主
2、对于同名钩子函数将合并为一个数组,都将被调用。且混入对象的钩子将在组件自身钩子之前调用
3、对于值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对


17、常用的全局API

  • Vue.extend(ops)
    使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象
  • Vue.use( plugin )
    安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入
  • Vue.nextTick( [callback, context] )
    在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

18、其他

  • 做下拉刷新时,如果你的列表依赖于data,data里的数据依赖好几个接口,那么在刷新时不要直接更改data,而应该做一个临时变量data_tem,让data_tem变化,等data_tem稳定后再赋值给data。这样可以有效避免页面闪烁
  • provide / inject
    这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

性能优化

  • 从vendor中提取框架js文件,改为由CDN加载
    优化前:
    Vue项目经验 - 图2
    文件组成:
    Vue项目经验 - 图3

优化后:
Vue项目经验 - 图4

Vue项目经验 - 图5
vendor.js文件减少了约50%
具体做法:
1、在webpack.base.conf.js中添加externals字段,如下:

  1. externals: {
  2. 'vue': 'Vue', //key为业务代码中引入vue时的模块名,value为使用CDN加载的js文件中的全局变量
  3. 'vue-router': 'VueRouter'
  4. },

2、在html中引入vue,vue-router的cdn链接

  1. <script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
  2. <script src="https://cdn.bootcss.com/vue-router/2.7.0/vue-router.min.js"></script>

3、注释掉 Vue.use(Router)之类的代码(待定)

  • 页面级组件按需加载,用webpack打成异步组件
    比如有5个页面级组件,那么在主app.js里只用留主页面组件,其他四个可以按需加载
  1. {
  2. path: '/detail/:id',
  3. name: 'Detail',
  4. // component: Detail
  5. component: resolve => {
  6. // require.ensure([], () => resolve(require('@/pages/detail')), 'Detail');
  7. require.ensure([], () => resolve(require('@/pages/detail-h5')), 'Detail');
  8. }
  9. }

这样可以减小app.js文件大小

效果:
优化前:
Vue项目经验 - 图6
优化后:
Vue项目经验 - 图7