【Vue】从官方文档中扒拉出来的小技巧,看看你知道那些吧

1,159 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 4 天,点击查看活动详情


想着作为一个 Vuer,平时却没有怎么好好的看过官方文档,这次找了个时间看了一番,不得不说 Vue 的文档写的还是很详细的。从里面整理出来一些小技巧,希望能帮助大家提高平时的开发效率。毕竟:

传输一个对象的所有属性

平时我们在开发的时候,经常会通过父组件给子组件传递很多 prop,一般我们可能会这样写:

<child-comp
    :prop1="prop1"
    :prop2="prop2"
    :prop3="prop3"
    :prop4="prop4"
    ...
></child-comp>

这样写当然也是没问题的, 但是这样的写法并不是很优雅,并且每需要多传一个属性给子组件,我就需要多写一个类似:prop="prop"的属性传递,写多了也很繁琐。

其实我们可以使用 v-bind="object"来代替一个个的 :prop-name="propValue", v-bind会传递这个对象中的所有 property给子组件。

假设我们要传递给子组件这么一些属性

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

通过 v-bind,我们可以写成这样:

<child-comp v-bind="post"></child-comp>

这种写法其实是等同于下面这种写法的,但是会方便很多。

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

$attrs 实现透传

包含了父作用域中不作为组件 props 或自定义事件的 attribute 绑定和事件。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定,并且可以通过 v-bind="$attrs" 传入内部组件——这在创建高阶的组件时会非常有用。

简单来说,就是在父作用域传给子组件的一些属性或者事件,但是在子作用域又没有进行定义的,都会被包含在 $attrs 里面,比如 classstyleid,在创建高阶的组件的时候非常有用。

我们在用基础组件的时候,往往需要根据业务场景对这些基础组件进行一些封装,这时候 $attrs就能发挥很大的作用。通过 v-bind="$attrs",轻松实现属性透传。

但是当组件是单个根节点时,非 propattribute 会自动添加到根结点的 attribute 上,这时候我们可以通过设置inheritAttrs: false将其禁用,以保证非 propattribute只在我们添加的元素上生效。

// 子组件
<template>
  <div>$attrs: <input v-bind="$attrs" /></div>
</template>

<script>
export default {
  name: "AttrsChildcomp",
  inheritAttrs: false,
  created() {
    console.log(this.$attrs);
  },
};
</script>

// 父组件
<template>
  <div>
    <s-input
      id="1"
      class="input"
      style="color: red"
      placeholder="请输入"
      @input="change"
    >
    </s-input>
  </div>
</template>

显示的效果如下:

需要注意的点是,在 Vue2 中,是可以通过 v-on="$listeners" 来获取传递的事件,但是在 Vue3$listeners被移除了,通过 $attrs 就能获取到事件监听器。

具体示例放在了 codesandox 中,可以戳这里

Props 验证

平时在使用 props 传递的时候,是不是只会这样写

props: ['propsA']
// 或者这样
props: {
  propB: {
    type: Number,
    default: 100
  },
}

但其实,prop 也能自定义验证函数,实现更精细的控制,这在开发一个公共类型的组件时尤其有帮助。

props: {
    // 自定义验证函数
    propC: {
      validator(value) {
        // 这个值必须与下列字符串中的其中一个相匹配
        return ["success", "warning", "danger"].includes(value);
      },
    },
  },

当 prop 验证失败的时候,开发环境下 Vue 将会产生一个控制的告警用于提示。

codesandox - 代码示例

定义 v-model 修饰符

v-model有许多内置的修饰符,比如 .trim,.number,可以更好的提高我们的开发效率,但是在某些情况下,我们还需要有自定义的修饰符。

我们来演示一个自定义修饰符 capitalize,它可以将 v-model 绑定的字符串第一个字母大写,添加到组件 v-model 的修饰符将通过 modelModifiers prop 提供给组件。

// 父组件
<Modifier v-model.capitalize="myText" />
  
// 子组件
<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

<script>
export default {
  name: "Modifier",
  props: {
    // v-model 默认绑定的属性
    modelValue: String,
    // 修饰符会通过 modelModifiers 传递
    modelModifiers: {
      default: () => ({}),
    },
  },
  methods: {
    emitValue(e) {
      let value = e.target.value;
      // 在输入的时候,发现存在 capitalize 修饰符,执行首字母变大写的逻辑
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1);
      }
      this.$emit("update:modelValue", value);
    },
  },
  created() {
    console.log(this.modelModifiers.capitalize); // true
  },
};
</script>

