Vue: Components Basics
2022-03-22
0. Intro
Components 让我们可以把 UI 分解成 independent and resuable pieces,然后针对每个部分去考虑它的功能和实现。
通常来说,app 都是用 树结构来组织的。
这和我们写 HTML element 的方式很相似,但是 Vue 有它自己的 component model,让我们可以 encapsulate custom content and logic in each component.
Vue 也能和 native Web Components 一起工作,详情可见Vue and Web Components | Vue.js (vuejs.org)
1. 定义 Component
With Build Step - SFC syntax, vue file
当我们用 build step 的时候,我们经常把每个 Vue component 放在单独的 .vue
文件中,这也被成为 SFC - Single-File Component
。
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
<script>
export default {
data() {
return {
count: 0,
};
},
};
</script>
Without Build Step - js file with inline string
Template 是通过 inline JS string 来写的,vue 会做 compilation。
我们也可以用 ID selector 来指向具体的 element,一般都是 native <template>
elements,Vue 会用它的 content 作为 template source。
The template is inlined as a JavaScript string here, which Vue will compile on the fly. You can also use an ID selector pointing to an element (usually native
<template>
elements) - Vue will use its content as the template source.
下面的例子定义了一个 component,然后 export it as the default export of a .js
file,但你也可以用 named exports 来 export multiple components from the same file.
export default {
data() {
return {
count: 0,
};
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`,
};
2. Using a Component
Local Registration
- 我们需要在 parent component 里面 import child component,然后再使用它。
- 在 components option 里面 register,注册之后这个 component 就可以用它 register 的 key 作为 tag 来使用了。
<ButtonCounter>
Note: 使用次数不限,每个 component 是 isolated instance。
<script>
import ButtonCounter from "./ButtonCounter.vue";
export default {
components: {
ButtonCounter,
},
};
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
<ButtonCounter />
</template>
Global Registration
Local Registration vs Global Registration
Component Registration | Vue.js (vuejs.org)
3. Naming Convention
- SFC:PascalCase - case-sensitive, use
/>
to close a tag - 假如你的 template 直接在 DOM 里面写了,就用 kebab-case,并且 explicit closing the tag
kebab-case Example
<!-- if this template is written in the DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
4. Passing Props
Props 是你可以在 component 上 register 的自定义 attribute,当一个 value 传入 prop attribute 后,它就变成了一个 component instance property,value 可以在 template 和 this
context 中 access。
Props 数量不限,默认为任何 value 都可以传入。
我们要先用 props option 来 declare it in the list of props this component accepts,然后才能用。
In-depth reading: Props | Vue.js (vuejs.org)
Example 1
<!-- BlogPost.vue -->
<script>
export default {
props: ["title"],
};
</script>
<template>
<h4>{{ title }}</h4>
</template>
<!-- BlogPage.vue -->
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />
Example 2
大部分时候我们都会从通过 parent component 的 data option 来传入数据,然后通过 v-for
来 render。
<BlogPost v-for="post in posts" :key="post.id" :title="post.title" />
export default {
// ...
data() {
return {
posts: [
{ id: 1, title: "My journey with Vue" },
{ id: 2, title: "Blogging with Vue" },
{ id: 3, title: "Why Vue is so fun" },
],
};
},
};
Dynamic property - v-bind
假如我们要传的值是动态的,就要用 v-bind
了。
5. Listening to Events
假如 child 要传信息给 parent,我们需要:
- 在 parent data option 里面加一个 property;
- 在 parent component 里面 listen child event;
- 在 child component 里面 emit event by calling the built-in
$emit
method,传入 event name;- 我们也可以用 emits option 来 declare emitted events
- 我们还可以加一些 validation
- 这样 parent 就可以接收到 event 然后 update 这个 property value 了;
In-depth reading: Component Events | Vue.js (vuejs.org)
Example
Parent
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
@enlarge-text="postFontSize += 0.1"
/>
</div>
data() {
return {
posts: [
/* ... */
],
postFontSize: 1
}
}
Child
<!-- BlogPost.vue, omitting <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
Parent - use emits option
<!-- BlogPost.vue -->
<script>
export default {
props: ["title"],
emits: ["enlarge-text"],
};
</script>
6. Content Distribution with Slots
如果我们想要给 component 传入 content,要怎么做呢?记得在 child 里面加上 slot tag!
In-depth reading: Slots | Vue.js (vuejs.org)
Example
<AlertBox> Something bad happened. </AlertBox>
Use <slot>
<template>
<div class="alert-box">
<strong>Error!</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
Without <Slot>
7. Dynamic Components
这是用来 dynamically switch between components 的,比如切换 tabs。
Use :is
我们传入 :is
的 value 可以是:
- the name string of a registered component
- the actual imported component object
我们也可以用 is
attribute 来创建 regular HTML element。
When switching between multiple components with
<component :is="...">
, a component will be unmounted when it is switched away from. We can force the inactive components to stay “alive” with the built-in<KeepAlive>
component .
Example
<!-- Component changes when currentTab changes -->
<component :is="currentTab"></component>
8. DOM Template Parsing Caveats(警告)
如果你直接在 DOM 里面写 Vue template 的话,可能会由于 browser 的原生 HTML parsing behavior 出现一些问题。
It should be noted that the limitations discussed only apply if you are writing your templates directly in the DOM. They do NOT apply if you are using string templates from the following sources:
- Single-File Components
- Inlined template strings (e.g.
template: '...'
) <script type="text/x-template">
Components Basics | Vue.js (vuejs.org)
这部分我没有详细看,因为我肯定是用 SFC。