Vue 技巧

1.通过 v-bind="props" 以及v-bind="attrs" 实现属性透传 很多时候,我们会写一些嵌套组件,

比如 A 的子组件是 B,B 的子组件是 C。这个时候如果 A 传递 props 给 B,B 又得传递 props 给 C,我们经常在 B 传给 C 的时候这么写.

<template>
  <child-component  :someprop1="someprop1          
                    :someprop2="someprop2"
                    :someprop3="someprop3"
                    :someprop4="someprop4".../>
</template>

这样是很不优雅的,其实你可以直接使用 v-bind: $props

<template>
  <child-component v-bind="$props"/>
</template>

这里我们利用 v-bind 可以传入一个对象的所有 property,类似 v-bind="Obj"。例如,对于一个给定的对象 post

post: {
  id: 1,
  title: 'My Journey with Vue'
}

下面的模板:

<blog-post v-bind="post"></blog-post>

等价于:

<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

这个配合 v-bind="attrs" 在封装一些组件的时候非常有用,比如实现属性透传。 vm.attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用

比如将上面传递进来的 props 全部绑定到 el-input 中,我们可以在子组件中这么写:

<template>
  <div>
    <el-input v-bind="$attrs" ></el-input>
  </div>
</template>

2.两个 component 接受同样的 props

上面例子中,要求组件 B 和组件 C 接受同样的的 props,其中 B 组件写法如下(C 组件类似):

<template>
  <child-component v-bind="$props"/>
</template>

<script>
  import ChildComponent from '@/components/ChildComponent'
  
  export default {
    props:{
      someProp1: String,
      someProp2: String,
      someProp3: String,
      // and so on
    }
  }
</script>

但这样有个问题,就是 C 组件修改了 props,那么 B 组件得一起修改,这样做的一个坏处有可能会有遗漏。另外就是代码冗余,看起来不精简。其实我们可以在 B 组件中这么写

<template>
  <child-component v-bind="$props"/>
</template>

<script>
  import ChildComponent from '@/components/ChildComponent'
  
  export default {
    props:{
      ...ChildComponent.options.props
    }
  }
</script>

3.Props 校验

由于 Javascript 是弱类型语言,在写 props 的时候,最佳实践是对 props 使用 type 指定类型以及设定默认值,如下:

Vue.component('my-component', {
    // 带有默认值的对象
    propA: {
      type: Object,
      // 对象或数组默认值必须从一个工厂函数获取
      default: function () {
        return { message: 'hello' }
      }
    }
})

但是有可能我们不知道的是,props 可以自定义验证函数

Vue.component('my-component', {
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
})

甚至上面的 type 除了可以设置 String Number Boolean Array Object Date Function Symbol 之外,还可以自定义构造函数,其底层实现原理是通过 instanceof 去判断

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

Vue.component('blog-post', {
  props: {
    author: Person
  }
})

4.作用域插槽实现 UI 和业务逻辑的分离

很多时候,我们想复用一个组件的业务逻辑,但是不想使用该组件的 UI,那么可以使用作用域插槽实现 UI 和业务逻辑的分离。

作用域插槽大致的思路是将 DOM 结构交给调用方去决定,组件内部只关注业务逻辑,最后将数据和事件等通过 :item ="item" 的方式传递给父组件去处理和调用,实现 UI 和业务逻辑的分离。再结合渲染函数,就可以实现无渲染组件的效果

具体可以看我的另一篇文章 【Vue 进阶】从 slot 到无渲染组件

其中父组件调用的时候可以类似这样,其中 #rowv-slot:row 的缩写

<!-- 父组件 -->
<template>
  ...
  <my-table>
    <template #row="{ item }">
      /* some content here. You can freely use 'item' here */
    </template>
  </my-table>
  ...
</template>
<!-- 子组件 -->
<span>
  <!-- 使用类似 v-bind:item="item",将子组件中的事件或者data传递给父组件-->
  <slot v-bind:item="item">
    {{ item.lastName }}
  </slot>
