2

书写规范

  • prop 必须指定 type

    {
      // ...
      props: {
        title: String,
        likes: Number,
        isPublished: Boolean,
        commentIds: Array,
        author: Object
      }
      //...
    }

    对于需要默认值的prop,建议指定其默认值

项目构建

  • vue-cli 安装中找不到vue命令

这是在ubuntu系统中遇到的,全局安装后可以

npm install -g  vue-cli

图片描述

Vue 组件间通信

  • 父子 props/event $parent/$children ref provide/inject
  • 兄弟 bus vuex
  • 跨级 bus vuex provide.inject

    跨层级的组件间通讯可以使用 eventbus
    但是要注意两点:
    event 会注册到全局,要注意模块化
    注意销毁,不然会使用两次
1、在vue中子组件为何不可以修改父组件传递的Prop, 如果修改了,2、Vue是如何监控到属性的修改并给出警告的
  • 单向数据流,易于监测数据的流动,出现了错误可以更加迅速的定位到错误发生位置
    每当父组件属性值修改时, 该值都将被覆盖。
    ——> 引申出一个问题:

    如果在子组件需要修改prop呢?
- 通过事件修改父组件状态
- 或者子组件把prop赋给data()或者comuted, 
  • 如果修改了:

Vue 是如何监控到属性的修改并给出警告的

单向数据流

封装Vue组件的一些技巧
vue 最早是双向数据流,子组件可以改变 props ,之后因为引发很多问题而改成单向数据流。现在在子组件内触发修改会导致报错。
以函数透传的形式实现控制子组件修改父组件状态不是一个好办法。

传统实现还是 v-model

但是,v-model 里子组件不能直接改变父组件状态,而是要通过事件,本质上依然是单向数据流。

provide 是非响应式的

Vue provide inject 实现响应式

webpack

https://vuejs-templates.githu...

打包优化

优化vue项目打包速度(webpack)

懒加载(按需加载路由)

webpack 中提供了 require.ensure()来实现按需加载。以前引入路由是通过 import 这样的方式引入,改为 const 定义的方式进行引入。

  • 不进行页面按需加载引入方式:
import  home   from '../../common/home.vue'
  • 进行页面按需加载的引入方式:
const  home = r => require.ensure( [], () => r (require('../../common/home.vue')))

https://blog.csdn.net/qq_2762...

slot

slot 在ui库中有广泛的应用

  • <slot></slot>标签,简单来说就是占位符,它会帮你占好位置,等你需要的时候直接将html传入,它会帮你显示出来。
  • 也有人说:props可以将数据从父组件传入子组件,slot可以将html从父组件传入子组件。那么如何实现呢?

    单个插槽:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <script type="text/javascript" src="vue.min.js"></script>
    </head>
    <body>
        <div id="app">
            <h1>我是父组件的标题</h1>
            <my-component>
                <p>这是一些初始内容</p>
                <p>这是更多的初始内容</p>
            </my-component>
        </div>
        <script type="text/javascript">
            Vue.component('my-component', {
              // 有效,因为是在正确的作用域内
              template: '<div>\
                            <h2>我是子组件的标题</h2>\
                            <slot>只有在没有要分发的内容时才会显示。</slot>\
                        </div>',
              data: function () {
                return {
                  
                }
              }
            });
            new Vue({
                el:'#app',
                data:{
                    msg:'你好啊'
                }
            })

        </script>
    </body>
</html>

组件中的模板中写入slot标签,在父级调用子组件的时候传入html即可。

