この記事では、Vue について知っておくべき 25 のことを要約して共有します。開発速度を向上させるためのヒントを活用してください。皆さんのお役に立てれば幸いです。
プロップ定義で validator
オプションを使用すると、プロップを制限できます。グループ固有の値:
export default { name: 'Image', props: { src: { type: String, }, style: { type: String, validator: s => ['square', 'rounded'].includes(s) } } };
このバリデーター関数は prop を受け取り、true または false を返します。ブール値で許可されるよりも多くのオプションが必要な場合にも使用できます。ボタン タイプまたはアラート タイプ (情報、成功、危険、警告) は、より一般的な用途の一部です。 [関連する推奨事項: vuejs ビデオ チュートリアル ]
Vue のスロットにはデフォルトのコンテンツを含めることができ、より簡単に操作できるコンポーネントを作成できます。 :
<button class="button" @click="$emit('click')"> <slot> <!-- 如果没有提供slot则使用 --> Click me </slot> </button>
基本的には、コンポーネントの任意の部分を取得してスロットにラップし、コンポーネントのその部分を任意のものでオーバーライドできます。デフォルトでは、通常どおり動作しますが、さらに多くのオプションがあります:
<template> <button class="button" @click="$emit('click')"> <!-- 一开始在 slot 标签中添加什么都不做 --> <!-- 我们可以通过向 slot 提供内容来覆盖它 --> <slot> <div class="formatting"> {{ text }} </div> </slot> </button> </template>
これで、このコンポーネントをさまざまな方法で使用できるようになります。単純なデフォルトの方法または独自のカスタム方法:
<!-- 使用组件的默认功能 --> <ButtonWithExtensionPoint text="Formatted text" /> <!-- 使用扩展点创建自定义行为 --> <ButtonWithExtensionPoint> <div class="different-formatting"> 在这里做一些不同的事情 </div> </ButtonWithExtensionPoint>
これはご存じないかもしれません: 直接簡単に表示するには引用符を使用するだけです。ネストされた値:
watch { '$route.query.id'() { // ... } }
これは、深くネストされたオブジェクトを操作する場合に便利です
場合によっては、v- を使用する代わりに、 if
の場合、v-show
を使用する方が効率的です。
<ComplicatedChart v-show="chartEnabled" />
v-if
をオンまたはオフにすると、完全に作成され、要素を破壊します。 v-show
違いは、要素を作成してそこに残し、スタイルを display: none
に設定して非表示にすることです。
切り替える必要があるコンポーネントのレンダリングにコストがかかる場合は、これがより効率的になります。一方、そのコンポーネントがすぐに必要ない場合は、v-if
を使用すると、コンポーネントのレンダリングがスキップされ、ページの読み込みが速くなります。
スコープ付きスロットの方が興味深いですが、それらを使用するには、大量の ## も使用する必要があります。 #template タグ。
<DataTable> <template #header="tableAttributes"> <TableHeader v-bind="tableAttributes" /> </template> </DataTable>
<DataTable #header="tableAttributes"> <TableHeader v-bind="tableAttributes" /> </DataTable>
const $slots = { default: <default slot>, icon: <icon slot>, button: <button slot>, };
$slots オブジェクトは
コンポーネントのスロットにのみ適用され、適用されません。 で定義されたすべての スロット。 例として、複数の名前付きスロットを含む複数のスロットを定義するこのコンポーネントを取り上げます。
<!-- Slots.vue --> <template> <div> <h2>这里是一些slots</h2> <slot /> <slot name="second" /> <slot name="third" /> </div> </template>
コンポーネントにスロットを 1 つだけ適用すると、そのスロットだけが
$ に表示されます。スロットオブジェクト内: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:html;toolbar:false;"><template>
<Slots>
<template #second>
这将应用于第二个slot
</template>
</Slots>
</template></pre><div class="contentsignin">ログイン後にコピー</div></div><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">$slots = { second: <vnode> }</pre><div class="contentsignin">ログイン後にコピー</div></div>
これをコンポーネントで使用すると、たとえば、スロットのラッピング要素を非表示にすることで、コンポーネントに適用されているスロットを検出できます:
<template> <div> <h2>一个包裹的slot</h2> <div v-if="$slots.default" class="styles"> <slot /> </div> </div> </template>
さて
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>这是一个非常棒的组件</h2> <div class="default-styling"> <!-- slot中没有内容,但仍会呈现此 div--> </div> <button @click="$emit('click')">Click me!</button> </div>
v-if これをラッパー div に追加すると、問題は解決します。コンテンツはスロットに適用されませんか?次のように:
<div> <h2>这是一个非常棒的组件</h2> <button @click="$emit('click')">Click me!</button> </div>
7. スロットの変更を観察する方法
スロットのコンテンツがいつ変更されたかを知る必要がある場合があります: <!-- 可惜这个活动不存在 --> <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>
<style> /*全局应用*/ .component p { margin-bottom: 16px; } </style> <style scoped> /*范围限定于此特定组件*/ .component { background: green; } </style>
Scoped CSS 比较容易保持整洁,并且不会意外地将样式渗入应用程序的其他部分。但有时你需要覆盖子组件的样式,并突破该范围。Vue 有一个deep
专门用于此的选择器:
<style scoped> /* 覆盖子组件的 CSS,同时保持样式范围*/ .my-component >>> .child-component { font-size: 24px; } </style>
注意:如果你使用的是 SCSS 之类的 CSS 预处理器,则可能需要改用/deep/
。
上下文感知组件是“神奇的”——它们可以自动适应周围发生的事情,处理边缘情况,状态共享等等。有 3 种主要类型的上下文感知组件,但是我觉得配置是其中最有趣的一种。
当你将一个大组件分解成多个小组件时,它们通常仍然需要共享状态。你可以“在幕后”实现这一点,而不是将这项工作推给使用组件的人。
可以将一个Dropdown
组件分解为Select
和Option
组件以提供更大的灵活性。但是为了更容易使用,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>
有时需要根据应用程序其余部分的情况更改组件的行为。这样做通常是为了自动处理边缘情况,否则会很麻烦。Popup
或者Tooltip
应该重新定位自己,这样它就不会溢出页面。但是,如果该组件位于 modal 内部,则它应该重新定位自身,以免溢出modal。如果Tooltip
知道它何时在模态内,这可以自动完成。
当你创建了上下文感知 CSS,根据父元素或兄弟元素中发生的情况应用不同的样式。
.statistic { color: black; font-size: 24px; font-weight: bold; } /* 在彼此相邻的统计数据之间进行一些分离*/ .statistic + .statistic { margin-left: 10px; }
CSS 中变量让我们更进一步允许我们在页面的不同部分设置不同的值。
如果你从 Vue 外部获得一个变量,那么能够使其具有响应性就很好。这样你就可以在计算道具、观察者和其他任何地方使用它,它就像 Vue 中的任何其他状态一样工作。
当你正在使用 options API,你只需将它放在data
组件的部分中:
const externalVariable = getValue(); export default { data() { return { reactiveVariable: externalVariable, }; } };
当你在 Vue 3 中使用组合 API,则可以使用ref
或reactive
这样:
import { ref } from 'vue'; // 可以完全在 Vue 组件之外完成 const externalVariable = getValue(); const reactiveVariable = ref(externalVariable); // 使用 .value 访问 console.log(reactiveVariable.value);
使用reactive
来代替:
import { reactive } from 'vue'; // 可以完全在 Vue 组件之外完成 const externalVariable = getValue(); // Reactive 仅适用于对象和数组 const anotherReactiveVariable = reactive(externalVariable); // 直接访问 console.log(anotherReactiveVariable);
如果你仍在使用 Vue 2(就像我们中的许多人一样),你可以使用observable
而不是reactive
获得完全相同的结果。
你知道你可以在 v-for
中解构吗?
<li v-for="{ name, id } in users" :key="id" > {{ name }} </li>
众所周知,你可以使用这样的元组从 v-for 中获取索引:
<li v-for="(value, key) in [ 'Hai Yong', 'Frozen', 'Web Beginner' ]"> {{ index + 1 }} - {{ value }} </li>
使用对象时,你还可以抓住key:
<li v-for="(value, key) in { name: 'Hai Yong', released: 2021, director: 'A blogger', }"> {{ key }}: {{ value }} </li>
也可以结合这两种方法,获取属性的键和索引:
<li v-for="(value, key, index) in { name: 'Hai Yong', released: 2021, director: 'A blogger', }"> #{{ index + 1 }}. {{ key }}: {{ value }} </li>
v-for
指令允许我们遍历一个数组,但它也让我们遍历一个范围:
<template> <ul> <li v-for="n in 5">项目#{{ n }}</li> </ul> </template>
显示效果:
当我们使用v-for
范围时,它将从 1 开始并以我们指定的数字结束。
你的组件中的任何响应都可以被观察到:
export default { computed: { someComputedProperty() { // 更新计算道具 }, }, watch: { someComputedProperty() { // 当计算的 prop 更新时做一些事情 } } };
你可以看:
如果你使用组合 API,只要它是一个ref
或reactive
对象就可以监视任何值,。
从子组件复制 prop 类型,只是为了在父组件中使用它们。但窃取这些道具类型比只是复制它们要好得多。
例如,我们Icon
在这个组件中使用了一个组件:
<template> <div> <h2>{{ heading }}</h2> <Icon :type="iconType" :size="iconSize" :colour="iconColour" /> </div> </template>
为了让它工作,我们需要添加正确的道具类型,从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 类型开始偏离组件中的 prop 类型,将引入错误Icon
。
所以这就是为什么我们会窃取它们:
import Icon from './Icon'; export default { components: { Icon }, props: { ...Icon.props, heading: { type: String, required: true, }, }, };
除了在我们的示例中,我们在每个道具名称的开头添加了“icon”。所以我们必须做一些额外的工作来实现这一点:
import Icon from './Icon'; const iconProps = {}; // Do some processing beforehand Object.entries(Icon.props).forEach((key, val) => { iconProps[`icon${key[0].toUpperCase()}${key.substring(1)}`] = val; }); export default { components: { Icon }, props: { ...iconProps, heading: { type: String, required: true, }, }, };
现在,如果Icon
组件中的 prop 类型被修改,我们的组件将保持最新。
但是如果在Icon
组件中添加或删除了一个 prop 类型呢?为了涵盖这些情况,我们可以使用v-bind
计算道具来保持动态。
有时我们需要检测点击是发生在特定元素el
的内部还是外部。这是我们通常使用的方法:
window.addEventListener('mousedown', e => { // 获取被点击的元素 const clickedEl = e.target; // `el` 是你正在检测外部点击的元素 if (el.contains(clickedEl)) { // 单击“el”内部 } else { // 在`el`之外点击 } });
我们是否可以v-for
只使用模板来制作一个组件?在此过程中,我发现了如何递归地使用slot。
这是组件的样子:
<!-- VFor.vue --> <template> <div> <!-- 渲染第一项 --> {{ list[0] }} <!-- 如果我们有更多的项目可以继续,但需要离开我们刚刚渲染的项目 --> <v-for v-if="list.length > 1" :list="list.slice(1)" /> </div> </template>
如果你想用作用域slot来做这件事——为什么不呢?!— 只需要进行一些调整:
<template> <div> <!-- 将项目传递到要渲染的slot中 --> <slot v-bind:item="list[0]"> <!-- Default --> {{ list[0] }} </slot> <v-for v-if="list.length > 1" :list="list.slice(1)" > <!-- 递归向下传递作用域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>
并不是你添加到组件的每一点信息都是状态。有时你需要添加一些元数据来为其他组件提供更多信息。
例如,如果你要为 Google Analytics 等分析仪表板构建一堆不同的小部件:
如果你希望布局知道每个小部件应占用多少列,你可以将其作为元数据直接添加到组件上:
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` 包含组件的所有元数据 console.log(`Using ${this.$options.metadata} columns`); }, };
请记住,此元数据对于组件的每个实例都是相同的,并且不是响应式的。
其他用途包括(但不限于):
这是 SFC 的一个鲜为人知的功能。你可以像使用常规 HTML 文件一样导入文件:
<!-- "single" 文件组件 --> <template src="./template.html"></template> <script src="./script.js"></script> <style scoped src="./styles.css"></style>
如果你需要共享样式、文档或其他任何内容这会很方便。也非常适合那些因滚动而磨损手指的超长组件文件
可重用组件不一定是大的或复杂的东西,我经常使小而短的组件可重复使用。因为我不会到处重写这段代码,更新它变得容易得多,而且我可以确保每个OverflowMenu
看起来和工作完全一样——因为它们是一样的!
<!-- OverflowMenu.vue --> <template> <Menu> <!-- 添加自定义按钮来触发我们的菜单 --> <template #button v-slot="bind"> <!-- 使用 bind 传递点击处理程序、a11y 属性等。 --> <Button v-bind="bind"> <!-- 使用我们自己的“...”图标,此按钮没有文字 --> <template #icon> <svg src="./ellipsis.svg" /> </template> </Button> </template> </Menu> </template>
这里我们使用了一个Menu
组件,但是在触发它打开的按钮上添加了一个“...”(省略号)图标。可能并不不值得用它来制作可重用的组件,因为它只有几行。每次我们想使用Menu
这样的时候,我们不能只添加图标吗?但这OverflowMenu
将使用数十次,现在如果我们想要更新图标或其行为,我们可以很容易地做到。而且使用起来也简单多了!
<template> <OverflowMenu :menu-items="items" @click="handleMenuClick" /> </template>
你可以通过给它一个从组件外部调用方法ref
:
<!-- Parent.vue --> <template> <ChildComponent ref="child" /> </template>
// Parent.vue 中的某个地方 this.$refs.child.method();
通常,我们使用道具和事件在组件之间进行通信。道具被发送到子组件,事件被发送回父组件。
<template> <ChildComponent :tell-me-what-to-do="someInstructions" @something-happened="hereIWillHelpYouWithThat" /> </template>
但有时你可能会遇到需要父组件触发子组件中的方法的情况。这是只有向下传递道具不起作用的地方。可以向下传递一个布尔值并让子组件监视它:
<!-- Parent.vue --> <template> <ChildComponent :trigger="shouldCallMethod" /> </template>
// Child.vue export default { props: ['trigger'], watch: { shouldCallMethod(newVal) { if (newVal) { // 当触发器设置为 `true` 时调用该方法 this.method(); } } } }
这工作正常,但仅限于第一次调用。如果你需要多次触发此操作,则必须清理并重置状态。然后逻辑看起来像这样:
Parent 组件传递true
给trigger
prop
Watch被触发,Child组件调用方法
Child 组件发出一个事件告诉 Parent 组件该方法已成功触发
Parent 组件重置trigger
回false
,因此我们可以再次执行此操作
啊。
相反,如果我们在子组件上设置ref
,我们可以直接调用该方法:
<!-- Parent.vue --> <template> <ChildComponent ref="child" /> </template>
// Parent.vue 中的某个地方 this.$refs.child.method();
我们打破了“props down, events up”规则,打破了封装,但它更清晰、更容易理解值得这样做!
有时,“最佳”解决方案最终会成为最差的解决方案。
使用观察者最棘手的部分是有时候它似乎不能正确触发。一般都是因为你试图查看一个数组或一个对象,但没有设置deep
为true
:
export default { name: 'ColourChange', props: { colours: { type: Array, required: true, }, }, watch: { // 使用对象语法而不仅仅是方法 colours: { // 这将让 Vue 知道查看数组内部 deep: true, // 我们必须将我们的方法移动到处理程序字段 handler() console.log('颜色列表已更改!'); } } } }
使用 Vue 3 的反应式 API 看起来像这样:
watch( colours, () => { console.log('颜色列表已更改!'); }, { deep: true, } );
如果你想了解更多信息,可以参阅Vue 3和Vue 2的文档。
你可以在 URL 中存储(一些)状态,允许你直接跳转到页面上的特定状态。
比如你可以加载一个已选择日期范围过滤器的页面:
someurl.com/edit?date-range=last-week
这对于用户可能共享大量链接的应用程序部分、服务器呈现的应用程序或在两个独立应用程序之间传递比常规链接通常提供的信息更多的信息非常有用。
你可以存储过滤器、搜索值、模式是打开还是关闭,或者我们滚动到的列表中的位置——非常适合无限分页。
使用vue-router
这样的方式获取查询(这也适用于 Nuxt 和 Vuepress 等大多数 Vue 框架):
const dateRange = this.$route.query.dateRange;
要更改它,我们使用RouterLink
组件并更新query
:
<RouterLink :to="{ query: { dateRange: newDateRange } }">
该template
标签可以在模板内的任何地方使用,以更好地组织代码。
我喜欢用它来简化v-if
逻辑,有时v-for
也是。
在这个例子中,我们有几个元素都使用相同的v-if
条件:\
<template> <div class="card"> <img src="imgPath" / alt="知っておくべき Vue の 25 のヒント" > <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" / alt="知っておくべき Vue の 25 のヒント" > <h3> {{ title }} </h3> <template v-if="expanded"> <h4> {{ subheading }} </h4> <div class="card-content"> <slot/> </div> <SocialShare/> </template> </div> </template>
你可以为 Vue 中的错误和警告提供自定义处理程序:
// Vue 3 const app = createApp(App); app.config.errorHandler = (err) => { alert(err); }; // Vue 2 Vue.config.errorHandler = (err) => { alert(err); };
Bugsnag 和 Rollbar 等错误跟踪服务挂接到这些处理程序中以记录错误,但你也可以使用它们来更优雅地处理错误以获得更好的用户体验。
例如,如果错误未得到处理,应用程序不仅会崩溃,还可以显示整页错误屏幕并让用户刷新或尝试其他操作。
在 Vue 3 中,错误处理程序仅适用于模板和观察程序错误,但 Vue 2 错误处理程序几乎可以捕获所有内容。两个版本中的警告处理程序仅适用于开发。
以上が知っておくべき Vue の 25 のヒントの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。