</span>

5.调试 template

很多时候,我们会遇到 template 模板中变量报错的问题,这个时候,我们很想通过 console.log 打印到控制台,看它的值是什么

// 这里最好是判断一下,只有在测试环境中才使用
// main.js
Vue.prototype.$log = window.console.log;

// 组件内部
<div>{{$log(info)}}</div>

6.动态的指令参数

在 Vue 2.6 中提供了这样一个特性:可以动态的将指令参数传递给组件。假设你有一个组件 <my-button>,有时候你需要绑定一个点击事件 click,有时候需要绑定一个双击事件 dblclick,这个时候你可以这么写

<template>
  ...
  <my-button @[someEvent]="handleSomeEvent()"/>
  ...
</template>

<script>
  ...
  data(){
    return{
      ...
      someEvent: someCondition ? "click" : "dblclick"
    }
  },
  
  methods:{
    handleSomeEvent(){
      // do something
    }
  }
  ...
</script>

7.表单输入控制——表单修饰符/change事件/filter/指令

我们经常遇到控制表单输入内容的需求,比如输入框内一定是是数字,不能有特殊字符等等。这里我提供一些自己的一些思路,供大家选择使用

表单修饰符

如果是简单的控制输入一定是数字或者去掉用户输入的收尾空白符,可以直接使用 Vue 提供的表单修饰符 .number 和 .trim

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="age" type="number">

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符

<input v-model.trim="msg">

change事件

<input v-model="value2" type="text" @change="inputChange(value2)" />
methods: {
  inputChange: function(val) {
    if (!val) return ''
    val = val.toString()
    this.value2 = val.charAt(0).toUpperCase() + val.slice(1)
  }
}

filter

还可以通过过滤器 filter 进行

<input v-model="value1"  type="text" />
Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})
watch: {
  value1(val) {
     this.value1 = this.$options.filters.capitalize(val);
  }
}

详情可以看这个 demo

指令

声明一个全局的指令

// 只能输入正整数,0-9的数字
Vue.directive('enterIntNumber', {
  inserted: function (el) {
    let trigger = (el, type) => {
      const e = document.createEvent('HTMLEvents')
      e.initEvent(type, true, true)
      el.dispatchEvent(e)
    }
    el.addEventListener("keyup", function (e) {
      let input = e.target;
      let reg = new RegExp('^\\d{1}\\d*$');  //正则验证是否是数字
      let correctReg = new RegExp('\\d{1}\\d*');  //正则获取是数字的部分
      let matchRes = input.value.match(reg);
      if (matchRes === null) {
        // 若不是纯数字 把纯数字部分用正则获取出来替换掉
        let correctMatchRes = input.value.match(correctReg);
        if (correctMatchRes) {
          input.value = correctMatchRes[0];
        } else {
          input.value = "";
        }
      }
      trigger(input, 'input')
    });
  }
});
<!--限制输入正整数-->
<input v-enterIntNumber placeholder="0" type="number">

8.key 值的使用

Vue 中,使用 v-for,官方建议带上 key 值,因为如果不使用 keyVue 默认会使用一种“就地复用”的策略进行更新。在一些情况下,很有可能会导致渲染不正确,之前总结过一篇 使用 key 不当踩坑的经历,感兴趣可以看下

除了 v-for, 在使用 Vue-router 做项目时,会遇到如 /path/:id 这样只改变 id 号的场景,但渲染不同的组件。由于 router-view 是复用的,单纯的改变 id 号并不会刷新 router-view,这并不是我们所期望的结果

这个时候,我们可以给每个 router-view 添加一个不相同 key 值,让 Vue 每次切换路由参数的时候,认为是不同的组件,从而得到更新

<router-view :key="key"></router-view>

实际上对于所有的 DOM,Vue 都有可能采取就地复用的策略,所以如果遇到了渲染顺序不正确的问题,可以往 key 值设置的方向考虑