具名插槽

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <script type="text/javascript" src="vue.min.js"></script>
    </head>
    <body>
        <div id="app">
            <my-component>
                  <h1 slot="header">这里可能是一个页面标题</h1>

                  <p>主要内容的一个段落。</p>
                  <p>另一个主要段落。</p>

                  <p slot="footer">这里有一些联系信息</p>
            </my-component>
        </div>
        <script type="text/javascript">
            Vue.component('my-component', {
              // 有效,因为是在正确的作用域内
              template: '<div class="container">\
                          <header>\
                            <slot name="header"></slot>\
                          </header>\
                           <main>\
                            <slot></slot>\
                          </main>\
                          <footer>\
                            <slot name="footer"></slot>\
                          </footer>\
                        </div>',
              data: function () {
                return {
                  
                }
              }
            });
            new Vue({
                el:'#app',
                data:{
                    msg:'你好啊'
                }
            })

        </script>
    </body>
</html>

具名插槽,顾名思义当有多个slot标签时,你需要给他们起个自己的名字,在父组件调用时需要slot="内部的对应名字",当存在没有命名的slot标签时,父级组件传入的默认html代码将全部输出在无名的slot标签中。

作用域插槽

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <script type="text/javascript" src="vue.min.js"></script>
    </head>
    <body>
        <div id="app">
             <!-- 在父级中,具有特殊特性 slot-scope 的 <template> 元素必须存在,表示它是作用域插槽的模板。slot-scope 的值将被用作一个临时变量名,此变量接收从子组件传递过来的 prop 对象: -->
             <child>
                <template scope="props">
                  <span>hello from parent</span><br>
                  <span>{{ props.text }}</span>
                </template>
            </child>
        </div>
        <script type="text/javascript">
            // 在子组件中,只需将数据传递到插槽,就像你将 prop 传递给组件一样:
            Vue.component('child',{
               template:'<ul>' +
                            '<slot text="hello from child"></slot>' +
                         '</ul>',
                data:function(){
                  return {

                  }
                },
            });
            new Vue({
                el:'#app',
                data:{
                    msg:'你好啊'
                }
            })

        </script>
    </body>
</html>

https://codepen.io/AlexZ33/pe...

作用域插槽是一种特殊类型的插槽,用作一个 (能被传递数据的) 可重用模板,来代替已经渲染好的元素。
其实就是相当于,在父组件中可以获取到子组件传递出来的props对象,从而渲染到父组件上。

watch

Vue开发——watch监听数组、对象、变量
这里要注意的是对数组的watch

clipboard.png
vue——数组的深度监听

组件 v-if 和 v-show 切换时生命周期钩子的执行

v-if
初始渲染
初始值为 false 组件不会渲染,生命周期钩子不会执行,v-if 的渲染是惰性的。
初始值为 true 时,组件会进行渲染,并依次执行 beforeCreate,created,beforeMount,mounted 钩子。
切换
false => true
依次执行 beforeCreate,created,beforeMount,mounted 钩子。
true => false
依次执行 beforeDestroy,destroyed 钩子。
v-show
渲染
无论初始状态,组件都会渲染,依次执行 beforeCreate,created,beforeMount,mounted 钩子,v-show 的渲染是非惰性的。
切换
对生命周期钩子无影响,切换时组件始终保持在 mounted 钩子。

指令

v-cloak

vue v-cloak 解决页面加载时闪烁出现vue标签或者指令的问题

自定义指令(directive)

v-Throttle

export default function (Vue) {
  Vue.directive('throttle', {
    bind: (el: Node, binding: {
      value: number;
    }) => {
      let throttleTime = binding.value // 防抖时间
      if (!throttleTime) {
        // 用户若不设置防抖时间,则默认2s
        throttleTime = 2000
      }
      let cbFun: number | null
      el.addEventListener(
        'click',
        (event: Event) => {
          if (!cbFun) {
            // 第一次执行
            cbFun = setTimeout(() => {
              cbFun = null
            }, throttleTime)
          } else {
            event && event.stopImmediatePropagation()
          }
        },
        true
      )
    }
  })
}

v-WaterMark

