[toc]

Vue

  • 在每个 new Vue 实例的子组件中,其根实例可以通过 $root 属性进行访问

全局挂载事件 this.$root.$on('eventName', () => {}) this.$root.$emit('eventName')

移除自定义事件监听器 this.$root.$off('eventName')

vue3 实际已经禁止使用这个了 https://v3.vuejs.org/guide/migration/events-api.html

vue-2

  • __ob__: Observer vue设置的数据监听器,一般不可枚举

  • 重置数据到初始化状态

/* 只能用assign 否者会报错 Use nested data properties instead */
Object.assign(this.$data , this.$options.data())

/* 当data数据中有用到 this.props this.methods中的方法 需要重新绑定 this; 因为这些属性没有挂载在$options上,所以直接赋值会是undefined*/
Object.assign(this.$data, this.$options.data.call(this));
  • Vue.component(id, 组件对象) 注册或获取全局组件

  • 虚拟 DOM

    1. 需要适配上层 API 对 dom 的操作,所以需要具有普适性,所以不是最优性能实现,但是比所有的都直接操作DOM要更好,保障了性能的下限
    2. 跨平台,因为本质是js对象,,可以做服务端渲染 weex 等
    3. 有些高性能应用中,无法极致优化,比如vscode手动操作DOM进行的性能优化

自定义组件用 v-model

参考 base-dialog

<input :value="value" @input="input">  // 等价于下面方式
<input v-model="value"> // 语法糖

后面为了满足非输入元素(也就是自定义组件)父子通信有了如下方式

父组件:

<ChildComponent v-model="select"></ChildComponent>

data() {
    return {
        select: 0
    }
}

子组件:()

model: {
    prop: 'sel', // 与下面 props 中的 sel 一致 
    event: 'eventName' // 用于 this.$emit('eventName', '参数') 触发
},
props: {
    value: '', // v-model 默认子组件接收就是用的 value  事件触发用的 this.$emit('input', '参数');如果要换其它名称就用上面的方式
    sel: {
        type: Number, // 用于接收父组件 v-model 中的值
        default: 0
    },
}

计算属性

computed 不能传参

默认只有getter

computed: {
    testComputed: {
        get() {
            return this.name // name变动时调用 getter
        },

        set(v) {
            this.name = v
        }
    }
}

this.testComputed = 'new name' // 触发setter

事件处理

<button @click="fn('some string', $event)"></button>

// 如果这里 fn 没有参数传递  那么在 method 中的 fn 默认第一个参数为 even 

// 获取 原始 DOM 事件
methods: { fn(str, event) {}}

组件中,可以用 $on,$once 监听所有的生命周期钩子函数,如监听组件的 destroyed 钩子函数可以写成 this.$on('hook:destroyed', () => {}) 其他周期函数同理

父组件监听子组件生命周期函数

// 方式一
// 父组件
<SomeCustomComponent @created="someFn" />
// 子组件
created() {
    this.$emit('created')
}

// 方式二
@hook:created 监听组件的 created 生命钩子函数 同理 其它周期函数也可以这样 监听
<SomeCustomComponent @hook:created="someFn" />

自定义事件

  1. 自定义事件名,会被转换为全小写;camelCase 或 PascalCase 与 kebab-case,永远不会相同;推荐使用 kebab-case 命名

  2. 定义的组件 如果要使用原生事件(click等) 需要加上 .native;

watch 处理

