最大化组件灵活性

Vue.js 组件将 props 和 slot 作为输入;它们的输出渲染为 HTML 并发出事件。

为了最大限度地提高组件的灵活性,利用插槽和 props 总是有意义的。

明智地利用 props 和默认值意味着组件可以被重用和扩展。例如,我们可以将其设置为默认属性,而不是在组件中硬编码一个值。在这种情况下,日期默认为当前日期,new Date()。然后我们使用计算属性提取纪元:

<template>
    <div>Date as epoch: {{ epoch }}</div>
</template>

<script>
export default {
    props: {
        date: {
            type: Date,
            default() {
                return new Date()
            }
        }
    },
    computed: {
        epoch() {
            return Number(this.date)
        }
    }
}
</script>

注册并使用时,渲染如下:

Date as epoch: 1574289255348

插槽可以被认为是组件将渲染委托给其使用者的一种方式。将模板的某些部分委托给父组件有助于提高可重用性。

用于最大化可重用性的插槽的一个具体示例是 无渲染组件模式(renderless component)。例如,在纪元显示示例中,我们可以利用作用域插槽并从组件中删除任何渲染逻辑:

<template>
    <div>
        <slot :epoch="epoch" />
    </div>
</template>

在父组件中,可以使用作用域槽来定义渲染:

<template>
    <div>
        <Epoch>
            <template v-slot:default="{ epoch }">
                Epoch as rendered with parent template {{ epoch }}
            </template>
        </Epoch>
    </div>
</template>

这意味着组件的委托被委托给父级并显示以下内容:

Epoch as rendered with parent template 1574289270190

下一组实践通过使 API 可预测来最大化组件的重用。在许多方面,转发属性(forwarding attributes)、利用合并的样式(style)和类(class)属性以及实现 v-model 接口是使 Vue.js 自定义组件的行为更像 HTML 元素的另一种方法。

转发属性可能很有趣。例如,CustomInput 组件(在 CustomInput.vue 文件中)可能需要传递 type 属性以及 required 属性:

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

CustomInput 组件可用于渲染任何类型的组件(src/App.vue):

<template>
    <div id="app">
        <fieldset>
        <label for="textinput">
            Text Input
        </label>
        <CustomInput
            type="text"
            name="textinput"
            id="textinput"
        />
        </fieldset>
        <fieldset>
        <label for="dateinput">
            Date Input
        </label>
        <CustomInput
            type="date"
            name="dateinput"
            id="dateinput"
        />
        </fieldset>
    </div>
</template>

<script>
import CustomInput from './components/CustomInput.vue'

export default {
    components: {
        CustomInput
    }
}
</script>

这会正确渲染文本和日期输入:

image 2023 10 13 20 37 48 207
Figure 1. Figure 5.9: CustomInput with text and date as types

Vue.js 围绕 class/内联 style 做了很多繁重的工作,因为它将组件上定义的样式和类对象与该组件中根元素的样式和类对象合并。 根据文档,“类和样式属性更智能一些,因此两个值都会合并”(Vue.js 组件属性指南: https://vuejs.org/v2/guide/components-props.html#Replacing-Merging-with-Existing-Attributes )。

转发属性

$attrs 是 Vue.js 中的一个特殊属性,用于在父组件向子组件传递未经处理的父组件属性。它的作用是将父组件中传递给子组件但子组件没有明确声明的属性传递给子组件,以便子组件可以访问这些属性。

具体来说,$attrs 的作用包括:

  1. 传递未声明的属性:当父组件向子组件传递属性时,如果这些属性在子组件中没有通过 props 声明,那么这些属性会被自动传递到子组件的 $attrs 对象中。这使得子组件可以访问这些属性,而不必显式声明它们。

  2. 继承父组件的属性$attrs 允许子组件继承父组件的属性,包括静态和动态属性。子组件可以在其模板中直接使用 $attrs 对象来访问这些属性的值。

  3. 动态传递属性$attrs 可以用于动态地传递属性给子组件,这在需要根据条件传递不同属性的情况下非常有用。

下面是一个示例,说明如何使用 $attrs

父组件:

<template>
  <child-component message="Hello" color="blue" />
</template>

子组件:

<template>
  <div>
    <p>{{ $attrs.message }}</p>
    <p :style="{ color: $attrs.color }">{{ $attrs.color }}</p>
  </div>
</template>

在这个示例中,messagecolor 在子组件中没有通过 props 声明,但它们会自动传递到子组件的 $attrs 对象中,使子组件能够访问它们的值。

$attrs 在某些情况下非常有用,特别是在需要将多个未声明属性传递给子组件或根据父组件属性的动态性来传递属性时。

这将显示 "Hello" 和 "blue"。

$attrs 是在需要在子组件中访问父组件传递的属性,但又不想显式声明这些属性时非常有用的。

在 Vue.js 中,输入元素和组件倾向于通过 v-model 进行控制,v-model 是一种双向响应式 Vue.js 绑定。v-model 是使用 v-bind:value 和 v-on:input 提供值并使其与子组件或元素的输出保持同步的简写。

受控名称与不受控相反。在不受控的情况下,传递的值仅用作起始值;当输入完成捕获时(例如,键入完成),输入(input)事件才会发出。

如果一个组件实现了 v-model 形状,它就可以直接替代表单元素。

例如,实现 v-model 接口的 TextInput 可与 input 和 textarea 互换使用:

<template>
    <div>
        <textarea
            v-if="type === 'long'"
            :value="value"
            @input="$emit('input', $event.target.value)"
        ></textarea>
        <input
            v-else
            :value="value"
            @input="$emit('input', $event.target.value)"
            type="text"
        />
    </div>
</template>

<script>
export default {
    props: ['value', 'type']
}
</script>

然后可以在 src/App.vue 中使用它,如下所示:

<template>
    <div id="app">
        <label>Short Text: {{ shortText }}</label>
        <TextInput v-model="shortText" type="short" />
        <label>Long Text: {{ longText }}</label>
        <TextInput v-model="longText" type="long" />
    </div>
</template>

<script>
import TextInput from './components/TextInput.vue'

export default {
    components: {
        TextInput
    },
    data() {
        return {
            shortText: '',
            longText: ''
        }
    }
}
</script>

应用程序渲染如下:

image 2023 10 13 20 54 09 282
Figure 2. Figure 5.10: Custom component implementing v-model

至此,我们研究了如何利用 props 和 slot、继承属性以及实现众所周知的 Vue.js 接口来帮助最大限度地提高组件灵活性。

下一节将致力于通过学习如何在没有 .vue 文件的情况下使用 Vue.js 组件来加深我们对 Vue.js 组件的理解。