// chart/WaterMark.js
export default class WaterMark {
  constructor (userNm, userCd) {
    this.userNm = userNm
    this.userCd = userCd
  }
  draw () {
    let canvas = document.createElement('canvas')
    let ctx = canvas.getContext('2d')
    canvas.width = 250
    canvas.height = 130
    ctx.translate(canvas.width / 2, canvas.height / 2 - 35)
    ctx.rotate(-Math.PI / 12)
    ctx.font = '700 12px/Mircosoft Yahei'
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'
    ctx.fillStyle = '#eaf0f5'
    // ctx.fillStyle = '#f00'
    ctx.fillText(`镜心的小树屋 ${this.userNm} ${this.userCd}`, 0, 0)
    return canvas.toDataURL('image/png')
  }
}
// rule.js
/**
 * 用户信息相关
 */
exports.useInfor = {
  data: {
    name: '',
    userCd: '',
    userNm: ''
  },
  clear: function () {
    this.data = {
      name: '',
      userCd: '',
      userNm: '',
      flag: false
    }
  }
}

// directives/WaterMark.js
import WaterMark from '../chart/WaterMark.js'
import { useInfor } from '../rule'

export default {
  inserted (el, binding) {
    let timer = setInterval(() => {
      if (useInfor.data.userNm && useInfor.data.userCd) {
        let wMark = new WaterMark(useInfor.data.userNm, useInfor.data.userCd)
        let imageSrc = wMark.draw()
        if (binding.value && binding.value === false) return
        el.style.background = `#fff url(${imageSrc}) repeat top left`
        clearInterval(timer)
      }
    }, 50)
  }
}

clipboard.png

v-check

v-touch.tap

clipboard.png

clipboard.png

v-focus

// https://cn.vuejs.org/v2/guide/custom-directive.html#%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0
// 注册一个全局自定义指令 'v-focus'
export default {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
}

路由之间跳转

  • 声明式(标签跳转)
  • 编程式( js 跳转) router.push('index')

clipboard.png

页面刷新

最近遇到一个需求,需要刷新当前页面来更新数据
vue 无痕刷新

组件局部刷新

【BUG】iview多选框插件默认选中有问题,局部刷新后会取消选中状态?

clipboard.png

clipboard.png

clipboard.png

clipboard.png

clipboard.png

[Vue warn]: Invalid prop: type check failed for prop "value". Expected String, Number, got Undefined. Vue出现该错误的解决办法

https://www.cnblogs.com/sifo/...

iview样式修改不生效

在使用vue框架iview是经常需要在组件里面修改某一个样式,为了防止组件之间的样式污染,我们需要使用scoped来限制在当前组件内生效,但往往样式会无法生效。我之前的解决方案是在当前这个组件的父级加一个只有当前组件才有的class然后早style没有scoped中去写样式,这样既可以改变iview自带的样式,也可以人为去限制组件之间的样式污染,这也是当时没有办法下自己想到的唯一的办法。最近又发现一个新的解决方案/deep/深度选择器 ->戳这里

clipboard.png

.reset-sub-modal /deep/ .ivu-modal-footer
    padding-bottom: 25px

clipboard.png

生命周期

图片描述

https://www.cnblogs.com/golov...

vue中的dom异步更新及$nextTick()

clipboard.png

1、dom异步更新的原理

观察到数据变化,vue开启一个队列,并缓冲同一事件循环的所有数据变化;
如果同一个watcher被多次触发,只会被推入队列中一次;(去重,避免不必要计算和DOM操作)
在下一个事件循环tick中,vue刷新队列并执行已去重的工作;

2、vue.nextTick()

因此,改数据(eg vm.someData = ’88’)时,组件不会马上更新,而是在vue下一个tick刷新队列时更新;
为了在数据变化后,要在vue的dom状态更新后做什么,可在数据变化后调用vue.nextTick(callback),则dom更新完成后会调用回调函数;

3、nextTick的使用场景

Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中;
在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中;

更多原理和示例戳这里

内置组件component

文档
简单示例: https://jsfiddle.net/chrisvfr...