文档open in new window

  • immediate: 表示初始化监听时,立即触发一次,从而不必在 created 等钩子函数中去获取第一次传入的 prop 值
  • deep: 当监听的是对象时,并且父组件只是修改了对象某个属性值,并没有修改引用地址,则需要设置deep进行监听,当修改了对象引用,则不需要

  • 无论何时,绑定的数据对象上 message 属性发生了改变,插值处的内容都会更新;通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。

  • 过滤器函数总接受表达式的值作为第一个参数。

  • 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 。你不应该在模板表达式中试图访问用户定义的全局变量。

  • Vue 2.x 中,过滤器只能在 mustache 绑定和 v-bind 表达式(从 2.1.0 开始支持)中使用,因为过滤器设计目的就是用于文本转换。为了在其他指令中实现更复杂的数据变换,你应该使用计算属性。

  • 然而,不同的是计算属性是基于它们的依赖进行缓存的,计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

  • v-if v-show 说明

    • v-else 元素必须紧跟在 v-if 或者 v-else-if 元素的后面——否则它将不会被识别
    • v-if 条件块内的事件监听器和子组件适当地被销毁和重建
    • v-show 元素总是会被渲染,并且只是简单地基于 CSS 进行切换
    • elementUI 的 dialog 使用的是 v-show
  • v-model后不能跟表达式


  • v-bind="$attrs" v-on="$listeners"

$attrs: 包含了父作用域中 不作为 prop 被识别和获取的属性,在孙一级中定义prop获取或者不设置prop,直接用this.$attrs获取,两者只能选其一(使用示例参考 vue-admin componentCommunicate)

$listeners: 包含了父作用域中传入的 v-on 事件监听器,除了原生事件,可以是 click 等事件也可以是自定义事件,可以 this.$listeners.click() this.$emit('click') 两种方式调用(使用示例参考 vue-admin componentCommunicate)

可用于透传参数,比如二次封装某些第三方组件时,<el-upload v-bind="$attrs"> 那自己封装的组件就可以直接设置 第三方的组件属性


生命周期

  • created 非相应式的data数据可以赋初始值 data: { a: {} }; this.a.b=1
  • destroyed : 如果有定时器,在该钩子函数中务必清除

参考: https://www.jianshu.com/p/a20f2023c78a

beforeCreate:在实例初始化之后,data observer 和 event/watcher事件配置之前被调用,此时data、watcher、methods没有。
vue实例什么都没有,但$route对象是存在的,可以根据路由信息进行重定向之类的操作。

created:在实例已经创建完成之后被调用。在这一步,实例已完成以下配置:数据观测(data observer) ,属性和方法的运算, watch/event 事件回调。挂载阶段还没开始,$el属性目前不可见。
此时 this.$data 可以访问,watcher、events、methods也出现了,若根据后台接口动态改变data和methods的场景下,可以使用。

beforeMount:在挂载开始之前被调用,相关的 render 函数 首次被调用。但是render正在执行中,此时DOM还是无法操作的。此时的vue实例对象,相比于created生命周期,此时只是多了一个$el的属性,但其值为undefined。
页面渲染时所需要的数据,应尽量在这之前完成赋值。

mounted:在挂载之后被调用。在这一步 创建vm.$el并替换el,并挂载到实例上;此时元素已经渲染完成了

beforeUpdate:$vm.data更新之后,虚拟DOM重新渲染 和打补丁之前被调用。
你可以在这个钩子中进一步地修改$vm.data,这不会触发附加的重渲染过程。

updated:虚拟DOM重新渲染 和打补丁之后被调用。
当这个钩子被调用时,组件DOM的data已经更新,所以你现在可以执行依赖于DOM的操作。但是不要在此时修改data,否则会继续触发beforeUpdate、updated这两个生命周期,进入死循环!

beforeDestroy:实例被销毁之前调用。在这一步,实例仍然完全可用。

destroyed:Vue实例销毁后调用。此时,Vue实例指示的所有东西已经解绑定,所有的事件监听器都已经被移除,所有的子实例也已经被销毁。

mixin

import mixIn from 'mixin.js';
{
    mixins: [mixIn]
}
  • data对象的数据会进行递归合并,并在发生冲突时以组件数据优先

  • 同名钩子函数将合并为一个数组,都将被调用。混入对象的钩子将在组件自身钩子之前调用

  • 值为对象的选项,例如 methods、computed、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

  • 可以将一些公共方法,比如获取store中数据,放入mixin中,抽取成公共的mixin使用