在这个示例中,每当输入框触发 input 事件时,都会将首字母变大写。示例在这

Provide/Inject

有时候我们会碰到一些深度嵌套的组件,当孙孙孙子组件想用顶层父组件的内容,如果仍然将 prop 沿着组件一层一层往下传,会非常的麻烦,这时候就可以使用 provideinject

父组件用一个 provide 来提供数据,子组件用 inject来接受数据,我们可以将这种方式看作是长距离的 prop,只是父组件并不知道哪些自组件用了这些数据,而子组件也并不知道接收的属性来自哪里。

// 父组件
export default {
  name: "ParentComponent",
  // provide: {
  //   provideData: "provideData",
  // },
  // 一般使用上面的方式即可,但是要使用到组件的实例属性,则需要下面这种方式
  provide() {
    return {
      provideData: this.str.length,
    };
  },
};

// 孙子组件
export default {
  name: "ChildComponent",
  inject: ["provideData"],
};

但是这样传递的数据是没有响应式的,即父组件 provide 的数据更改了,孙子组件 inject 到的数据也不能做出相应的更改,但是可以通过 provide 传递一个具有响应式的数据来改变这种情况。

示例戳这里

指令的动态参数

在 Vue 中,指令的参数是可以使用表达式的,只需要用方括号括起来:

<a v-bind:[attributeName]="url"> ... </a>
// 或者
<a v-on:[eventName]="doSomething"> ... </a>

在示例中,attributeNameeventName都会作为表达式被求值,将求得的值作为最终的参数使用。

如果 attributeNamehref ,那就等价于 v-bind:href

同理,如果当 eventName 的值为 "focus" 时,v-on:[eventName]将等价于 v-on:focus

监听组件的生命周期

假设我有一个父组件 <parent-component> 和 一个子组件 <child-component>, 我想在子组件更新的时候,父组件进行一些逻辑处理。一般可能会在子组件中的生命周期函数里 emit('something'),但其实有更简洁的方式

<template>
  <child-component @hook:updated="onUpdated">
</template>

这样就可以轻松在父组件内部轻松监听到子组件的生命周期,从而避免父子组件产生更强的耦合。

Vue2 中,这些事件名和相应的生命周期钩子一致,并带有 hook: 前缀,但是在 Vue3 中,事件名附带的是 vnode- 前缀:

<template>
  <child-component @vnode-updated="onUpdated">
</template>

组件样式特性

scoped

<style> 标签带有 scoped attribute 的时候,它的 CSS 只会应用到当前组件的元素上。通过PostCSS转换可以看到,在样式后面添加了[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>
  <div class="example" data-v-f3f3eg9>hi</div>
</template>

插槽选择器

我们在创建一个组件的时候,为了灵活性,有时候会在组件内部设置插槽,但是在默认情况下,该组件的作用域样式是影响不到 <slot/>里的内容的,因为这些内容被认为是父组件的,但是我们可以通过 :slotted 伪类以确切地将插槽内容作为选择器的目标。

<style scoped>
:slotted(div) {
  color: red;
}
</style>

深度选择器

在需求的开发中,难免会用到第三方的组件库,但是在 scoped 下,又不能对第三方组件样式进行修改,放在全局又容易对其他地方产生影响,这时候可以使用 :deep() 这个伪类, 或者使用 ::v-deep>>> 深度选择器。

<style scoped>
.a :deep(.b) {
  /* ... */
}
//或者
.a >>> .b {
  /* ... */
}
</style>
<style lang="scss" scoped>
.a ::v-deep .b {
  /* ... */
}
</style>

全局选择器

有时候需要在同一个组件中同时包含作用域样式和非作用域样式,会这样写:

<style>
/* global styles */
</style>

<style scoped>
/* local styles */
</style>

需要两个 <style> 标签,如果组件中需要的全局样式不是很多的时候,可以使用 :global 伪类来实现,就可以少写一个标签了。

<style scoped>
:global(.red) {
  color: red;
}
</style>

$event

有时候在绑定了事件时,我们需要传一些参数的情况下,还想获取原始的 DOM 事件,可以用特殊变量 $event 把它传入方法:

// 在方法中传入 $event
<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>
// ...
methods: {
  warn(message, event) {
    // 现在可以访问到原生事件
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}