正常项目中:

clipboard.png

clipboard.png

clipboard.png

clipboard.png

.sync

对于VUE的初学者来讲,肯定会感觉prop的写法很麻烦,很讨厌!你肯定想如果prop也可以实现双向绑定那怎是一个爽字了得!不过现实是残酷的,如果子组件可以任意修改父组件的内容,那势必会带来数据的混乱,从而造成维护的困扰!毕竟父组件也是有尊严的!

https://www.jianshu.com/p/d42...

触发其他组件方法

使用 Vue.js 怎么调用其他组件的方法
中央事件总线eventbus

clipboard.png

/**
 * 中央事件总线
 * 这个插件可以在可以在所有的组件之间随意使用 
 * 常用于兄弟组件间通信
 * 注意:
 * $bus.on应该在 created钩子内使用,如果在mounted使用,它可能接受不到其他组件来自created钩子发出的事件       
 * 第二点是使用了$bus.on,在beforeDestroy钩子里应该使用$bus.off解除,因为组件销毁后,就没必要把监听的句柄储存在vue-bus里了
 * @param {*} Vue 传入的Vue class
 */
const install = function (Vue) {
  const Bus = new Vue ({
    methods: {
      emit (event, ...args) {
        this.$emit(event, ...args)
      },
      on (event, callback) {
        this.$on(event, callback)            
      },
      off (event, callback) {
        this.$off(event, callback)
      }
    }
  })
  Vue.prototype.$bus = Bus
}

export default install

多次触发问题-> issue

常用组件

资料集合
multiselect
在 vue-multiselect 基础上创建 ImageSelect 组件
https://github.com/vuejs/awes...

组件封装

封装Vue组件的一些技巧

elementui 使用

改变element-ui的disable属性样式

参考如下:

.activity-page .save-info .is-disabled .el-input\_\_inner {
    background-color: white;
    color: #606266;
    /\* border: 0; \*/
}

.activity-page .save-info .el-radio-group .is-disabled .el-radio\_\_input.is-checked+.el-radio\_\_label {
    background-color: white;
    color: #409EFF;
}

.activity-page .save-info .el-radio-group .is-disabled .el-radio\_\_input.is-checked .el-radio\_\_inner {
    border-color: #409EFF;
    background: #409EFF;
}

.activity-page .save-info .is-disabled .el-textarea\_\_inner {
    background-color: white;
    color: #606266;
    /\* border: 0; \*/
}

webpack打包速度优化

我的知乎专栏

实现label-required

clipboard.png

 .label-required
    display: inline-block
    height: 33px
    line-height: 28px
    &:before
      content: '*'
      display: inline-block
      margin-right: 4px
      line-height: 1
      font-family: SimSun
      font-size: 12px
      color: #ed3f14

$parent

$parent 也可以用来访问父组件的数据。

而且子组件可以通过$parent 来直接修改父组件的数据,不会报错!

可以使用props的时候,尽量使用props显式地传递数据(可以很清楚很快速地看出子组件引用了父组件的哪些数据)。

另外在一方面,直接在子组件中修改父组件的数据是很糟糕的做法,props单向数据流就没有这种顾虑了。

禁止直接更改组件上的class样式 ,用自定义class覆盖

clipboard.png

clipboard.png

clipboard.png

自动化部署

非大厂隔离环境的话可用ssh( 大厂生产环境是隔离的 要通过跳板机 代码不可能通过ssh直接发到生产的):
Vue-CLI 3.x 自动部署项目至服务器

常见问题

组件中 data 为什么是函数

为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?

因为组件是用来复用的,JS 里对象是引用关系,这样作用域没有隔离;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

v-model原理解析

从最初学习Vue就知道v-model可以实现双数据绑定,但它能实现绑定的原理到底是什么呢?通过查看官方文档和各种博客资料,以下是我的理解。

