vue 组件模式 ¶
有用的Vue模式,技巧,提示和技巧以及有帮助的精选链接。
组件声明 ¶
单文件组件 (SFC) - 最常用 ¶
<template> <button class="btn-primary" @click.prevent="handleClick"> {{text}} </button> </template> <script> export default { data() { return { text: 'Click me', }; }, methods: { handleClick() { console.log('clicked'); }, }, } </script> <style scoped> .btn-primary { background-color: blue; } </style>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
字符串模板 (ES6模板字面值) ¶
Vue.component('my-btn', { template: ` <button class="btn-primary" @click.prevent="handleClick"> {{text}} </button> `, data() { return { text: 'Click me', }; }, methods: { handleClick() { console.log('clicked'); }, }, });
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
渲染函数 ¶
Vue.component('my-btn', { data() { return { text: 'Click me', }; }, methods: { handleClick() { console.log('clicked'); }, }, render(h) { return h('button', { attrs: { class: 'btn-primary' }, on: { click: this.handleClick, }, }, this.text); }, });
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
JSX ¶
Vue.component('my-btn', { data() { return { text: 'Click me', }; }, methods: { handleClick() { console.log('clicked'); }, }, render() { return ( <button class="btn-primary" onClick={this.handleClick}> {{this.text}} </button> ); }, });
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vue-class-component ¶
<template> <button class="btn-primary" @click.prevent="handleClick"> {{text}} </button> </template> <script> import Vue from 'vue'; import Component from 'vue-class-component'; @Component export default MyBtn extends Vue { text = 'Click me'; handleClick() { console.log('clicked'); } } </script> <style scoped> .btn-primary { background-color: blue; } </style>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
参考: ¶
- Official - Single File Component
- Official - Render Functions & JSX
- 7 Ways To Define A Component Template in VueJS
组件通信 ¶
Props和Events ¶
基本上,vue组件遵循单向数据流,即props向下(参见官方指南 和 event向上。
props是只读数据,因此无法从子组件更改props。
当props更改时,子组件将自动重新渲染(props是响应性数据源)。
子组件只能将event事件直接发送到父组件,因此父组件可以更改data
,映射到子组件的props
。
<template> <button @click="$emit('click')">{{text}}</button> </template> <script> export default { name: 'v-btn', props: { text: String, }, }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
<template> <v-btn :text="buttonText" @click="handleClick"></v-btn> </template> <script> export default { data() { return { clickCount: 0, buttonText: 'initial button text', }; }, methods: { handleClick() { this.buttonText = `Button clicked ${++this.clickCount}`; console.log('clicked', this.buttonText); } } }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
参考: ¶
- Official - Props
- Vue.js Component Communication Patterns
- Creating Custom Inputs With Vue.js
- Vue Sibling Component Communication
- Managing State in Vue.js
- Vue.js communication part 2: parent-child components
组件事件处理 ¶
参考: ¶
- Official - Custom Events
- Leveraging Vue events to reduce prop declarations
- Vue.js Component Hooks as Events
- Creating a Global Event Bus with Vue.js
- Vue.js Event Bus + Promises
组件条件渲染 ¶
指令 (v-if
/ v-else
/ v-else-if
/ v-show
) ¶
v-if
<h1 v-if="true">Render only if v-if condition is true</h1>
复制成功
v-if
and v-else
<h1 v-if="true">Render only if v-if condition is true</h1> <h1 v-else>Render only if v-if condition is false</h1>
复制成功
2
v-else-if
<div v-if="type === 'A'">Render only if `type` is equal to `A`</div> <div v-else-if="type === 'B'">Render only if `type` is equal to `B`</div> <div v-else-if="type === 'C'">Render only if `type` is equal to `C`</div> <div v-else>Render if `type` is not `A` or `B` or `C`</div>
复制成功
2
3
4
v-show
<h1 v-show="true">Always rendered, but it should be visible only if `v-show` conditions is true</h1>
复制成功
如果要有条件地渲染多个元素,
你可以在<template>
元素上使用指令(v-if
/v-else
/v-else-if
/v-show
)。
请注意,<template>
元素实际上并未渲染为DOM。 它是一个不可见的封装。
<template v-if="true"> <h1>All the elements</h1> <p>will be rendered into DOM</p> <p>except `template` element</p> </template>
复制成功
2
3
4
5
JSX ¶
如果在vue应用程序中使用JSX,则可以应用所有技术,例如if else
和switch case
语句以及ternary
和logical
运算符。
if else
声明
export default { data() { return { isTruthy: true, }; }, render(h) { if (this.isTruthy) { return <h1>Render value is true</h1>; } else { return <h1>Render value is false</h1>; } }, };
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
switch case
声明
import Info from './Info'; import Warning from './Warning'; import Error from './Error'; import Success from './Success'; export default { data() { return { type: 'error', }; }, render(h) { switch (this.type) { case 'info': return <Info text={text} />; case 'warning': return <Warning text={text} />; case 'error': return <Error text={text} />; default: return <Success text={text} />; } }, };
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
或者你可以使用 object
映射来简化 switch case
import Info from './Info'; import Warning from './Warning'; import Error from './Error'; import Success from './Success'; const COMPONENT_MAP = { info: Info, warning: Warning, error: Error, success: Success, }; export default { data() { return { type: 'error', }; }, render(h) { const Comp = COMPONENT_MAP[this.type || 'success']; return <Comp />; }, };
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
三元运算符
export default { data() { return { isTruthy: true, }; }, render(h) { return ( <div> {this.isTruthy ? ( <h1>Render value is true</h1> ) : ( <h1>Render value is false</h1> )} </div> ); }, };
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
逻辑运算符
export default { data() { return { isLoading: true, }; }, render(h) { return <div>{this.isLoading && <h1>Loading ...</h1>}</div>; }, };
复制成功
2
3
4
5
6
7
8
9
10
参考 ¶
动态组件 ¶
带is属性的组件 ¶
<component :is="currentTabComponent"></component>
复制成功
在上面的代码示例中,如果在<component>
中呈现不同的组件,则将销毁渲染的组件。 如果你想让组件保持它们的实例而不在<component>
标签中被销毁,你可以将<component>
标签包装在<keep-alive>
标签中,如下所示:
<keep-alive> <component :is="currentTabComponent"></component> </keep-alive>
复制成功
2
3
参考 ¶
- Official - Dynamic Components
- Official - Dynamic & Async Components
- Dynamic Component Templates with Vue.js
构建 ¶
库 ¶
基础构建 ¶
<template> <div class="component-b"> <component-a></component-a> </div> </template> <script> import ComponentA from './ComponentA'; export default { components: { ComponentA, }, }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
References ¶
继承 ¶
当你想要继承单个vue组件时
<template> <button class="button-primary" @click.prevent="handleClick"> {{buttonText}} </button> </template> <script> import BaseButton from './BaseButton'; export default { extends: BaseButton, props: ['buttonText'], }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
参考: ¶
混入 ¶
// closableMixin.js export default { props: { isOpen: { default: true } }, data: function() { return { shown: this.isOpen } }, methods: { hide: function() { this.shown = false; }, show: function() { this.shown = true; }, toggle: function() { this.shown = !this.shown; } } }
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template> <div v-if="shown" class="alert alert-success" :class="'alert-' + type" role="alert"> {{text}} <i class="pull-right glyphicon glyphicon-remove" @click="hide"></i> </div> </template> <script> import closableMixin from './mixins/closableMixin'; export default { mixins: [closableMixin], props: ['text'] }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
参考: ¶
插槽 (默认) ¶
<template> <button class="btn btn-primary"> <slot></slot> </button> </template> <script> export default { name: 'VBtn', }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
<template> <v-btn> <span class="fa fa-user"></span> Login </v-btn> </template> <script> import VBtn from './VBtn'; export default { components: { VBtn, } }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
参考: ¶
- Official - Slot Content
- Understanding Component Slots with Vue.js
- Composing Custom Elements With Slots And Named Slots
- Writing Abstract Components with Vue.js
具名插槽 ¶
BaseLayout.vue
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
复制成功
2
3
4
5
6
7
8
9
10
11
App.vue
<base-layout> <template 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 slot="footer"> <p>Here's some contact info</p> </template> </base-layout>
复制成功
2
3
4
5
6
7
8
9
10
11
12
参考 ¶
作用域插槽 ¶
<template> <ul> <li v-for="todo in todos" v-bind:key="todo.id" > <!-- We have a slot for each todo, passing it the --> <!-- `todo` object as a slot prop. --> <slot v-bind:todo="todo"> {{ todo.text }} </slot> </li> </ul> </template> <script> export default { name: 'TodoList', props: { todos: { type: Array, default: () => ([]), } }, }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <todo-list v-bind:todos="todos"> <template slot-scope="{ todo }"> <span v-if="todo.isComplete">✓</span> {{ todo.text }} </template> </todo-list> </template> <script> import TodoList from './TodoList'; export default { components: { TodoList, }, data() { return { todos: [ { todo: 'todo 1', isComplete: true }, { todo: 'todo 2', isComplete: false }, { todo: 'todo 3', isComplete: false }, { todo: 'todo 4', isComplete: true }, ]; }; }, }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
参考: ¶
- Official - Scoped Slots
- Getting Your Head Around Vue.js Scoped Slots
- Understanding scoped slots in Vue.js
- Scoped Component Slots in Vue.js
- The Trick to Understanding Scoped Slots in Vue.js
- The Power of Scoped Slots in Vue
- Building a list keyboard control component with Vue.js and scoped slots
渲染 Props ¶
在大多数情况下,您可以使用 scoped
插槽而不是渲染 props
。 但是,在某些情况下它可能有用。
单文件组件(SFC
)中
<template> <div id="app"> <Mouse :render="__render"/> </div> </template> <script> import Mouse from "./Mouse.js"; export default { name: "app", components: { Mouse }, methods: { __render({ x, y }) { return ( <h1> The mouse position is ({x}, {y}) </h1> ); } } }; </script> <style> * { margin: 0; height: 100%; width: 100%; } </style>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
JSX
中
const Mouse = { name: "Mouse", props: { render: { type: Function, required: true } }, data() { return { x: 0, y: 0 }; }, methods: { handleMouseMove(event) { this.x = event.clientX; this.y = event.clientY; } }, render(h) { return ( <div style={{ height: "100%" }} onMousemove={this.handleMouseMove}> {this.$props.render(this)} </div> ); } }; export default Mouse;
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
参考: ¶
传递 Props ¶
有时,你可能希望将 props
和 listeners
传递给子组件,而无需声明所有子组件的 props
。
您可以在子组件中绑定 $attrs
和 $listeners
,并将 inheritAttrs
设置为 false
(否则div和子组件都将接收属性)
子组件中:
<template> <div> <h1>{{title}}</h1> <child-component v-bind="$attrs" v-on="$listeners"></child-component> </div> </template> <script> export default { name: 'PassingPropsSample', inheritAttrs: false, props: { title: { type: String, default: 'Hello, Vue!' } } }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在父组件中,你可以这样做:
<template> <passing-props-sample title="Hello, Passing Props" childPropA="This props will properly mapped to <child-component />" @click="handleChildComponentClick" > </passing-props-sample> </template> <script> import PassingPropsSample from './PassingPropsSample'; export default { components: { PassingPropsSample }, methods: { handleChildComponentClick() { console.log('child component clicked'); } } }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
参考: ¶
高阶组件 (HOC) ¶
参考: ¶
- Higher Order Components in Vue.js
- Do we need Higher Order Components in Vue.js?
- Higher-Order Components in Vue.js
依赖注入 ¶
Vue支持依赖/注入机制,无论组件层次结构有多深,只要它们位于同一父链中,就可以将object
提供给它的所有后代。 请注意,provide
和inject
绑定不是响应式的,除非你传递一个观察对象。
<parent-component> <child-component> <grand-child-component></grand-child-component> </child-component> </parent-component>
复制成功
2
3
4
5
在上面的示例组件层次结构中,为了从parent-component
派生数据,您应该将数据(对象)作为props
传递给child-component
和grand-child-component
。 但是,如果parent-component``提供
数据(对象),grand-child-component
只能从parent-component
定义inject
提供的对象。
参考: ¶
Provide / Inject ¶
// ParentComponent.vue export default { provide: { theme: { primaryColor: 'blue', }, }, };
复制成功
2
3
4
5
6
7
8
9
// GrandChildComponent.vue <template> <button :style="{ backgroundColor: primary && theme.primaryColor }"> <slot></slot> </button> </template> <script> export default { inject: ['theme'], props: { primary: { type: Boolean, default: true, }, }, }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Provide / @Inject Decorator ¶
// ParentComponent.vue import { Component, Vue, Provide } from 'vue-property-decorator'; @Component export class ParentComponent extends Vue { @Provide theme = { primaryColor: 'blue', }; }
复制成功
2
3
4
5
6
7
8
9
10
11
// GrandChildComponent.vue <template> <button :style="{ backgroundColor: primary && theme.primaryColor }"> <slot></slot> </button> </template> <script> import { Component, Vue, Inject, Prop } from 'vue-property-decorator'; export class GrandChildComponent extends Vue { @Inject() theme; @Prop({ default: true }) primary: boolean; }; </script>
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
错误处理 ¶
错误捕获钩子 ¶
export default { name: 'ErrorBoundary', data() { return { error: false, errorMessage: '', }; }, errorCaptured (err, vm, info) { this.error = true; this.errorMessage = `${err.stack}\n\nfound in ${info} of component`; return false; }, render (h) { if (this.error) { return h('pre', { style: { color: 'red' }}, this.errorMessage); } return this.$slots.default[0] } };
复制成功
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<error-boundary> <another-component/> </error-boundary>
复制成功
2
3
Examples ¶
参考 ¶
高效提示 ¶
watch on create
// don't created() { this.fetchUserList(); }, watch: { searchText: 'fetchUserList', }
复制成功
2
3
4
5
6
7
// do watch: { searchText: { handler: 'fetchUserList', immediate: true, } }
复制成功
2
3
4
5
6
7