🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[[官方文档1]](https://vuejs.org/v2/guide/components.html) [TOC] ## a tree of components almost any type of application interface can be abstracted into a tree of components ![a tree of components](images/components.png) In a large application, it is necessary to divide the whole app into components to make development manageable.here’s an (imaginary) example of what an app’s template might look like with components: ~~~ <div id="app"> <app-nav></app-nav> <app-view> <app-sidebar></app-sidebar> <app-content></app-content> </app-view> </div> ~~~ all Vue components are also Vue instances, and so accept the same options object (except for a few root-specific options). ## lifecycle hooks Each Vue instance goes through a series of initialization steps when it’s created - for example, it needs to - set up data observation, - compile the template, - mount the instance to the DOM, - and update the DOM when data changes. Along the way, it also runs functions called **lifecycle hooks**, giving users the opportunity to add their own code at specific stages. ![Lifecycle-Diagram](images/lifecycle.png) ## Component Registration ### Component Names The component’s name is the first argument of `Vue.component`. You have two options when defining component names,with `kebab-case` and with `PascalCase`. Because HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you’re using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents. * When defining a component with `kebab-case`, you must also use kebab-case when referencing its custom element, such as in `<my-component-name>`. * When defining a component with `PascalCase`, you can use either case when referencing its custom element. That means both `<my-component-name>`and `<MyComponentName>`are acceptable. * Only `kebab-case` names are valid directly in the DOM (i.e. non-string templates). ### Global Registration Globally registered components can be used in the template of any root Vue instance (`new Vue`) created afterwards – and even inside all subcomponents of that Vue instance’s component tree. ~~~ Vue.component('my-component-name', { // ... options ... }) ~~~ That means they can be used in the template of any root Vue instance (`new Vue`) created after registration. For example: ~~~javascript //js Vue.component('component-a', { /* ... */ }) Vue.component('component-b', { /* ... */ }) Vue.component('component-c', { /* ... */ }) new Vue({ el: '#app' }) ~~~ ~~~html <!-- html--> <div id="app"> <component-a></component-a> <component-b></component-b> <component-c></component-c> </div> ~~~ This even applies to all subcomponents, meaning all three of these components will also be available *inside each other*. Remember that **global registration must take place before the root Vue instance is created (with`new Vue`)** ### Local Registration Global registration often isn’t ideal. For example, if you’re using a build system like Webpack, globally registering all components means that even if you stop using a component, it could still be included in your final build. This unnecessarily increases the amount of JavaScript your users have to download. In these cases, you can define your components as plain JavaScript objects: ~~~javascript //js var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ } ~~~ Then define the components you’d like to use in a`components`option: ~~~javascript //js new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } }) ~~~ For each property in the`components`object, the key will be the name of the custom element, while the value will contain the options object for the component. >[warning] locally registered components are *not* also available in subcomponents. if you wanted `ComponentA `to be available in `ComponentB`, you’d have to use: * plaint js: ~~~javascript //js var ComponentA = { /\* ... \*/ } var ComponentB = { components: { 'component-a': ComponentA }, // ... } ~~~ * using ES2015 modules, such as through Babel and Webpack ~~~javascript //js import ComponentA from './ComponentA.vue' export default { components: { ComponentA }, // ... } ~~~ >[warning] in ES2015+, placing a variable name like`ComponentA`inside an object is shorthand for`ComponentA: ComponentA`, meaning the name of the variable is both: > * the custom element name to use in the template, and > * the name of the variable containing the component options ## Module Systems using a module system with`import`/`require`, such as with `Babel` and `Webpack`. ### Local Registration in a Module System In these cases, we recommend creating a `components` directory, with each component in its own file. Then you’ll need to import each component you’d like to use, before you locally register it. For example, in a hypothetical `ComponentB.js` or `ComponentB.vue` file: ~~~javascript //js import ComponentA from './ComponentA' import ComponentC from './ComponentC' export default { components: { ComponentA, ComponentC }, // ... } ~~~ Now both `ComponentA` and `ComponentC` can be used inside `ComponentB`‘s template. ### Automatic Global Registration of Base Components using Webpack (or [Vue CLI 3+](https://github.com/vuejs/vue-cli), which uses Webpack internally), you can use `require.context` to globally import base components in your app’s entry file (e.g.`src/main.js`): ~~~javascript //js import Vue from 'vue' import upperFirst from 'lodash/upperFirst' import camelCase from 'lodash/camelCase' const requireComponent = require.context( // The relative path of the components folder './components', // Whether or not to look in subfolders false, // The regular expression used to match base component filenames /Base[A-Z]\w+\.(vue|js)$/ ) requireComponent.keys().forEach(fileName => { // Get component config const componentConfig = requireComponent(fileName) // Get PascalCase name of component const componentName = upperFirst( camelCase( // Gets the file name regardless of folder depth fileName .split('/') .pop() .replace(/\.\w+$/, '') ) ) // Register component globally Vue.component( componentName, // Look for the component options on `.default`, which will // exist if the component was exported with `export default`, // otherwise fall back to module's root. componentConfig.default || componentConfig ) }) ~~~ ## Props ### **Array** Type listing props as an array of strings: ~~~javascript //js props: ['title', 'likes', 'isPublished', 'commentIds', 'author'] ~~~ ### **Object** Type listing props as an object, where the properties’ names and values contain the prop names and types, respectively: ~~~javascript //js props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // or any other constructor } ~~~ >[info] This not only documents your component, but will also warn users in the browser’s JavaScript console if they pass the wrong type. ### Prop Validation To specify prop validations, you can provide an object with validation requirements to the value of `props`, instead of an array of strings. And the `type` of `props` can be one of the following native constructors: * String * Number * Boolean * Array * Object * Date * Function * Symbol For example: ~~~javascript //js Vue.component('my-component', { props: { // Basic type check (`null` and `undefined` values will pass any type validation) propA: Number, // Multiple possible types propB: [String, Number], // Required string propC: { type: String, required: true }, // Number with a default value propD: { type: Number, default: 100 }, // Object with a default value propE: { type: Object, // Object or array defaults must be returned from // a factory function default: function () { return { message: 'hello' } } }, // Custom validator function propF: { validator: function (value) { // The value must match one of these strings return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } }) ~~~ When prop validation fails, Vue will produce a console warning (if using the development build). >[warning] `props` are validated **before** a component instance is created, so instance properties (e.g.`data`,`computed`, etc) will not be available inside `default` or `validator` functions. ### Non-Prop Attributes A `non-prop attribute` is an attribute that is passed to a component, but does not have a corresponding prop defined. * **replace**: For most attributes, the value provided to the component will replace the value set by the component. For example, passing`type="text"`will replace`type="date"`. * **merging**: the`class`and`style`attributes's values are merged * **Disabling Attribute Inheritance** If you do **not** want the root element of a component to inherit attributes, you can set`inheritAttrs: false`in the component’s options. For example: ~~~javascript //js Vue.component('my-component', { inheritAttrs: false, // ... }) ~~~ With `inheritAttrs: false`and `$attrs`, you can manually decide which element you want to forward attributes to, which is often desirable for `base components`. This pattern allows you to use base components more like raw HTML elements, without having to care about which element is actually at its root: ~~~html <!-- html--> <base-input v-model="username" required placeholder="Enter your username" ></base-input> ~~~ ~~~javascript //js Vue.component('base-input', { inheritAttrs: false, props: ['label', 'value'], template: ` <label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > </label> ` }) ~~~ >[warning] `inheritAttrs: false`option does **not** affect `style` and `class` bindings. ### Passing Static or Dynamic Props So far, you’ve seen props passed a static value, like in: ~~~html <!-- html--> <blog-post title="My journey with Vue"></blog-post> ~~~ You’ve also seen props assigned dynamically with`v-bind`, such as in: ~~~html <!-- html--> <!-- Dynamically assign the value of a variable --> <blog-post v-bind:title="post.title"></blog-post> <!-- Dynamically assign the value of a complex expression --> <blog-post v-bind:title="post.title + ' by ' + post.author.name" ></blog-post> ~~~ * **Number** ~~~html <!-- html--> <!-- Even though `42` is static, we need v-bind to tell Vue that --> <!-- this is a JavaScript expression rather than a string. --> <blog-post v-bind:likes="42"></blog-post> <!-- Dynamically assign to the value of a variable. --> <blog-post v-bind:likes="post.likes"></blog-post> ~~~ * **Bool** ~~~html <!-- html--> <!-- Including the prop with no value will imply `true`. --> <blog-post is-published></blog-post> <!-- Even though `false` is static, we need v-bind to tell Vue that --> <!-- this is a JavaScript expression rather than a string. --> <blog-post v-bind:is-published="false"></blog-post> <!-- Dynamically assign to the value of a variable. --> <blog-post v-bind:is-published="post.isPublished"></blog-post> ~~~ * **Array** ~~~html <!-- html--> <!-- Even though the array is static, we need v-bind to tell Vue that --> <!-- this is a JavaScript expression rather than a string. --> <blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post> <!-- Dynamically assign to the value of a variable. --> <blog-post v-bind:comment-ids="post.commentIds"></blog-post> ~~~ * **Object** ~~~html <!-- html--> <!-- Even though the object is static, we need v-bind to tell Vue that --> <!-- this is a JavaScript expression rather than a string. --> <blog-post v-bind:author="{ name: 'Veronica', company: 'Veridian Dynamics' }" ></blog-post> <!-- Dynamically assign to the value of a variable. --> <blog-post v-bind:author="post.author"></blog-post> ~~~ ~~~javascript //js post: { id: 1, title: 'My Journey with Vue' } ~~~ The following template: ~~~html <!-- html--> <blog-post v-bind="post"></blog-post> ~~~ Will be equivalent to: ~~~html <!-- html--> <blog-post v-bind:id="post.id" v-bind:title="post.title" ></blog-post> ~~~ ### One-Way Data Flow (Parent->Child) All props form a **one-way-down binding** between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around. This prevents child components from accidentally mutating the parent’s state. In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should **not** attempt to mutate a prop inside a child component. If you do, Vue will warn you in the console. Two cases where it’s tempting to mutate a prop: 1. **The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards.** In this case, it’s best to define a local data property that uses the prop as its initial value: ~~~javascript //js props: ['initialCounter'], data: function () { return { counter: this.initialCounter } } ~~~ 2. **The prop is passed in as a raw value that needs to be transformed.** In this case, it’s best to define a computed property using the prop’s value: ~~~javascript //js props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } } ~~~ >[warning] `objects` and `arrays` in JavaScript are passed by reference, so if the `prop` is an array or object, mutating the object or array itself inside the child component **will** affect parent state. ## Custom Events ## Slots Vue implements a content distribution API inspired by the [Web Components spec draft](https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md), using the`<slot>`element to serve as distribution outlets for content. >[info] In 2.6.0, we introduced a new unified syntax (the`v-slot`directive) for named and scoped slots. the`<slot>`element has a special attribute,`name`, which can be used to define additional slots: ~~~html <!-- BaseLayout.vue--> <template> <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> </template> ~~~ A`<slot>`outlet without`name`implicitly has the name “default”. When we use `BaseLayout.vue` in the parent `App.vue`, to provide content to named slots, we can use the`v-slot`directive on a`<template>`, providing the name of the slot as`v-slot`‘s argument: ~~~html <!-- App.vue--> <template> <base-layout> <template v-slot:header> <h1>Here might be a page title</h1> </template> <p>A paragraph for the main content.</p> <p>And another one.</p> <template v-slot:footer> <p>Here's some contact info</p> </template> </base-layout> </template> ~~~ Now everything inside the`<template>`elements will be passed to the corresponding slots. Any content not wrapped in a`<template>`using`v-slot`is assumed to be for the default slot, or wrap default slot content in a`<template>`if you wish to be explicit: ~~~html <!-- App.vue--> <template> <base-layout> <template v-slot:header> <h1>Here might be a page title</h1> </template> <template v-slot:default> <p>A paragraph for the main content.</p> <p>And another one.</p> </template> <template v-slot:footer> <p>Here's some contact info</p> </template> </base-layout> </template> ~~~ Either way, the rendered HTML will be: ~~~html <div class="container"> <header> <h1>Here might be a page title</h1> </header> <main> <p>A paragraph for the main content.</p> <p>And another one.</p> </main> <footer> <p>Here's some contact info</p> </footer> </div> ~~~ Note that **`v-slot`can only be added to a `<template>`** (with [one exception](https://vuejs.org/v2/guide/components-slots.html#Abbreviated-Syntax-for-Lone-Default-Slots)) when *only* the default slot is provided content, the component’s tags can be used as the slot’s template. This allows us to use`v-slot`directly on the component: ~~~html <current-user v-slot:default="slotProps"> {{ slotProps.user.firstName }} </current-user> ~~~ This can be shortened even further. Just as non-specified content is assumed to be for the default slot,`v-slot`without an argument is assumed to refer to the default slot: ~~~html <current-user v-slot="slotProps"> {{ slotProps.user.firstName }} </current-user> ~~~ >[warning] **Recommended**: Whenever there are multiple slots, use the full `<template>` based syntax for *all* slots: ~~~html <current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> <template v-slot:other="otherSlotProps"> ... </template> </current-user> ~~~ ### Named Slots Shorthand (`v-slot:`->`#`) `v-slot:header`can be rewritten as`#header`: ~~~html <base-layout> <template #header> <h1>Here might be a page title</h1> </template> <p>A paragraph for the main content.</p> <p>And another one.</p> <template #footer> <p>Here's some contact info</p> </template> </base-layout> ~~~ However, just as with other directives, the shorthand is only available when an argument is provided. That means the following syntax is invalid: ~~~html <!-- This will trigger a warning --> <current-user #="{ user }"> {{ user.firstName }} </current-user> ~~~ Instead, you must always specify the name of the slot if you wish to use the shorthand: ~~~html <current-user #default="{ user }"> {{ user.firstName }} </current-user> ~~~ ### Slot props Sometimes, it’s useful for slot content to have access to data only available in the child component. For example, imagine a `<current-user>` component with the following template: ~~~html <span> <slot>{{ user.lastName }}</slot> </span> ~~~ We might want to replace this fallback content to display the user’s first name, instead of last, like this: ~~~ <current-user> {{ user.firstName }} </current-user> ~~~ That won’t work, however, because only the`<current-user>`component has access to the`user`and the content we’re providing is rendered in the parent. To make`user`available to the slot content in the parent, we can bind`user`as an attribute to the`<slot>`element: ~~~html <span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span> ~~~ Attributes bound to a`<slot>`element are called **slot props**. Now, in the parent scope, we can use`v-slot`with a value to define a name for the slot props we’ve been provided: ~~~html <current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> </current-user> ~~~ In cases like above, when *only* the default slot is provided content, the component’s tags can be used as the slot’s template. This allows us to use`v-slot`directly on the component: ~~~ <current-user v-slot:default="slotProps"> {{ slotProps.user.firstName }} </current-user> ~~~ This can be shortened even further. ~~~html <current-user v-slot="slotProps"> {{ slotProps.user.firstName }} </current-user> ~~~ Because, just as non-specified content is assumed to be for the default slot, `v-slot`without an argument is assumed to refer to the default slot. Whenever there are multiple slots, use the full`<template>`based syntax for*all*slots: ~~~html <current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> <template v-slot:other="otherSlotProps"> ... </template> </current-user> ~~~ **Destructuring Slot Props** Internally, scoped slots work by wrapping your slot content in a function passed a single argument: ~~~javascript function (slotProps) { // ... slot content ... } ~~~ That means the value of`v-slot`can actually accept any valid JavaScript expression that can appear in the argument position of a function definition. So in supported environments ([single-file components](https://vuejs.org/v2/guide/single-file-components.html) or [modern browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Browser_compatibility)), you can also use [ES2015 destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring) to pull out specific slot props, like so: ~~~html <current-user v-slot="{ user }"> {{ user.firstName }} </current-user> ~~~ It also opens other possibilities, such as renaming props, e.g. `user` to `person`: ~~~html <current-user v-slot="{ user: person }"> {{ person.firstName }} </current-user> ~~~ You can even define fallbacks, to be used in case a slot prop is undefined: ~~~html <current-user v-slot="{ user = { firstName: 'Guest' } }"> {{ user.firstName }} </current-user> ~~~ **Slot props allow us to turn slots into reusable templates that can render different content based on input props.** This is most useful when you are designing a reusable component that encapsulates data logic while allowing the consuming parent component to customize part of its layout. For example, we are implementing a`<todo-list>`component that contains the layout and filtering logic for a list: ~~~html <ul> <li v-for="todo in filteredTodos" v-bind:key="todo.id" > {{ todo.text }} </li> </ul> ~~~ Instead of hard-coding the content for each todo, we can let the parent component take control by making every todo a slot, then binding `todo` as a slot prop: ~~~html <ul> <li v-for="todo in filteredTodos" v-bind:key="todo.id" > <!-- We have a slot for each todo, passing it the `todo` object as a slot prop. --> <slot name="todo" v-bind:todo="todo"> <!-- Fallback content --> {{ todo.text }} </slot> </li> </ul> ~~~ Now when we use the`<todo-list>`component, we can optionally define an alternative`<template>`for todo items, but with access to data from the child: ~~~ <todo-list v-bind:todos="todos"> <template v-slot:todo="{ todo }"> <span v-if="todo.isComplete">✓</span> {{ todo.text }} </template> </todo-list> ~~~ However, even this barely scratches the surface of what scoped slots are capable of. For real-life, powerful examples of scoped slot usage, we recommend browsing libraries such as [Vue Virtual Scroller](https://github.com/Akryum/vue-virtual-scroller), [Vue Promised](https://github.com/posva/vue-promised), and [Portal Vue](https://github.com/LinusBorg/portal-vue). ### Dynamic Slot Names > New in 2.6.0+ [Dynamic directive arguments](https://vuejs.org/v2/guide/syntax.html#Dynamic-Arguments) also work on `v-slot`, allowing the definition of dynamic slot names: ~~~html <base-layout> <template v-slot:[dynamicSlotName]> ... </template> </base-layout> ~~~