十分钟,让你学会Vue的这些巧妙冷技巧

学习成为一个更好的Vue开发者并不总是关于那些需要花时间和精力才能掌握的大概念。掌握一些技巧和窍门,可以让我们的编程生活变得更容易--没有大量重复的工作。

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

1. 将一个 prop 限制在一个类型的列表中 {#将一个-prop-限制在一个类型的列表中 data-id="heading-0"}

使用 prop 定义中的 validator 选项,可以将一个 prop 类型限制在一组特定的值中。

<pre class="public-DraftStyleDefault-pre" data-offset-key="ejtp2-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="ejtp2-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

export default {
name: 'Image',
props: {
src: {
type: String,
},
style: {
type: String,
validator: s => ['square', 'rounded'].includes(s)
}
}
};
复制代码
<button class="button" @click="emit('click')"> <slot> Click me </slot> </button> 复制代码 <template> <button class="button" @click="emit('click')">
<slot>
<div class="formatting">
{{ text }}
</div>
</slot>
</button>
</template>
复制代码

<ButtonWithExtensionPoint text="Formatted text" />

<ButtonWithExtensionPoint>
<div class="different-formatting">
Do something a little different here
</div>
</ButtonWithExtensionPoint>
复制代码
watch {
'route.query.id'() { // ... } } 复制代码 <ComplicatedChart v-show="chartEnabled" /> 复制代码 <DataTable> <template #header="tableAttributes"> <TableHeader v-bind="tableAttributes" /> </template> </DataTable> 复制代码 <DataTable #header="tableAttributes"> <TableHeader v-bind="tableAttributes" /> </DataTable> 复制代码 constslots = {
default: <default slot>,
icon: <icon slot>,
button: <button slot>,
};
复制代码

<template>
<div>
<h2>Here are some slots</h2>
<slot />
<slot name="second" />
<slot name="third" />
</div>
</template>
复制代码
<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>
复制代码
<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> <h2>This is a pretty great component, amirite?</h2> <div class="default-styling"> </div> <button @click="emit('click')">Click me!</button>
</div>
复制代码

<slot @change="update" />
复制代码
export default {
mounted() {
// 当有变化时调用update
const observer = new MutationObserver(this.update);

// 监听此组件的变化
observer.observe(this.$el, {
  childList: true,
  subtree: true
});

}
};
复制代码
<style scoped>
.component {
background: green;
}
</style>
复制代码

</pre>

</pre>

在 Vue3 中,错误处理程序只能处理 template 和 watcher 错误,但是 Vue2的错误处理程序可以捕获几乎所有错误。这两个版本中的警告处理程序只在开发阶段有效。 :::

例如,如果一个错误未被处理,应用程序不会直接崩溃,你可以显示一个完整的错误屏幕,让用户刷新或尝试其他东西。

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="1o3k1-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="1o3k1-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

// Vue 2
Vue.config.errorHandler = (err) => {
alert(err);
};
复制代码

</pre>

</pre>

我们可以为Vue中的错误和警告提供一个自定义处理程序。

25. 处理错误(和警告)的更好方法 {#处理错误和警告的更好方法 data-id="heading-28"}

现在看起来就更容易理解,而且它在做什么,一目了然。

<pre class="public-DraftStyleDefault-pre" data-offset-key="4ctqd-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="4ctqd-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

<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>
复制代码

</pre>

</pre>

我们可以使用 template 标签来分组这些元素,并将 v-if 提升到模板 template 本身。

但我们能优化它。

这有点笨拙,而且一开始并不明显,一堆这样的元素被显示和隐藏在一起。在一个更大、更复杂的组件上,这可能是一个更糟糕的情况

<pre class="public-DraftStyleDefault-pre" data-offset-key="4hvo2-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="4hvo2-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

<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>
复制代码

</pre>

</pre>

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

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

template 标签可以在模板中的任何地方使用,以更好地组织代码。

24. template 标签的另一个用途 {#template-标签的另一个用途 data-id="heading-27"}

<pre class="public-DraftStyleDefault-pre" data-offset-key="54tvh-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="54tvh-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

<RouterLink :to="{
query: {
dateRange: newDateRange
}
}">
复制代码

</pre>

</pre>

为了改变它,我们使用 RouterLink 组件并更新 query。

<pre class="public-DraftStyleDefault-pre" data-offset-key="57gce-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="57gce-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

const dateRange = this.$route.query.dateRange;
复制代码

</pre>

</pre>

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

我们可以存储过滤器、搜索值、模态框是打开还是关闭,或者在列表的哪个位置滚动以完美地实现无限分页。

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="41ohb-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="41ohb-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

someurl.com/edit?date-range=last-week
复制代码

</pre>

</pre>

例如,你加载一个已经选择了日期范围过滤器的页面:

我们可以在URL中存储(一点)状态,允许我们直接跳到页面上的一个特定状态。

23. 用Vue Router进行深度链接 {#用vue-router进行深度链接 data-id="heading-26"}

<pre class="public-DraftStyleDefault-pre" data-offset-key="e6g9u-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="e6g9u-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

</pre>

</pre>

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="e91pl-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="e91pl-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

export default {
name: 'ColourChange',
props: {
colours: {
type: Array,
required: true,
},
},
watch: {
// 使用对象语法,而不仅仅是方法
colours: {
// 这将让Vue知道要在数组内部寻找
deep: true,

  handler()
    console.log('The list of colours has changed!');
  }
}

}
}
复制代码

</pre>

</pre>

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

使用 watcher 最棘手的部分是,有时它似乎不能正确触发。

22. 监听数组和对象 {#监听数组和对象 data-id="heading-25"}

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

是的,我们打破了 "props down, events up"" 的规则,我们打破了封装,但是这样做更清晰,更容易理解,所以是值得的

<pre class="public-DraftStyleDefault-pre" data-offset-key="5316b-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="5316b-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">


<template>
<ChildComponent ref="child" />
</template>
复制代码

// Somewhere in Parent.vue
this.$refs.child.method();
复制代码

</pre>

</pre>

相反,如果我们在子组件上设置一个 ref,我们可以直接调用该方法:

  • 父组件将 true 传递给触发器 prop

  • Watch 被触发,然后 Child 组件调用该方法

  • 子组件发出一个事件,告诉父组件该方法已被成功触发

  • Parent组件将 trigger 重置为 false,所以我们可以从头再来一次

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="asee1-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="asee1-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

<template>
<ChildComponent
:tell-me-what-to-do="someInstructions"
@something-happened="hereIWillHelpYouWithThat"
/>
</template>
复制代码

// Child.vue
export default {
props: ['trigger'],
watch: {
shouldCallMethod(newVal) {
if (newVal) {
// Call the method when the trigger is set to true
this.method();
}
}
}
}
复制代码

</pre>

</pre>

通常情况下,我们使用 props 和 events 在组件之间进行交流。props 被下发到子组件中,而events 被上发到父组件中。

有时候,"最佳实践"并不适用于你正在做的事情,你需要一个像这样的逃生口。

再解释一下这个问题。

<pre class="public-DraftStyleDefault-pre" data-offset-key="cs0su-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="cs0su-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">


<template>
<ChildComponent ref="child" />
</template>
复制代码

// Somewhere in Parent.vue
this.$refs.child.method();
复制代码

</pre>

</pre>

我们可以从一个组件的外部通过给它一个 ref 用来调用一个方法。

21. 从组件外部调用一个方法 {#从组件外部调用一个方法 data-id="heading-24"}

但是这个OverflowMenu将被使用几十次,现在如果我们想更新图标或它的行为,我们可以非常容易地做到。而且,使用它也更简单了。

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

在这里,我们采用了一个菜单组件,但在触发它的按钮上添加了一个 ellipsis 图标。(省略号)的图标来触发它的打开。

<pre class="public-DraftStyleDefault-pre" data-offset-key="44acs-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="44acs-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">


<template>
<Menu>

<template #button v-slot="bind">

<Button v-bind="bind">
<template #icon>
<svg src="./ellipsis.svg" />
</template>
</Button>
</template>
</Menu>
</template>
复制代码

</pre>

</pre>

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

我经常让小的和短的组件可以重复使用。

可重复使用的组件不一定是大的或复杂的东西。

20. 可重复使用的组件并不是你所想的那样 {#可重复使用的组件并不是你所想的那样 data-id="heading-23"}

如果你需要分享样式、文件或其他任何东西,这可能会非常方便。

<pre class="public-DraftStyleDefault-pre" data-offset-key="8kcgm-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="8kcgm-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

<template src="./template.html"></template>
<script src="./script.js"></script>
<style scoped src="./styles.css"></style>
复制代码

</pre>

</pre>

可以像常规HTML文件一样导入文件:

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

19. 多文件单文件组件 {#多文件单文件组件 data-id="heading-22"}

  • 保持单个组件的版本号

  • 用于构建工具的自定义标志,以区别对待组件

  • 在计算属性、数据、watch 等之外为组件添加自定义功能

  • 其它

这方面的其他用途包括(但不限于):

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="1u8gk-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="1u8gk-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

</pre>

</pre>

我们也可以通过特殊的$options属性从组件内部访问元数据。

<pre class="public-DraftStyleDefault-pre" data-offset-key="2dr-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="2dr-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

import LiveUsersWidget from './LiveUsersWidget.vue';
const { columns } = LiveUsersWidget;
复制代码

</pre>

</pre>

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="5ggh3-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="5ggh3-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

export default {
name: 'LiveUsersWidget',
// 👇 只需将其作为一个额外的属性添加
columns: 3,
props: {
// ...
},
data() {
return {
//...
};
},
};
复制代码

export default {
name: 'LiveUsersWidget',
// 👇 只需将其作为一个额外的属性添加
columns: 3,
props: {
// ...
},
data() {
return {
//...
};
},
};
复制代码

</pre>

</pre>

如果你想让布局知道每个小组件应该占多少列,你可以直接在组件上添加元数据。

[图片上传失败...(image-61f25d-1654309788446)]

编辑 切换为居中

<figcaption class="Image-caption is-placeholder Image-captionV2" style="margin-top: 3px; padding: 0px 1em; font-size: 0.9em; line-height: 1.5; text-align: center; color: rgb(191, 191, 191); font-weight: 400;">添加图片注释,不超过 140 字(可选)</figcaption>

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

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

18. 组件元数据 {#组件元数据 data-id="heading-21"}

<pre class="public-DraftStyleDefault-pre" data-offset-key="6ldr7-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="6ldr7-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

<template>
<div>

<v-for :list="list" />

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

</div>
</template>
复制代码

</pre>

</pre>

下面是这个组件的使用方法。

<pre class="public-DraftStyleDefault-pre" data-offset-key="26mti-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="26mti-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

<template>
<div>

<slot v-bind:item="list[0]">

{{ 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>
复制代码

</pre>

</pre>

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="b20pd-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="b20pd-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">


<template>
<div>

{{ list[0] }}

<v-for
v-if="list.length > 1"
:list="list.slice(1)"
/>
</div>
</template>
复制代码

</pre>

</pre>

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

17. 递归插槽 {#递归插槽 data-id="heading-20"}

<pre class="public-DraftStyleDefault-pre" data-offset-key="1ei2j-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="1ei2j-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

window.addEventListener('mousedown', e => {
// 获取被点击的元素
const clickedEl = e.target;

if (el.contains(clickedEl)) {
//在 "el "里面点击了
} else {
//在 "el "外点击了
}
});
复制代码

</pre>

</pre>

有时我需要检测一个点击是发生在一个特定元素el的内部还是外部。这就是我通常使用的方法。

16. 检测元素外部(或内部)的单击 {#检测元素外部或内部的单击 data-id="heading-19"}

但是,如果一个 prop 类型从 Icon 组件中被添加或删除了呢?为了应对这些情况,我们可以使用v-bind和一个计算的 prop 来保持动态。

现在,如果Icon组件中的 prop 类型被修改,我们的组件将保持最新状态。

<pre class="public-DraftStyleDefault-pre" data-offset-key="64qvb-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="64qvb-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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,
},
},
};
复制代码

</pre>

</pre>

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

不需要再复杂了。

<pre class="public-DraftStyleDefault-pre" data-offset-key="8f5v-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="8f5v-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

</pre>

</pre>

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

当 Icon 组件的 prop类型被更新时,我们肯定会忘记返回这个组件并更新它们。随着时间的推移,当该组件的 prop类型开始偏离Icon组件中的 prop 类型时,就会引入错误。

多么痛苦啊。

<pre class="public-DraftStyleDefault-pre" data-offset-key="4m6dm-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="4m6dm-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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,
},
},
};
复制代码

</pre>

</pre>

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="3iq26-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="3iq26-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

</pre>

</pre>

例如,我们在这个组件中使用了一个Icon组件。

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

15.窃取 prop 类型 {#窃取-prop-类型 data-id="heading-18"}

如果你使用组合API,任何值都可以被监视,只要它是一个ref或reactive对象。

  • 计算属性

  • props

  • 嵌套值

我们可以监听:

<pre class="public-DraftStyleDefault-pre" data-offset-key="d6vgu-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="d6vgu-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

</pre>

</pre>

14. 监听你的组件中的任何东西 {#监听你的组件中的任何东西 data-id="heading-17"}

当我们使用带范围的v-for时,它将从1开始,以我们指定的数字结束。

<pre class="public-DraftStyleDefault-pre" data-offset-key="akfa7-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="akfa7-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

Item #1
Item #2
Item #3
Item #4
Item #5
复制代码

</pre>

</pre>

渲染结果:

<pre class="public-DraftStyleDefault-pre" data-offset-key="5551n-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="5551n-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

</pre>

</pre>

v-for指令允许我们遍历数组,但它也允许我们遍历一个范围

13. 在指定范围内循环 {#在指定范围内循环 data-id="heading-16"}

<pre class="public-DraftStyleDefault-pre" data-offset-key="djcb8-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="djcb8-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

{{ index + 1 }}. {{ key }}: {{ value }}

</li>
复制代码

</pre>

</pre>

也可以将这两种方法结合起来,获取key以及属性的 index。

<pre class="public-DraftStyleDefault-pre" data-offset-key="20nae-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="20nae-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

</pre>

</pre>

当使用一个对象时,可以这样使用 key:

<pre class="public-DraftStyleDefault-pre" data-offset-key="2pmaq-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="2pmaq-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

</pre>

</pre>

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="c0kjs-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="c0kjs-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

{{ name }}
</li>
复制代码

</pre>

</pre>

你知道可以在-vfor中使用解构吗?

12. v-for 中的解构 {#v-for-中的解构 data-id="heading-15"}

如果你还在使用 Vue2,你可以使用observable而不是reactive来实现完全相同的结果。

<pre class="public-DraftStyleDefault-pre" data-offset-key="476h1-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="476h1-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

import { reactive } from 'vue';

// 可以完全在Vue组件之外完成
const externalVariable = getValue();
// reactive 只对对象和数组起作用
const anotherReactiveVariable = reactive(externalVariable);

// Access directly
console.log(anotherReactiveVariable);
复制代码

</pre>

</pre>

使用 reactive 代替:

<pre class="public-DraftStyleDefault-pre" data-offset-key="5deh3-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="5deh3-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

import { ref } from 'vue';

// 可以完全在Vue组件之外完成
const externalVariable = getValue();
const reactiveVariable = ref(externalVariable);

console.log(reactiveVariable.value);
复制代码

</pre>

</pre>

如果使用Vue3的组合API,可以直接使用ref或reactive。

<pre class="public-DraftStyleDefault-pre" data-offset-key="e970m-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="e970m-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

const externalVariable = getValue();

export default {
data() {
return {
reactiveVariable: externalVariable,
};
}
};
复制代码

</pre>

</pre>

如果我们使用的选项API,需要的只是将其放在组件的数据部分中:

这样,我们就可以在computed props、watch和其他任何地方使用它,它的工作方式就像Vue中的任何其他状态一样。

如果你从Vue之外得到一个变量,让它具有反应性是很好的。

11. 如何在Vue之外创建一个具有响应性的变量(Vue2和3) {#如何在vue之外创建一个具有响应性的变量vue2和3 data-id="heading-14"}

CSS变量让我们更进一步,允许我们在一个页面的不同部分设置不同的值。

<pre class="public-DraftStyleDefault-pre" data-offset-key="dmj1i-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="dmj1i-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

.statistic {
color: black;
font-size: 24px;
font-weight: bold;
}

.statistic + .statistic {
margin-left: 10px;
}
复制代码

</pre>

</pre>

创建了 context-aware的CSS,根据父级或同级元素的情况应用不同的样式。

3.样式 {#样式 data-id="heading-13"}

2. Configuration 有时,一个组件的行为需要根据应用程序的其他部分的情况来改变。这通常是为了自动处理边缘情况,否则处理起来会很烦人。 一个 Popup 或 Tooltip 应该重新定位,以便它不会溢出页面。但是,如果该组件是在一个modal 内,它应该重新定位,以便它不会溢出 modal。 如果Tooltip知道它是在一个模态里面,这可以自动完成。

<pre class="public-DraftStyleDefault-pre" data-offset-key="dbhkf-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="dbhkf-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">


<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>
复制代码

</pre>

</pre>

我们一般会把 Dropdown 组件分解成 Select 和 Option 组件,这样会获得更多的灵活性。但是为了方便使用,Select 和Option组件彼此共享 selected 状态。

我们可以在 "幕后 "做这些工作,而不是把这些工作推给使用者。

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

1.状态共享 {#状态共享 data-id="heading-11"}

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

上下文感知组件(context-aware)是"魔法的",它们自动适应周围发生的事情,处理边缘情况、状态共享等等。

10. 用上下文感知组件创造魔法 {#用上下文感知组件创造魔法 data-id="heading-10"}

注意:如果你使用像SCSS这样的CSS预处理器,你可能需要使用/deep/来代替。

<pre class="public-DraftStyleDefault-pre" data-offset-key="def7g-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="def7g-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

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

</pre>

</pre>

Vue有一个 deep 选择器:

但有时你需要覆盖一个子组件的样式,并跳出这个作用域。

Scoped CSS在保持内容整洁方面非常棒,而且不会将样式引入应用的其他组件中。

9. 重写子组件的样式--正确的方法 {#重写子组件的样式--正确的方法 data-id="heading-9"}

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

<pre class="public-DraftStyleDefault-pre" data-offset-key="4ojtm-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(18, 18, 18); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="6buem" data-offset-key="4ojtm-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

<style>
/* 全局 */
.component p {
margin-bottom: 16px;
}
</style>

<style scoped>
/* 在该组件内有效 */
.component {
background: green;
}
</style>
复制代码

</pre>

</pre>

不过,如果需要的话,也可以添加一个非作用域样式块来添加全局样式

通常情况下,在处理样式时,我们希望它们能被划分到一个单独的组件中。

8. 将局部和全局的 style混合在一起 {#将局部和全局的-style混合在一起 data-id="heading-8"}

这个涉及的内容还是很多的,后面会单独出一篇文章来讲

MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。

然而,我的朋友Austin想出了一个非常干净的方法,使用MutationObserver来做这件事。

不幸的是,Vue没有内置的方法让我们检测这一点。

有时我们需要知道插槽内的内容何时发生了变化。

7. 如何监听一个插槽的变化 {#如何监听一个插槽的变化 data-id="heading-7"}

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

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

例如,当我们在添加默认样式时,我们在插槽周围添加一个div:

  • 当使用封装的div来添加默认样式时

  • 插槽是空的

  • 如果我们将默认内容与嵌套槽相结合

使用条件插槽的主要原因有三个:

那么,为什么我们希望能够有条件地渲染插槽呢? {#那么为什么我们希望能够有条件地渲染插槽呢 data-id="heading-6"}

如果不使用v-if,那么如果没有插槽,就会得到一个空的不必要的div。根据div的样式,这可能会打乱我们的布局,让界面看起来很奇怪。

现在,应用样式的包装器div只有在我们用某些东西填充这个插槽时才会被渲染。

我们可以在我们的组件中使用这一点来检测哪些插槽已经被应用到组件中,例如,通过隐藏插槽的包装元素。

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

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

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

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

我们先来看如何做,然后在讨论为什么想隐藏插槽。

6. 有条件地渲染插槽 {#有条件地渲染插槽 data-id="heading-5"}

简单、直截了当,令人赞叹不已。

不使用 template:

普通写法:

幸运的是,有一个简写可以让我们摆脱它,但只有在我们使用单个作用域槽的情况下。

限定范围的插槽非常有趣,但为了使用它们,您还必须使用许多模板标记。

5. 单个作用域插槽的简写(不需要 template 标签) {#单个作用域插槽的简写不需要-template-标签 data-id="heading-4"}

反过来说,如果不需要立即执行昂贵的组件件,可以使用v-if,这样它就会跳过渲染它,而使页面的加载速度更快一些。

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

当v-if被打开或关闭时,它将创建并完全销毁该元素。相反,v-show将创建该元素并将其留在那里,通过设置其样式为display: none来隐藏它。

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

4. 知道何时使用v-if(以及何时避免使用) {#知道何时使用v-if以及何时避免使用 data-id="heading-3"}

你可能不知道这一点,我们可以通过使用引号轻松地直接监听嵌套值:

3. 使用引号来监听嵌套属性 {#使用引号来监听嵌套属性 data-id="heading-2"}

现在我们可以用许多不同的方式使用这个组件。简单的、默认的方式,或者自定义的方式。

我们可以取组件的任何部分,将其封装在一个插槽中,在外面我们可以用想要的任何内容覆盖组件的该部分。默认情况下,它仍然会按照原来的方式工作,但这样做会有了更多的选项

我最喜欢使用默认槽,就是用它们来创建扩展点。

Vue中的槽可以有默认的内容,这使我们可以制作出更容易使用的组件。

2. 默认内容和扩展点 {#默认内容和扩展点 data-id="heading-1"}

按钮类型或警告类型(信息、成功、危险、警告)是最常见的用法、、。颜色也是一个很好的用途。

当单单传入的 true 或 false 来控制某些条件不能满足需求时,我通常使用这个方法来做。

这个验证函数接受一个prop,如果prop有效或无效,则返回true或false。

转载来源:十分钟,让你学会Vue的这些巧妙冷技巧 - 掘金

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

推荐阅读更多精彩内容