9.自定义组件使用 v-model

我们知道,v-model 是 v-bind 以及 v-on 配合使用的语法糖,以下的两者的实现是一致的:

<input v-model="value" />
<input v-bind:value="value" v-on:input="value= $event.target.value" />

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。

其中 v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件
  • checkbox 和 radio 使用 checked property 和 change 事件
  • select 字段将 value 作为 prop 并将 change 作为事件

以上的情况,我们在自定义组件中使用的时候,就需要使用 model 选项了,按照官方的示例,写了个 demo

这里的 lovingVue 的值将会传入这个名为 checkedprop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVueproperty 将会被更新。

10.CSS scoded 和深度作用选择器

在 Vue-loader 中,当 <style> 标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素,它通过使用 PostCSS 来实现以下转换,可以注意到 .example 后面添加了专属于该元素的 [data-v-f3f3eg9]

<style scoped>
.example {
  color: red;
}
</style>

<template>
  <div class="example">hi</div>
</template>
<style>
.example[data-v-f3f3eg9] {
  color: red;
}
</style>

<template>
  <!-- 留意data-v-f3f3eg9 -->
  <div class="example" data-v-f3f3eg9>hi</div>
</template>

但有时候,我们想设置混用本地和全局样式,可以在一个组件中同时使用有 scoped 和非 scoped 样式

<style>
/* 全局样式 */
</style>

<style scoped>
/* 本地样式 */
</style>

但这样全局的样式就有可能产生一些副作用,我们很多时候这么设置,其实只是想设置父组件中子组件的样式。因为父组件设置了 scoped 之后,父组件的样式将不会渗透到子组件中

这个时候,我们可以直接通过深度作用选择器去影响子组件,如下

<style scoped>
.a >>> .b { /* ... */ }
</style>

会编译成如下:

.a[data-v-f3f3eg9] .b { /* ... */ }

有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/ 或 ::v-deep 操作符取而代之——两者都是 >>> 的别名,同样可以正常工作

11.watch immediate

有时候我们想在一个组件中 watch 一个值,进行一些初始化页面和更新页面的操作,比如 this.getDetails()
类似如下:

watch: {
  id: {
    handler(newValue) {
      this.getDetails(newValue);
    }
  }
}

这里 watch 的一个特点是,最初绑定的时候是不会执行的,要等到 id 改变时才执行监听计算。这可能导致我们页面第一次渲染出错

想要在 watch 中声明了 id 后立即先执行 handler 方法,可以加上 immediate: true

watch: {
  id: {
    handler(newValue) {
      this.getDetails(newValue);
    },
    // 代表在wacth里声明了id后这个方法之后立即先去执行handler方法
    immediate: true
  }
}

12.v-cloak 解决页面闪烁问题

很多时候,我们页面模板中的数据是异步获取的,在网络不好的情况下,渲染页面的时候会出现页面闪烁的效果,影响用户体验,v-cloak 指令保持在元素上直到关联实例结束编译,利用它的特性,结合 CSS 的规则 [v-cloak] { display: none } 一起使用就可以隐藏掉未编译好的 Mustache 标签,直到实例准备完毕

// template 中
<div class="#app" v-cloak>
    <p>{{value.name}}</p>
</div>

// css 中
[v-cloak] {
    display: none;
}
//需要注意,虽然解决了闪烁的问题,但这段时间内如果什么都不处理的话,会直接白屏,这并不是我们想要的效果,我们应该加一个 loading 或者骨架屏的效果,提升用户体验

13.v-once 和 v-pre 提升性能

我们知道 Vue 的性能优化很大部分在编译这一块,Vue 源码就有类似标记静态节点的操作,以在 patch 的过程中跳过编译,从而提升性能。另外,Vue 提供了 v-pre 给我们去决定要不要跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。

<span v-pre>{{ this will not be compiled }}</span>   显示的是{{ this will not be compiled }}
<span v-pre>{{msg}}</span>     即使data里面定义了msg这里仍然是显示的{{msg}}