过滤器

https://v2.cn.vuejs.org/v2/guide/filters.html

<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

props:

  • 同步且不变,可以直接在created等钩子函数直接获取值,而不是得到子组件设置的默认 porp 值;

  • 异步,只能在watch中监听prop,从而获取值

  • 当子组件中需要修改当前的prop时,可以利用watch监听其变化,在data中设置一个新的变量,以供子组件修改赋值

  • 修改props 两种方式

    • 通过 $emit 回掉父组件的函数修改
    • 通过 .sync 修饰符方式 (父子组件是时刻也是必须保持一致)
  • .sync 一般用于简单数据处理 复杂数据用自定义事件接收,这里只是语法糖,业务逻辑还是要根据不同情况去添加其它逻辑代码 参考open in new window

<!-- 相当于对下面的代码进行语法糖,利用prop传递值给子组件 子组件通过emit修改父组件的数据 -->
<ChildComponent v-bind:someValue="sValue" v-on:update:count="sValue = $event" />

实际用法示例

<Parent>
    <span>{{ sValue }}</span> <!-- 这里的值也会跟着一起修改 -->
    <ChildComponent :someValue.sync="sValue" />
    data () {
        return { sValue: 1 }
    }
</Parent>
// ChildComponent 组件中 
props: {
    someValue: {
        type: Number,
        default: 0
    }
}
someClick() {
    /* 执行后 父组件的 sValue 值会改变 */
    this.$emit('update:someValue', this.someValue + 1) // 这里的update:someValue 必须与父组件定义的 prop 名称一致
}

插槽 slot

  • v-slot:name 简写 #name

  • <slot>这里的内容是默认内容,当父级有内容传入时,将会被替换</slot>

  • 作用域插槽:

    • 让父组件可以使用子组件插槽绑定的数据
    • v-slot 只能添加在 <template>
  • v-slot:default 设置了后,如果使用非具名 slot 接收父组件传递的默认文本,不会被渲染到 slot 中 反而会将绑定了 v-slot:default 的template多渲染一次

定义 slot 组件

主要操作:

  1. 设置插槽名称
  2. 绑定作用域插槽数据
<template>
    <div>
        <slot >这是默认接收</slot>
        <slot v-bind:data1="data1">这是默认作用域</slot>
        <!-- 具名插槽 -->
        <slot name="name"></slot>
        <!-- 作用域插槽:让父级可以使用子插槽组件中的数据 -->
        <slot name="scopedSlotData1" v-bind:data1="data1">
            {{ data1 }}
        </slot>
        <slot name="scopedSlot" v-bind:data1="data1" v-bind:data2="data2">
            {{ data1.name }}{{ data1.name }}
        </slot>
    </div>
</template>
<script>
export default { data () { data1: { name: 'warren1' } data2: { name: 'warren2' } } }
</script>

引用 slot

具名插槽作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)

主要操作:

  1. 使用哪个插槽
  2. 是否使用子组件的数据
<template>
    <div>
        <MySlot>
            <!-- 使用具名插槽 v-slot:name 简写 #name -->
            <template v-slot:name>new name</template>

            <!-- 作用域插槽简写 v-slot:default="data" 等同于 v-slot="data"  -->
            <template v-slot="data">
                {{ data }}
                <!-- 这里显示显示 { data1: { name: 'warren1' } } 没有具名的slot (即上面定义的第二个slot)-->
            </template>

            <template v-slot:scopedSlotData1="data">
                {{ data }}
                <!-- 这里显示显示 { data1: { name: 'warren1' } } -->
            </template>
            <template v-slot:scopedSlot="data">
                <!-- 这里的 data  可以修改为任意名称 -->
                <!-- 下面的 {{}} 将会被替换为 { "data1": { "name": "warren1" }, "data2": { "name": "warren2" } } --><b>{{ data }}</b>
            </template>
        </MySlot>
    </div>
