作者:Michael Thiessen
译者:前端小智
起源:dev

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

学习成为一个更好的Vue开发者并不总是对于那些须要花工夫和精力能力把握的大概念。把握一些技巧和诀窍,能够让咱们的编程生存变得更容易--没有大量反复的工作。

在用 Vue 开发的这几年里,我学到了很多有用的技巧。有些很取巧,有些简直每天都在用,有些则更高级--但它们都很有用。

1. 将一个 prop 限度在一个类型的列表中

应用 prop 定义中的 validator 选项,能够将一个 prop 类型限度在一组特定的值中。

export default {  name: 'Image',  props: {    src: {      type: String,    },    style: {      type: String,      validator: s => ['square', 'rounded'].includes(s)    }  }};

这个验证函数承受一个prop,如果prop无效或有效,则返回truefalse

当单单传入的 true 或 false 来管制某些条件不能满足需要时,我通常应用这个办法来做。

按钮类型或正告类型(信息、胜利、危险、正告)是最常见的用法、、。色彩也是一个很好的用处。

2. 默认内容和扩大点

Vue中的槽能够有默认的内容,这使咱们能够制作出更容易应用的组件。

<button class="button" @click="$emit('click')">  <slot>    <!-- Used if no slot is provided -->    Click me  </slot></button>

我最喜爱应用默认槽,就是用它们来创立扩大点。

咱们能够取组件的任何局部,将其封装在一个插槽中,在里面咱们能够用想要的任何内容笼罩组件的该局部。默认状况下,它依然会依照原来的形式工作,但这样做会有了更多的选项

<template>  <button class="button" @click="$emit('click')">    <slot>      <div class="formatting">        {{ text }}      </div>    </slot>  </button></template>

当初咱们能够用许多不同的形式应用这个组件。简略的、默认的形式,或者自定义的形式。

<!-- Uses default functionality of the component --><ButtonWithExtensionPoint text="Formatted text" /><ButtonWithExtensionPoint>  <div class="different-formatting">    Do something a little different here  </div></ButtonWithExtensionPoint>

3. 应用引号来监听嵌套属性

你可能不晓得这一点,咱们能够通过应用引号轻松地间接监听嵌套值:

watch {  '$route.query.id'() {    // ...  }}

4. 晓得何时应用v-if(以及何时防止应用)

与其应用v-if,有时应用v-show来代替,会有更高的性能。

<ComplicatedChart v-show="chartEnabled" />

v-if被关上或敞开时,它将创立并齐全销毁该元素。相同,v-show将创立该元素并将其留在那里,通过设置其款式为display: none来暗藏它。

如果你要切换的组件的渲染老本很高,那么这样做会更有效率。

反过来说,如果不须要立刻执行低廉的组件件,能够应用v-if,这样它就会跳过渲染它,而使页面的加载速度更快一些。

5. 单个作用域插槽的简写(不须要 template 标签)

限定范畴的插槽十分乏味,但为了应用它们,您还必须应用许多模板标记。

侥幸的是,有一个简写能够让咱们解脱它,但只有在咱们应用单个作用域槽的状况下。

一般写法:

<DataTable>  <template #header="tableAttributes">    <TableHeader v-bind="tableAttributes" />  </template></DataTable>

不应用 template:

<DataTable #header="tableAttributes">  <TableHeader v-bind="tableAttributes" /></DataTable>

简略、含糊其辞,令人赞叹不已。

6. 有条件地渲染插槽

咱们先来看如何做,而后在探讨为什么想暗藏插槽。

每个Vue组件都有一个非凡的$slots对象,外面有你所有的插槽。默认槽的键是default,任何被命名的槽都应用其名称作为键。

const $slots = {  default: <default slot>,  icon: <icon slot>,  button: <button slot>,};

但这个$slots对象只有实用于该组件的插槽,而不是每一个定义的插槽。

拿这个定义了几个插槽的组件来说,包含几个命名的插槽。

<!-- Slots.vue --><template>  <div>    <h2>Here are some slots</h2>    <slot />    <slot name="second" />    <slot name="third" />  </div></template>

如果咱们只对组件利用一个插槽,那么只有那个插槽会显示在咱们的$slots对象中。

<template>  <Slots>    <template #second>      This will be applied to the second slot.    </template>  </Slots></template>
$slots = { second: <vnode> }

咱们能够在咱们的组件中应用这一点来检测哪些插槽曾经被利用到组件中,例如,通过暗藏插槽的包装元素。

<template>  <div>    <h2>A wrapped slot</h2>    <div v-if="$slots.default" class="styles">      <slot />    </div>  </div></template>

当初,利用款式的包装器div只有在咱们用某些货色填充这个插槽时才会被渲染。

如果不应用v-if,那么如果没有插槽,就会失去一个空的不必要的div。依据div的款式,这可能会打乱咱们的布局,让界面看起来很奇怪。

那么,为什么咱们心愿可能有条件地渲染插槽呢?

应用条件插槽的次要起因有三个:

  • 当应用封装的div来增加默认款式时
  • 插槽是空的
  • 如果咱们将默认内容与嵌套槽相结合

例如,当咱们在增加默认款式时,咱们在插槽四周增加一个div:

<template>  <div>    <h2>This is a pretty great component, amirite?</h2>    <div class="default-styling">      <slot >    </div>    <button @click="$emit('click')">Click me!</button>  </div></template>

然而,如果父组件没有将内容利用到该插槽中,咱们最终会在页面上渲染出一个空的div

<div>  <h2>This is a pretty great component, amirite?</h2>  <div class="default-styling">    <!-- 槽中没有内容,但这个div 依然被渲染。蹩脚 -->  </div>  <button @click="$emit('click')">Click me!</button></div>

解决办法就是像下面讲的一样,多个条件判断,就行啦。

7. 如何监听一个插槽的变动

有时咱们须要晓得插槽内的内容何时产生了变动。

<!-- 惋惜这个事件不存在 --><slot @change="update" />

可怜的是,Vue没有内置的办法让咱们检测这一点。

然而,我的敌人Austin想出了一个十分洁净的办法,应用MutationObserver来做这件事。

MutationObserver接口提供了监督对DOM树所做更改的能力。它被设计为旧的Mutation Events性能的替代品,该性能是DOM3 Events标准的一部分。
export default {  mounted() {    // 当有变动时调用`update`    const observer = new MutationObserver(this.update);    // 监听此组件的变动    observer.observe(this.$el, {      childList: true,      subtree: true    });  }};

这个波及的内容还是很多的,前面会独自出一篇文章来讲,记得关注刷碗智的公众号 <大迁世界> 哦

8. 将部分和全局的 style混合在一起

通常状况下,在解决款式时,咱们心愿它们能被划分到一个独自的组件中。

<style scoped>  .component {    background: green;  }</style>

不过,如果需要的话,也能够增加一个非作用域款式块来增加全局款式

<style>  /* 全局 */  .component p {    margin-bottom: 16px;  }</style><style scoped>  /* 在该组件内无效 */  .component {    background: green;  }</style>

但要小心,全局款式是危险的,难以追踪。但有时,它们是一个完满的逃生舱口,正是你所须要的。

9. 重写子组件的款式--正确的办法

Scoped CSS在放弃内容整洁方面十分棒,而且不会将款式引入利用的其余组件中。

但有时你须要笼罩一个子组件的款式,并跳出这个作用域。

Vue有一个 deep 选择器:

<style scoped>.my-component >>> .child-component {  font-size: 24px;}</style>

留神:如果你应用像SCSS这样的CSS预处理器,你可能须要应用/deep/来代替。

10. 用上下文感知组件发明魔法

上下文感知组件(context-aware)是“魔法的”,它们主动适应四周产生的事件,解决边缘状况、状态共享等等。

有3种次要的 context-aware ,但 Configuration 是我最感兴趣的一种。

1.状态共享

当你把一个大的组件分解成多个小的组件时,它们往往依然须要共享状态。

咱们能够在 "幕后 "做这些工作,而不是把这些工作推给使用者。

咱们个别会把 Dropdown 组件分解成 SelectOption 组件,这样会取得更多的灵活性。然而为了方便使用,Select Option 组件彼此共享 selected 状态。

<!-- 为简略起见,作为一个繁多组件应用 --><Dropdown v-model="selected" :options="[]" /><!-- 分多个组件,更灵便 --><Select v-model="selected">  <Option value="mustard">Mustard</Option>  <Option value="ketchup">Ketchup</Option>  <div class="relish-wrapper">    <Option value="relish">Relish</Option>  </div></Select>

2. Configuration

有时,一个组件的行为须要依据应用程序的其余局部的状况来扭转。这通常是为了主动解决边缘状况,否则解决起来会很烦人。

一个 PopupTooltip 应该从新定位,以便它不会溢出页面。然而,如果该组件是在一个modal 内,它应该从新定位,以便它不会溢出 modal。

如果Tooltip晓得它是在一个模态外面,这能够主动实现。

3.款式

创立了 context-aware 的CSS,依据父级或同级元素的状况利用不同的款式。

.statistic {  color: black;  font-size: 24px;  font-weight: bold;}.statistic + .statistic {  margin-left: 10px;}

CSS变量让咱们更进一步,容许咱们在一个页面的不同局部设置不同的值。

11. 如何在Vue之外创立一个具备响应性的变量(Vue2和3)

如果你从Vue之外失去一个变量,让它具备反馈性是很好的。

这样,咱们就能够在computed propswatch和其余任何中央应用它,它的工作形式就像Vue中的任何其余状态一样。

如果咱们应用的选项API,须要的只是将其放在组件的数据局部中:

const externalVariable = getValue();export default {  data() {    return {      reactiveVariable: externalVariable,    };  }};

如果应用Vue3的组合API,能够间接应用refreactive

import { ref } from 'vue';// 能够齐全在Vue组件之外实现const externalVariable = getValue();const reactiveVariable = ref(externalVariable);console.log(reactiveVariable.value);

应用 reactive 代替:

import { reactive } from 'vue';//  能够齐全在Vue组件之外实现const externalVariable = getValue();// reactive 只对对象和数组起作用const anotherReactiveVariable = reactive(externalVariable);// Access directlyconsole.log(anotherReactiveVariable);

如果你还在应用 Vue2,你能够应用observable而不是reactive来实现完全相同的后果。

12. v-for 中的解构

你晓得能够在-vfor中应用解构吗?

<li  v-for="{ name, id } in users"  :key="id">  {{ name }}</li>

更广为人知的是,能够通过应用这样的元组从v-for中取出索引。

<li v-for="(movie, index) in [  'Lion King',  'Frozen',  'The Princess Bride']">  {{ index + 1 }} - {{ movie }}</li>

当应用一个对象时,能够这样应用 key

<li v-for="(value, key) in {  name: 'Lion King',  released: 2019,  director: 'Jon Favreau',}">  {{ key }}: {{ value }}</li>

也能够将这两种办法联合起来,获取key以及属性的 index

<li v-for="(value, key, index) in {  name: 'Lion King',  released: 2019,  director: 'Jon Favreau',}">  #{{ index + 1 }}. {{ key }}: {{ value }}</li>

13. 在指定范畴内循环

v-for指令容许咱们遍历数组,但它也容许咱们遍历一个范畴

<template>  <ul>    <li v-for="n in 5">Item #{{ n }}</li>  </ul></template>

渲染后果:

Item #1Item #2Item #3Item #4Item #5

当咱们应用带范畴的v-for时,它将从1开始,以咱们指定的数字完结。

14. 监听你的组件中的任何货色

export default {  computed: {    someComputedProperty() {      // Update the computed prop    },  },  watch: {    someComputedProperty() {      // Do something when the computed prop is updated    }  }};

咱们能够监听:

  • 计算属性
  • props
  • 嵌套值

如果你应用组合API,任何值都能够被监督,只有它是一个refreactive对象。

15.窃取 prop 类型

我从一个子组件中复制 prop 类型,只是为了在一个父组件中应用它们。但我发现,偷取这些 prop 类型要比仅仅复制它们好得多。

例如,咱们在这个组件中应用了一个Icon组件。

<template>  <div>    <h2>{{ heading }}</h2>    <Icon      :type="iconType"      :size="iconSize"      :colour="iconColour"    />  </div></template>

为了让它工作,咱们须要增加正确的 prop 类型,从`Icon组件复制。

import Icon from './Icon';export default {  components: { Icon },  props: {    iconType: {      type: String,      required: true,    },    iconSize: {      type: String,      default: 'medium',      validator: size => [        'small',        'medium',        'large',        'x-large'      ].includes(size),    },    iconColour: {      type: String,      default: 'black',    },    heading: {      type: String,      required: true,    },  },};

如许苦楚啊。

Icon 组件的 prop类型被更新时,咱们必定会遗记返回这个组件并更新它们。随着工夫的推移,当该组件的 prop类型开始偏离Icon组件中的 prop 类型时,就会引入谬误。

因而,这就是为什么咱们要窃取组件的 prop 类型:

import Icon from './Icon';export default {  components: { Icon },  props: {    ...Icon.props,    heading: {      type: String,      required: true,    },  },};

不须要再简单了。

除了在咱们的例子中,咱们把 icon 加在每个 prop 名称的结尾。所以咱们必须做一些额定的工作来实现这一点。

import Icon from './Icon';const iconProps = {};Object.entries(Icon.props).forEach((key, val) => {  iconProps[`icon${key.toUpperCase()}`] = val;});export default {  components: { Icon },  props: {    ...iconProps,    heading: {      type: String,      required: true,    },  },};

当初,如果Icon组件中的 prop 类型被批改,咱们的组件将放弃最新状态。

然而,如果一个 prop 类型从 Icon 组件中被增加或删除了呢?为了应答这些状况,咱们能够应用v-bind和一个计算的 prop 来放弃动静。

16. 检测元素内部(或外部)的单击

有时我须要检测一个点击是产生在一个特定元素el的外部还是内部。这就是我通常应用的办法。

window.addEventListener('mousedown', e => {  // 获取被点击的元素  const clickedEl = e.target;    if (el.contains(clickedEl)) {   //在 "el "外面点击了  } else {   //在 "el "外点击了  }});

17. 递归插槽

有一次,我决定看看我是否能够只用模板来做一个v-for组件。在这个过程中,我也发现了如何递归地应用槽。

<!-- VFor.vue --><template>    <div>        <!--  渲染第一项 -->    {{ list[0] }}        <!-- 如果咱们有更多的我的项目,持续!然而不要应用咱们刚刚渲染的项 -->    <v-for      v-if="list.length > 1"            :list="list.slice(1)"        />    </div></template>

如果你想用作用域插槽来做这件事,只是须要一些调整

<template>  <div>    <!-- Pass the item into the slot to be rendered -->    <slot v-bind:item="list[0]">      <!-- Default -->      {{ list[0] }}    </slot>    <v-for      v-if="list.length > 1"      :list="list.slice(1)"    >      <!-- Recursively pass down scoped slot -->      <template v-slot="{ item }">        <slot v-bind:item="item" />      </template>    </v-for>  </div></template>

上面是这个组件的应用办法。

<template>  <div>    <!-- 惯例列表 -->    <v-for :list="list" />    <!-- 加粗的我的项目列表 -->    <v-for :list="list">      <template v-slot="{ item }">        <strong>{{ item }}</strong>      </template>    </v-for>  </div></template>

18. 组件元数据

并不是增加到一个组件的每一点信息都是状态。有时咱们须要增加一些元数据,给其余组件提供更多信息。

例如,如果正在为谷歌 analytics这样的剖析仪表:

如果你想让布局晓得每个小组件应该占多少列,你能够间接在组件上增加元数据。

export default {  name: 'LiveUsersWidget',  //  只需将其作为一个额定的属性增加  columns: 3,  props: {    // ...  },  data() {    return {      //...    };  },};
export default {  name: 'LiveUsersWidget',  //   只需将其作为一个额定的属性增加  columns: 3,  props: {    // ...  },  data() {    return {      //...    };  },};

你会发现这个元数据是组件上的一个属性。

import LiveUsersWidget from './LiveUsersWidget.vue';const { columns } = LiveUsersWidget;

咱们也能够通过非凡的$options属性从组件外部拜访元数据。

export default {  name: 'LiveUsersWidget',  columns: 3,  created() {    //  `$options` contains all the metadata for a component    console.log(`Using ${this.$options.metadata} columns`);  },};

只有记住,这个元数据对组件的每个实例都是一样的,而且不是响应式的。

这方面的其余用处包含(但不限于):

  • 放弃单个组件的版本号
  • 用于构建工具的自定义标记,以区别对待组件
  • 在计算属性、数据、watch 等之外为组件增加自定义性能
  • 其它

19. 多文件单文件组件

这是SFC(单文件组件)的一点已知性能。

能够像惯例HTML文件一样导入文件:

<template src="./template.html"></template><script src="./script.js"></script><style scoped src="./styles.css"></style>

如果你须要分享款式、文件或其余任何货色,这可能会十分不便。

20. 可重复使用的组件并不是你所想的那样

可重复使用的组件不肯定是大的或简单的货色。

我常常让小的和短的组件能够重复使用。

因为我没有到处重写这段代码,所以更新它变得更加容易,而且我能够确保每个OverflowMenu的外观和工作形式都齐全一样--因为它们是一样的!"。

<!-- OverflowMenu.vue --><template>  <Menu>    <!-- 增加一个自定义按钮来触发咱们的菜单   -->    <template #button v-slot="bind">      <!-- 应用bind来传递click处理程序、a11y 属性等 -->      <Button v-bind="bind">        <template #icon>          <svg src="./ellipsis.svg" />        </template>      </Button>    </template>  </Menu></template>

在这里,咱们采纳了一个菜单组件,但在触发它的按钮上增加了一个 ellipsis 图标。(省略号)的图标来触发它的关上。

这仿佛不值得把它做成一个可重复使用的组件,因为它只有几行。难道咱们就不能在每次要应用这样的菜单时增加图标吗?

然而这个OverflowMenu将被应用几十次,当初如果咱们想更新图标或它的行为,咱们能够非常容易地做到。而且,应用它也更简略了。

21. 从组件内部调用一个办法

咱们能够从一个组件的内部通过给它一个 ref 用来调用一个办法。

<!-- Parent.vue --><template>  <ChildComponent ref="child" /></template>
// Somewhere in Parent.vuethis.$refs.child.method();

再解释一下这个问题。

有时候,“最佳实际”并不适用于你正在做的事件,你须要一个像这样的逃生口。

通常状况下,咱们应用 props 和 events 在组件之间进行交换。props 被下发到子组件中,而events 被上发到父组件中。

<template>  <ChildComponent    :tell-me-what-to-do="someInstructions"    @something-happened="hereIWillHelpYouWithThat"  /></template>
// Child.vueexport default {  props: ['trigger'],  watch: {    shouldCallMethod(newVal) {      if (newVal) {        // Call the method when the trigger is set to `true`        this.method();      }    }  }}

这能够失常工作,但只能在第一次调用时应用。如果您须要屡次触发此操作,则必须进行清理并重置状态。逻辑是这样的

  • 父组件将 true 传递给触发器 prop
  • Watch 被触发,而后 Child 组件调用该办法
  • 子组件收回一个事件,通知父组件该办法已被胜利触发
  • Parent组件将 trigger 重置为 false,所以咱们能够从头再来一次

相同,如果咱们在子组件上设置一个 ref,咱们能够间接调用该办法:

<!-- Parent.vue --><template>  <ChildComponent ref="child" /></template>
// Somewhere in Parent.vuethis.$refs.child.method();

是的,咱们突破了 “props down, events up"” 的规定,咱们突破了封装,然而这样做更清晰,更容易了解,所以是值得的

有时,"最好的 "解决方案最终会成为最蹩脚的解决方案。

22. 监听数组和对象

应用 watcher 最辣手的局部是,有时它仿佛不能正确触发。

通常,这是因为咱们试图监听数组或对象,但没有将deep设置为true

export default {  name: 'ColourChange',  props: {    colours: {      type: Array,      required: true,    },  },  watch: {    // 应用对象语法,而不仅仅是办法    colours: {      // 这将让Vue晓得要在数组外部寻找      deep: true,      handler()        console.log('The list of colours has changed!');      }    }  }}

应用Vue 3的API会是这样的:

watch(  colours,  () => {    console.log('The list of colours has changed!');  },  {    deep: true,  });

23. 用Vue Router进行深度链接

咱们能够在URL中存储(一点)状态,容许咱们间接跳到页面上的一个特定状态。

例如,你加载一个曾经抉择了日期范畴过滤器的页面:

someurl.com/edit?date-range=last-week

这对于利用中用户可能共享大量链接的局部来说是十分棒的,对于服务器渲染的利用,或者在两个独立的利用之间通信的信息比一般链接通常提供的更多。

咱们能够存储过滤器、搜寻值、模态框是关上还是敞开,或者在列表的哪个地位滚动以完满地实现有限分页。

应用 vue-router 获取查问参数是这样工作的(这也实用于大多数Vue框架,如Nuxt和Vuepress):

const dateRange = this.$route.query.dateRange;

为了扭转它,咱们应用 RouterLink 组件并更新 query

<RouterLink :to="{  query: {    dateRange: newDateRange  }}">

24. template 标签的另一个用处

template 标签能够在模板中的任何中央应用,以更好地组织代码。

我喜爱用它来简化v-if逻辑,有时也用v-for

在这个例子中,咱们有几个元素都应用同一个v-if条件。

<template>  <div class="card">    <img src="imgPath" />    <h3>      {{ title }}    </h3>    <h4 v-if="expanded">      {{ subheading }}    </h4>    <div      v-if="expanded"      class="card-content"    >      <slot />    </div>    <SocialShare v-if="expanded" />  </div></template>

这有点蠢笨,而且一开始并不显著,一堆这样的元素被显示和暗藏在一起。在一个更大、更简单的组件上,这可能是一个更蹩脚的状况

但咱们能优化它。

咱们能够应用 template 标签来分组这些元素,并将 v-if 晋升到模板 template 自身。

<template>  <div class="card">    <img src="imgPath" />    <h3>      {{ title }}    </h3>    <template v-if="expanded">      <h4>        {{ subheading }}      </h4>      <div class="card-content">        <slot />      </div>      <SocialShare />    </template>  </div></template>

当初看起来就更容易了解,而且它在做什么,高深莫测。

25. 处理错误(和正告)的更好办法

咱们能够为Vue中的谬误和正告提供一个自定义处理程序。

// Vue 3const app = createApp(App);app.config.errorHandler = (err) => {  alert(err);};// Vue 2Vue.config.errorHandler = (err) => {  alert(err);};

像 Bugsnag 和 Rollbar 这样的谬误跟踪服务,能够钩住这些处理程序来记录谬误,但你也能够用它们来更优雅地处理错误,以取得更好的用户体验。

例如,如果一个谬误未被解决,应用程序不会间接解体,你能够显示一个残缺的谬误屏幕,让用户刷新或尝试其余货色。

在 Vue3 中,谬误处理程序只能解决 templatewatcher 谬误,然而 Vue2的谬误处理程序能够捕捉简直所有谬误。这两个版本中的正告处理程序只在开发阶段无效。

~ 完,我是刷碗智,刷碗,睡觉,打Lol 去。


代码部署后可能存在的BUG没法实时晓得,预先为了解决这些BUG,花了大量的工夫进行log 调试,这边顺便给大家举荐一个好用的BUG监控工具 Fundebug。

原文:https://dev.to/michaelthiesse...

交换

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq44924588... 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。