另外,如果只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。可以使用 v-once

<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 有子元素 -->
<div v-once>
  <h1>comment</h1>
  <p>{{msg}}</p>
</div>
<!-- 组件 -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` 指令-->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

14.函数式组件

函数式组件指的是无状态,无法实例化,内部没有任何生命周期处理方法的组件。因为函数式组件只是函数,所以渲染开销也低很多。可以通过声明 functional: true,表明它是一个函数式组件

在作为包装组件的时候,它们是非常有用的

程序化地在多个组件中选择一个来代为渲染
在将 children、props、data 传递给子组件之前操作它们
看官方的一个 demo,留意注释

// 根据不同的情况渲染不同的组件
var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }

Vue.component('smart-list', {
  functional: true, // 声明 functional: true,表明它是一个函数式组件
  props: {
    items: {
      type: Array,
      required: true
    },
    isOrdered: Boolean
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) { // 组件中所有的一切都是通过 context 传递的
    // 根据不同的情况渲染不同的组件
    function appropriateListComponent () {
      var items = context.props.items

      if (items.length === 0)           return EmptyList
      if (typeof items[0] === 'object') return TableList
      if (context.props.isOrdered)      return OrderedList

      return UnorderedList
    }

    return createElement(
      appropriateListComponent(),
      context.data, // 传递给组件的整个数据对象
      context.children // `VNode` 子节点的数组
    )
  }
})

留意下,组件中所有的一切都是通过 context 传递的(render 函数的第二个参数),比如上面通过 context.data context.children 分别代表传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件和VNode 子节点的数组,详细的 context 中包含的内容见官网

15.使用 Vue.observable 实现状态共享

众所众知,Vuex 就是专门用来解决多组件状态共享的情况,不过就像 Vuex 官方文档所说的,如果应用不够大,为避免代码繁琐冗余,最好不要使用它

Vue.observable( object ) 让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景,有点类似小型的 Vuex

我们新建一个文件 store.js

import Vue from "vue";

// 创建一个小型的 store,里面的数据可以实现多组件共享
export const store = Vue.observable({ count: 0 });

// 模糊VueX 的 mutation
export const mutations = {
  setCount(count) {
    store.count = count;
  }
};

在组件中使用 store 中的值以及更新 store 中的值,具体的可以看 demo

<template>
  <div id="app">
    <p>count:{{count}}</p>
    <button @click="setCount(count+1)">+1</button>
    <button @click="setCount(count-1)">-1</button>
  </div>
</template>

<script>
import HelloWorld from "./components/HelloWorld";
import { store, mutations } from "./store";

export default {
  name: "App",
  components: {
    HelloWorld
  },
  // 获取 store 中的数据
  computed: {
    count() {
      return store.count;
    }
  },
  // 更改 store 中的数据
  methods: {
    setCount: mutations.setCount
  }
};
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • slots 新语法向 3.0 看齐 ❝使用带有“#”的新命名插槽缩写语法,在Vue 2.6.0+中可用👍❞ [图片...
    不行了快拦住我阅读 127评论 0 0
  • 1 .watch一个变量的时候,初始时并不会执行。需要在create的时候手动调用一次。可以在watch的时候添加...
    菜鸟出动大神让路阅读 414评论 0 5
  • 1 .watch一个变量的时候,初始时并不会执行。需要在create的时候手动调用一次。可以在watch的时候添加...
    skoll阅读 227评论 0 0
  • 黑色的海岛上悬着一轮又大又圆的明月,毫不嫌弃地把温柔的月色照在这寸草不生的小岛上。一个少年白衣白发,悠闲自如地倚坐...
    小水Vivian阅读 3,093评论 1 5
  • 渐变的面目拼图要我怎么拼? 我是疲乏了还是投降了? 不是不允许自己坠落, 我没有滴水不进的保护膜。 就是害怕变得面...
    闷热当乘凉阅读 4,234评论 0 13