</template>
<script>
import MySlot from './mySlot.vue'
export default { components: { MySlot }, data () { } }
</script>

Vue Test Utils

https://vue-test-utils.vuejs.org/zh/

mocha:对测试过程进行描述 (语句中的describe和it等方法)

chai:断言库,即各种判断(expect方法)

Karma: 一个启动浏览器运行测试并生成报告的测试运行器

sinon: 模拟与其它系统或函数对接,主要有三个方法 spy stub mock

cypress: Fast, easy and reliable testing for anything that runs in a browser.

vue-loader

  • 在style中用别名引入scss文件时,如果报错,在别名前 ~ :~@/asset/xxx 在scss中@是变量定义的前缀,所以需要加上~以示区分

  • 独立的 .scss 文件中引入图片,图片的相对地址是根据,引入该样式的 vue 文件的相对地址决定的

  • @import '@/x/x/x.scss'; 引用文件时,不加后缀名可能导致编译错误

  • 深度作用域

.a >>> .b (实际使用中效果没有deep好)
.a /deep/ .b  (容易报错)
.a ::v-deep .b  (最新版,vue/cli 4.4.1,只有这个有效)

.parent :deep(.need)  vue3 用法

深入响应式原理

  • 非侵入性的响应式系统
  • 数据模型为js对象,对其修改时,视图更新

如何追踪变化

  • vue将接收的data全部用Object.defineProperty把属性转为getter/setter(导致不支持ie8以及一下)

  • 属性被访问和修改时通知变化

  • 每个组件实例都有对应的watcher实例对象(它会在组件渲染的过程中把属性记录为依赖,当依赖的setter被调用时,会通知watcher重新计算,从而使相关组件更新)

检测变化的注意事项

  • 只有在data对象上的属性才是响应式的

  • 改变对象和数组的一些情况不会被检测到更新

  • 要用到的状态,提前在data对象中声明

异步更新队列

vue router

路由的加载,管理,匹配都是 vueRouter 创建的实例属性 router.matcher 处理

vueRouter 的两个方法 match addRouter 都是 matcher 暴露的方法

$route.matched() 匹配当前页面路由所在的层级 比如地址是 /a/b/c b是a的child一级 c是b的child一级 那匹配的就是 a 和 a/b 和 a/b/c 的路由信息

  • beforeRouteUpdate 处理使用同一个组件页面 但是路由参数变化的情况 /:id 或 /?id=1

  • 相关的路由钩子函数,必须只能在路由上挂载的组件中才有效,内部其它组件中无效

new Router({
    /* 这里的键名必须是 routes */
    routes: baseRouter
})
  • router-view 与路由表中的 children 有关

  • router.push 改为了 Promise

  • 参数传递:

/* params 针对的是 定义的path形如 /xx/xx/:id   */
// 注意如果 path是 /xx/xx/ 的话,刷新页面  传递的数据会掉
router.push({ name: 'user', params: { id: '123' }})

// path 和 params 同时设置 将无效, 实际使用时  { path: '/user:is', params: { id: '123' }} 看起来path也很怪异
router.push({ path: '/user', params: { id: '123' }})

// name 和 path 都可以和 query 一起用  
// 定义的路由 path: '/user' 以下两种方式跳转后url都是: /user?id=123
router.push({ name: 'user', query: { id: '123' }})
router.push({ path: '/user', query: { id: '123' }})
  • 导航守卫的钩子中
next() // 进行管道中的下一个钩子
next({ path: '/' })  // 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航
// 以上两个的区别,前者表示,路由完成,显示对应页面;后者表示,需要开启一个新的路由过程,与跳转路由相似

