目录
组件声明
单文件组件 (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>
字符串模板 (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');
},
},
});
渲染函数
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);
},
});
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>
);
},
});
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>
参考:
- 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>
<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>
参考:
- 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>
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>
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>
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>;
}
},
};
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} />;
}
},
};
或者你可以使用 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 />;
},
};
三元运算符
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>
);
},
};
逻辑运算符
export default {
data() {
return {
isLoading: true,
};
},
render(h) {
return <div>{this.isLoading && <h1>Loading ...</h1>}</div>;
},
};
参考
动态组件
带is属性的组件
<component :is="currentTabComponent"></component>
在上面的代码示例中,如果在<component>
中呈现不同的组件,则将销毁渲染的组件。 如果你想让组件保持它们的实例而不在<component>
标签中被销毁,你可以将<component>
标签包装在<keep-alive>
标签中,如下所示:
<keep-alive>
<component :is="currentTabComponent"></component>
</keep-alive>
参考
- 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>
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>
参考:
混入
// 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;
}
}
}
<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>
参考:
插槽 (默认)
<template>
<button class="btn btn-primary">
<slot></slot>
</button>
</template>
<script>
export default {
name: 'VBtn',
};
</script>
<template>
<v-btn>
<span class="fa fa-user"></span>
Login
</v-btn>
</template>
<script>
import VBtn from './VBtn';
export default {
components: {
VBtn,
}
};
</script>
参考:
- 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>
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>
参考
作用域插槽
<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>
<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>
参考:
- 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>
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;
参考:
传递 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>
在父组件中,你可以这样做:
<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>
参考:
高阶组件 (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>
在上面的示例组件层次结构中,为了从parent-component
派生数据,您应该将数据(对象)作为props
传递给child-component
和grand-child-component
。 但是,如果parent-component``提供
数据(对象),grand-child-component
只能从parent-component
定义inject
提供的对象。
参考:
- Official API
- Official Guide
- Component Communication
- Dependency Injection in Vue.js App with TypeScript
Provide / Inject
// ParentComponent.vue
export default {
provide: {
theme: {
primaryColor: 'blue',
},
},
};
// 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>
@Provide / @Inject Decorator
// ParentComponent.vue
import { Component, Vue, Provide } from 'vue-property-decorator';
@Component
export class ParentComponent extends Vue {
@Provide
theme = {
primaryColor: 'blue',
};
}
// 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>
错误处理
错误捕获钩子
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]
}
};
<error-boundary>
<another-component/>
</error-boundary>
Examples
参考
高效提示
watch on create
// don't
created() {
this.fetchUserList();
},
watch: {
searchText: 'fetchUserList',
}
// do
watch: {
searchText: {
handler: 'fetchUserList',
immediate: true,
}
}