根据官方文档介绍,v-model本质上就是语法糖,即利用v-model绑定数据后,其实就是既绑定了数据,又添加了一个input事件监听,如下:
https://www.cnblogs.com/attac...

双向绑定原理

这个问题几乎是面试必问的,回答也是有深有浅。基本上要知道核心的 API 是通过 Object.defineProperty() 来劫持各个属性的setter / getter,在数据变动时发布消息给订阅者,触发相应的监听回调,这也是为什么 Vue.js 2.x 不支持 IE8 的原因(IE 8 不支持此 API,且无法通过 polyfill 实现)
https://cn.vuejs.org/v2/guide...

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤:
第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:

  • 在自身实例化时往属性订阅器(dep)里面添加自己
  • 自身必须有一个 update()方法
  • 待属性变动 dep.notice()通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,则功成身退。
    第四步:MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。

    vue组件中各方法执行顺序是怎么样的

    vue组件中各方法执行顺序是怎么样的

Props -》 Methods -》 Data -》Computed -》 Watch

clipboard.png

clipboard.png

长列表性能优化

我们应该都知道vue会通过object.defineProperty对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要vue来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止vue劫持我们的数据呢?可以通过object.freeze方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

export default {
  data: () => ({
    users: {}
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  }
};

另外需要说明的是,这里只是冻结了users的值,引用不会被冻结,当我们需要reactive数据的时候,我们可以重新给users赋值。

export default {
  data: () => ({
    users: []
  }),
  async created() {
    const users = await axios.get("/api/users");
    this.users = Object.freeze(users);
  },
  methods:{
    // 改变值不会触发视图响应
    this.users[0] = newValue
    // 改变引用依然会触发视图响应
    this.users = newArray
  }
};

vue-router

vue-router 有哪几种导航钩子?
  • 全局导航钩子
  • router.beforeEach(to, from, next),
  • router.beforeResolve(to, from, next),
  • router.afterEach(to, from ,next)
  • 组件内钩子
  • beforeRouteEnter,
  • beforeRouteUpdate,
  • beforeRouteLeave
  • 单独路由独享组件
  • beforeEnter
import NProgress from 'nprogress'
// http://ricostacruz.com/nprogress/

clipboard.png

vue 中 ajax 请求代码应该写在组件的 methods 中还是 vuex 的 action 中?

如果请求来的数据不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入 vuex 的 state 里

如果被其他地方复用,请将请求放入 action 里,方便复用,并包装成 promise 返回

clipboard.png

组件开发

组件要考虑的三个基本内容:

  • 交互
  • 样式
  • 内容
    组件需要提供修改样式的能力, 修改内容的能力,和基本的交互反馈能力。

而一个可复用的组件,需要开放控制粒度。通常来说,控制粒度约高的组件,开发者使用起来就越方便,但是维护起来也越困难。确定自己的组件控制开放到什么程度是重要的内容。

另外,也存在只提供基本的控制开放的组件。各个人会有其偏向性。而反馈组件的状态决定了组件是否易用。程序需要知道用户做了什么。

恰当的反馈,加上合适的控制,才能写出易用的组件。

extends

ElementUI radio 小改造

封装组件

封装Vue组件的一些技巧

代码规范

vue 风格指南

工程实践

Vue 项目架构设计与工程化实践

参考

vue中的css作用域、vue中的scoped坑点
css loader 深度作用选择器
vue nextTick深入理解-vue性能优化、DOM更新时机、事件循环机制
Vue.js 技术揭秘
Vue技术内幕
深度剖析:如何实现一个 Virtual DOM 算法
Vue使用中的小技巧
Vue问得最多的面试题
让vue-cli3.0 配置简单起来(vue.config.js编结)
Creating Vue.js Component Instances Programmatically
Vue项目经验
「Vue实践」武装你的前端项目
7个有用的Vue开发技巧
基于vue的进阶散点干货


白鲸鱼
1k 声望110 粉丝

方寸湛蓝