keep-alive

  1. include 名称匹配的组件会被缓存,包含的是组件的name属性值
  2. 通过同一个 router-view 进入的路由间切换, keep-alive 都有效,都会缓存页面
  3. 只要通过keep-alive下的路由(前提是要包含在include中) ,每次都会触发activated, 只有第一次进入会触发mounted(切换过router-view入口 再进入也会触发mounted)
  4. 注意 include 如果用字符串值,后面名称与逗号之间不要有空格
  5. exclude 优先级更高
beforeRouteLeave (to, from, next) {
    /** 这里主要是重置数据,当要去往的地址是新增、编辑、详情这些时,即可以不用重置数据 */
    if (!['routeName'].includes(to.name)) { this.key = '数据重置了' }
    next()
},

activated () { /** @des 这里更新表格数据等 */ },

created () { /** 这里请求不需要频繁更新的数据 */ },

处理保存页面状态

  • 最好一个模块有一个单独的 router-view
  • 在 activated 中请求需要实时更新的数据
  • 在 beforeRouteLeave 中处理和当前需要保存状态页面走同一个 router-view 的页面,否则在这些页面间切换,页面的状态也会被保留(data中的数据)

使用 dart-sass

替换 node-sass; 安装 dart-sass sass sass-loader 启动后,如果报错 this.getOptions is not a function 将 sass-loader版本改为10.1.1 (/deep/ 和 >>>) 替换为 ::v-deep

// vue.config.js
css: {
    loaderOptions: {
        sass: {
            implementation: require('sass'),
        },
    },
}

vuex

  • vue3 官方已经推荐 Piniaopen in new window

  • 实践使用参见 vue-admin/src/store

  • vuex esm. js sub is not function 报错 是vue浏览器插件问题,重启或关闭,升级可以解决

// state
this.$store.state
computed: {
    ...mapstate(['user']) // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
    // ...mapgetters  同上
} 

// getters 返回函数 可以获取参数  第二个参数为其它 getter
(state, getters) => (param) => {}
// mutation
(state, payload) => {}
// actions
({ state, commit, dispatch, rootState }, payload) => { } 
// rootState 返回绑定的根节点下的所有modules

公用数据,在组件中获取store中数据,若store中没有则等待其从服务器拉去数据

async mounted () {
    try {
        /* actionGetSomeData 中有异步数据请求 */
        await this.actionGetSomeData()
    } catch (error) {
        console.log(error)
    }
    /* 这里调用的方法需要等待store中的数据来改变界面显示 */
    this.someMethodsUpdateView()
},
  • Mutation 同步操作
// 用法一
this.$store.commit('name', payload) // 这里不用管是否 是提交的某个模块 commit 只要名称对应即可,所以 mutation 定义的名称要保证全局唯一
// 用法二
methods: {
    ...mapMutations([
        'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment', payload)`
    ]),
    ...mapMutations({
        add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment', payload)`
    })
}
  • Action 内部包含异步操作 异步原因:
  1. 区分 actions 和 mutations 为了能用 devtools 追踪状态变化
  2. 当用到相同的数据时,如果采用【异步获取数据+mutation】模式,会造成在很多地方写重复代码,就可以抽取到action中统一管理 还能防止重复发起http请求

如果用 Pinia 就没有这个问题了,它只提供了 actions

定义的action方法会被包裹一层,返回的是一个 Promise,所以触发 action 要在 then 中去接收返回值;

如果action中有数据请求,需要同步执行,必须有如下两种操作之一,推荐第二种:

/* 1. */
actionSome ({ commit }) {
    return new Promise((resolve, reject) => { resolve() })
}

/* 2. */
async actionSome ({ commit }) {
    try {
        await requestSome()
    } catch (error) {
    }
}
this.$store.dispatch('xxx')

methods: {
...mapActions([ // 默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间, 除了加命名空间, 所以注意名称全局不重复
        'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment', payload)`
    ]),
    ...mapActions('模块名称', ['f1', 'f2']) // 使用了 namespace
    ...mapActions(['f1', 'f2']) // 没使用 namespace
}
  • namespaced: true 命名空间

用法 store.dispatch('模块名/actionSetUserInfo')

elementUI

  • 表单重置 this.$refs.ruleForm.resetFields() 只会清除 新输入的数据 ;当在data上配置表单数据时设置了初始化值时,该默认值是不会被清除的,并且修改该默认值,重置后的值也是初始值

  • 当表单数据有多层嵌套对象时,在设置prop时要将 嵌套关系用字符串形式 赋值

  • 初始化时给表单赋值,触发了校验,赋值后还是给出了错误提示,检查是否是在 created 生命周期中进行的赋值,如果不是请保证在 created 中进行初始化赋值

  • upload 组件 :accept="image/png, image/jpeg, image/jpg"

  • v-for 中使用 ref 注意 获取 this.$refs.name 返回的是数组

el-cascader

  • 需要点击两次才能选中 (1) 其中一个原因是,多次赋值操作;解决办法为,保证只有在初始化时进行一次赋值操作 (2) 如果是对该组件进行了封装,且用 v-model 进行父子组件参数传递,且数据是异步,可以在 watch 监听时,判断 oldvalue 如果存在值 就不进行赋值操作,防止重复赋值 (3) 加个 emitPath: false 属性,便能搞定, 注意这样设置后 change事件返回的value 不是数组

  • 解决 tooltip 文字内容过多导致显示不正常, 注意这里是全局修改,可以给一个较大的 max-width 值

.el-popper.is-dark, .el-tooltip__popper.is-dark { max-width: calc(100vw - 300px); }  
.el-popper__popper.is-dark { max-width: calc(100vw - 300px); }

select change 三种方式传参 后两种是传递了当前选择的值和额外添加的参数

<!-- option 的value直接传对象 -->
<el-select v-model="name" placeholder="xxx" @change="fn($event,i)">
<el-select v-model="name" placeholder="xxx" @change="(val) => fn(val,i)">

tree

  • 没有采用 checkbox 需要设置选中且高亮 使用 setCurrentKey

有checkbox的情况 单选

 <el-tree
    ref="refDepTree"
    :data="treeData"
    check-strictly
    node-key="id"
    default-expand-all
    highlight-current
    :expand-on-click-node="false"
    show-checkbox
    :props="{ children: '', label: '', }"
    @check="check"
/>
check (v) {
    if (v.id === this.currentSelect) {
        this.currentSelect = ''
    } else {
        this.$refs.refDepTree.setCheckedKeys([])
        this.$nextTick(() => {
            this.$refs.refDepTree.setCheckedKeys([v.id])
        })
        this.currentSelect = v.id
    }
}

表格

  • 表格设置 height 属性, 如果需要动态修改,初始值不能填 0 或 能够判断为 true 的值; 源码:if (!el && (value || value === 0)) return Vue.nextTick(() => this.setHeight(value, prop));

  • 动态表格列,需要用 template 来包裹

<template v-if="judge"> <el-table-column prop="prop" label="name"></el-table-column> </template>
  • 在表格中使用 dropdown 的基础形式 也就是没有用 split-button 需要把 v-slot="scope" 加上

  • 动态表格添加项时 一定要注意把字段加上,如果表格中需要的字段,在是初始化时没有指定,在修改其值后 页面不会及时更新显示,再次操作时才会显示

  • 表格合并 注意被合并的单元格的起始单元格设置对应的行列合并即可,其他被合并的单元格应该将行列设置为0(隐藏这些单元格) rowspan是合并的行数:为1表示不变;为0表示去除该单元格,后面的单元格会向上往这格填; colspan同理

  • 固定列时,加了自定义滑动条,固定列和旁边列可能会出现行错位的问题 element-plus 用的scrollbar组件所以不存在该问题

.el-table__fixed-body-wrapper .el-table__body {
    padding-bottom: 10px; /* 这里的数值和设置的滑动条高度一致 */
}
Last Updated:
Contributors: Warren