简介
指令带有前缀 v-
,以表示它们是 Vue 提供的特殊特性。
v-bind
, v-if
, v-for
, v-on
绑定数据
1
2
3
4
5
| <div id="app">
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
|
1
2
3
4
5
6
| var app2 = new Vue({
el: '#app',
data: {
message: '页面加载于 ' + new Date().toLocaleString()
}
})
|
表单输入绑定
你可以用 v-model
指令在表单 <input>
及 <textarea>
元素上创建双向数据绑定。
默认的 prop
为 value
, event
为 input
。
1
2
| <input v-model="message" placeholder="edit me">
<p>Message is: </p>
|
src/core/vdom/create-component.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| function transformModel (options, data: any) {
const prop = (options.model && options.model.prop) || 'value'
const event = (options.model && options.model.event) || 'input'
;(data.attrs || (data.attrs = {}))[prop] = data.model.value
const on = data.on || (data.on = {})
const existing = on[event]
const callback = data.model.callback
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1
: existing !== callback
) {
on[event] = [callback].concat(existing)
}
} else {
on[event] = callback
}
}
|
参数
一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind
指令可以用于响应式地更新 HTML 特性:
1
| <a v-bind:href="url">...</a>
|
在这里 href 是参数,告知 v-bind
指令将该元素的 href
特性与表达式 url
的值绑定。
另一个例子是 v-on
指令,它用于监听 DOM 事件:
1
| <a v-on:click="doSomething">...</a>
|
在这里参数是监听的事件名。我们也会更详细地讨论事件处理。
缩写
Vue.js 为 v-bind
和 v-on
这两个最常用的指令,提供了特定简写:
v-bind
1
2
3
4
5
| <!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
|
v-on
1
2
3
4
5
| <!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
|
单文件组件
文件扩展名为 .vue
的 single-file components(单文件组件)
1
2
3
4
5
6
7
8
9
10
| <template>
<!-- ...模板 -->
</template>
<script>
// ...
</script>
<style>
/* ... */
</style>
|
自定义组件
Props
Type Checks
In addition, type
can also be a custom constructor function and the assertion will be made with an instanceof
check. For example, given the following constructor function exists:
1
2
3
4
| function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
|
You could use:
1
2
3
4
5
| Vue.component('blog-post', {
props: {
author: Person
}
})
|
to validate that the value of the author prop
was created with new Person
.
Non-Prop Attributes
Reactivity in Depth
Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive.
1
2
3
4
5
6
7
8
9
| var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive
|
When you pass a plain JavaScript object to a Vue instance as its data option, Vue will walk through all of its properties and convert them to getter/setters
using Object.defineProperty
.
Every component instance has a corresponding watcher
instance, which records any properties “touched” during the component’s render as dependencies. Later on when a dependency’s setter is triggered, it notifies the watcher, which in turn causes the component to re-render.
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the Vue.set(object, propertyName, value)
method:
1
| Vue.set(vm.someObject, 'b', 2)
|
You can also use the vm.$set
instance method, which is an alias to the global Vue.set
:
1
| this.$set(this.someObject, 'b', 2)
|
For Arrays
Vue cannot detect the following changes to an array:
- When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue
1
2
| Vue.set(vm.items, indexOfItem, newValue)
Vue.delete( target, propertyName/index )
|
Vue performs DOM updates asynchronously.
you can use Vue.nextTick(callback)
immediately after the data is changed.
Event Handling
Event Modifiers
1
2
3
4
5
| <!-- the click event's propagation will be stopped -->
<a v-on:click.stop="doThis"></a>
<!-- the submit event will no longer reload the page -->
<form v-on:submit.prevent="onSubmit"></form>
|
Transitions
1
2
3
| <transition name="fade">
<p v-if="show">hello</p>
</transition>
|
1
2
3
4
5
6
| .fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
|
Transition Classes
v-enter
v-enter-active
v-enter-to
v-leave
v-leave-active
v-leave-to
插件
通过全局方法 Vue.use()
使用插件。它需要在你调用 new Vue()
启动应用之前完成:
1
2
3
4
5
6
| // 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
new Vue({
// ...组件选项
})
|
Directive
1
2
3
4
5
| bind()
inserted()
update()
componentUpdated()
unbind()
|
Params:
1
| (el, binding, vnode, oldVnode)
|
VNode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void; // 当前 VNode 对应的真实dom节点
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// ......
}
|
1
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
32
| declare interface VNodeData {
key?: string | number;
slot?: string;
ref?: string;
is?: string;
pre?: boolean;
tag?: string;
staticClass?: string;
class?: any;
staticStyle?: { [key: string]: any };
style?: string | Array<Object> | Object;
normalizedStyle?: Object;
props?: { [key: string]: any };
attrs?: { [key: string]: string };
domProps?: { [key: string]: any };
hook?: { [key: string]: Function };
on?: ?{ [key: string]: Function | Array<Function> };
nativeOn?: { [key: string]: Function | Array<Function> };
transition?: Object;
show?: boolean; // marker for v-show
inlineTemplate?: {
render: Function;
staticRenderFns: Array<Function>;
};
directives?: Array<VNodeDirective>;
keepAlive?: boolean;
scopedSlots?: { [key: string]: Function };
model?: {
value: any;
callback: Function;
};
};
|
Vue
全局 API
Vue.extend
选项 / DOM
el
template
render
Vue
选项中的 render
函数若存在,则 Vue
构造函数不会使用 template
选项或 el
选项编译渲染。
实例方法
vm.$mount
1
2
3
4
5
6
7
| var MyComponent = Vue.extend({
template: '<div>Hello!</div>'
})
// 在文档之外渲染并且随后挂载
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)
|
生命周期(Lifecycle)
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- activated
- deactivated
- beforeUnmount
- unmounted
- errorCaptured
- renderTracked
- renderTriggered
Vue Router
Navigation Guards
https://router.vuejs.org/
Tricks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // nest
new Router({
routes: [{
path: '/parent',
component: {
render: (h) => h('router-view'),
},
children: [
{
path: '' /** default */,
component: ...
},
{
path: 'create',
component: ...
},
]
}]
})
|
Vuex
State
mapState
辅助函数
当映射的计算属性的名称与 state
的子节点名称相同时,我们也可以给 mapState
传一个字符串数组。
1
2
3
4
5
6
7
8
9
| export default {
computed: {
...mapState([
// 映射 this.count 为 store.state.count
'count'
])
}
}
|
Getter
可以认为是 store 的计算属性
1
2
3
4
5
6
7
8
9
10
11
12
| const store = new Vuex.Store({
// ...
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
// getter 作为第二个参数,可以调用其他 getter
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
})
|
1
2
3
4
5
6
7
| export const store = new Vuex.Store({
// ...
// 带参数的Getter
getters: {
getSubToc: state => (...path) => _.get(state.toc, path),
}
})
|
mapGetters
辅助函数
1
2
3
4
5
6
7
8
9
10
11
12
13
| import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
|
Mutation
Mutation
需遵守 Vue 的响应规则
既然 Vuex
的 store
中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue
组件也会自动更新。这也意味着 Vuex
中的 mutation
也需要与使用 Vue
一样遵守一些注意事项:
最好提前在你的 store
中初始化好所有所需属性。
当需要在对象上添加新属性时,你应该
使用 Vue.set(obj, 'newProp', 123)
, 或者
以新对象替换老对象。例如,利用 stage-3 的对象展开运算符我们可以这样写:
1
| state.obj = { ...state.obj, newProp: 123 }
|
mutation
必须是同步函数。在 Vuex
中,mutation
都是同步事务:
1
2
| store.commit('increment')
// 任何由 "increment" 导致的状态变更都应该在此刻完成。
|
使用常量替代 Mutation 事件类型
把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation
一目了然
Action
Action
类似于 mutation
,不同在于:
Action
提交的是 mutation
,而不是直接变更状态。
Action
可以包含任意异步操作。
Action
通过 store.dispatch
方法触发:
1
| store.dispatch('increment')
|
https://vuex.vuejs.org/zh/guide/
Class Component
https://class-component.vuejs.org/
install
1
| npm install --save vue vue-class-component
|
TypeScript
Babel
Data
1
2
3
| <template>
<div></div>
</template>
|
1
2
3
4
5
| @Component
export default class HelloWorld extends Vue {
// Declared as component data
message = 'Hello World!'
}
|
Methods
1
2
3
| <template>
<button v-on:click="hello">Click</button>
</template>
|
1
2
3
4
5
6
7
| @Component
export default class HelloWorld extends Vue {
// Declared as component method
hello() {
console.log('Hello World!')
}
}
|
Computed
Computed properties can be declared as class property getter / setter:
1
2
3
| <template>
<input v-model="name">
</template>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| @Component
export default class HelloWorld extends Vue {
firstName = 'John'
lastName = 'Doe'
// Declared as computed property getter
get name() {
return this.firstName + ' ' + this.lastName
}
// Declared as computed property setter
set name(value) {
const splitted = value.split(' ')
this.firstName = splitted[0]
this.lastName = splitted[1] || ''
}
}
|
Hooks
data
, render
and all Vue lifecycle hooks can be directly declared as class prototype methods.
Other Options
For all other options, pass them to the decorator function:
1
2
3
4
5
6
7
8
9
| @Component({
components: {
OtherComponent
},
setup(rops, context) {
// ...
}
})
export default class HelloWorld extends Vue {}
|
Additional Hooks
1
2
3
4
5
6
7
8
| import Component from 'vue-class-component'
// Register the router hooks with their names
Component.registerHooks([
'beforeRouteEnter',
'beforeRouteLeave',
'beforeRouteUpdate'
])
|
Custom Decorators
Property Type Declaration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| @Component({
computed: mapGetters([
'posts'
]),
methods: mapActions([
'fetchPosts'
])
})
export default class Posts extends Vue {
posts!: Post[]
fetchPosts!: () => Promise<void>
}
|
$refs
1
2
3
4
5
6
7
8
| export default class InputFocus extends Vue {
$refs!: {
input: HTMLInputElement
}
// ...
}
|
Property Decorator
https://github.com/kaorun343/vue-property-decorator
简易绑定
getter setter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| var obj = {
data: {
a: 1
},
get a() {
return this.data.a
},
set a(val) {
this.data.a = val
}
}
obj.a // 1
obj.a = 2
obj.a // 2
obj.data.a // 2
|
defineProperty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| var obj = {
data: {
num: 10,
name: 'sword'
},
}
for(let prop in obj.data) {
Object.defineProperty(obj, prop, {
get: function(){
return obj.data[prop]
},
set: function(val){
obj.data[prop] = val
}
})
}
obj.num // 10
obj.name // "sword"
obj.name = "armour"
obj.data.name // "armour"
|
Render Functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // tag name
this.$slots.default // array of children
)
},
props: {
level: {
type: Number,
required: true
}
}
})
|
1
2
| <anchored-heading :level="1">Hello world!</anchored-heading>
<anchored-heading :level="2">test</anchored-heading>
|
render
的返回值为 VNode
https://vuejs.org/v2/guide/render-function.html
JSX
https://github.com/vuejs/jsx
1
2
3
| render() {
return <p>hello</p>
}
|
Attributes/Props
1
2
3
| render() {
return <MyShare options={this.options} onSelect={this.onOptionSelect} />;
},
|
Slots
1
2
3
4
5
6
7
8
| render() {
return (
<MyComponent>
<header slot="header">header</header>
<footer slot="footer">footer</footer>
</MyComponent>
)
}
|
Directives
1
| <input vModel={this.newTodoText} />
|
1
| <input vOn:click_stop_prevent={this.newTodoText} />
|
v-html
:
1
| <p domPropsInnerHTML={html} />
|
Composition API
The new setup
component option is executed before the component is created.
We can make any variable reactive anywhere with a new ref
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
// in our component
setup (props) {
// using `toRefs` to create a Reactive Reference to the `user` property of props
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// update `props.user` to `user.value` to access the Reference value
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// set a watcher on the Reactive Reference to user prop
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
|
Reactivity APIs
reactive
, ref
, computed
, watchEffect
, watch
Lifecycle Hooks
- beforeCreate -> use
setup()
- created -> use
setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy ->
onBeforeUnmount
- destroyed ->
onUnmounted
- activated -> onActivated
- deactivated -> onDeactivated
Template Refs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <template>
<div ref="root"></div>
</template>
<script>
// ...
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
console.log(root.value) // <div/>
})
return {
root
}
}
</script>
|
Tricks
Reset Data
1
| Object.assign(this.$data, this.$options.data.apply(this))
|
chain v-model
1
| <input v-model="searchText">
|
does the same thing as:
1
2
3
4
| <input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
|
1
2
3
4
5
6
7
8
9
10
11
12
| // Address.vue
<template>
<div>
<quill-editor v-model="content" :options="option">
<div id="toolbar" slot="toolbar" ref="toolbar">
<button class="ql-bold">Bold</button>
<button class="ql-italic">Italic</button>
<button class="ql-image"></button>
</div>
</quill-editor>
</div>
</template>
|
1
2
3
4
5
6
7
8
9
10
11
12
| import { quillEditor } from 'vue-quill-editor';
export default {
props: ['value'],
components: { quillEditor },
computed: {
content: {
get() { return this.value; },
set(content) { this.$emit('input', content); },
},
}
}
|
router default
1
2
3
4
5
6
7
8
9
10
11
| children: [
{
path: '/main',
component: Main
},
// ... other children
{
path: '', // empty path
redirect: '/main'
}
];
|
Debug
print store in console:
1
| document.getElementsByTagName('div')[0].__vue__.$store.state
|
search Vue.js v2
Set a breakpoint on this line and reload the page.
vue.esm.js
step over next function call
1
| t.exports.default.config.devtools =true;
|
Search 2.6.1
in chunk-vue.[hash].js
1
| Cr.config.devtools = true;
|
Or search .config.devtools
Vetur
Project Setup
At project root create a jsconfig.json
.
1
2
3
4
5
6
7
8
9
10
11
12
13
| {
"include": [
"./src/**/*"
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
},
}
}
|
https://vuejs.github.io/vetur/setup.html#project-setup
Nginx 部署
1
2
3
4
5
6
7
8
9
10
11
12
| server {
listen 8066;
server_name localhost;
location / {
root /path/to/app/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location ~ ^/api/(.*)$ {
proxy_pass https://api.example.com;
}
}
|
Multiple Vue app:
1
2
3
4
5
| export default new Router({
mode: 'history',
base: '/app2',
routes: [...]
})
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| server {
listen 8080;
server_name localhost;
location ~ ^/app1/web {
alias /path/to/vue/app1/dist;
try_files $uri $uri/ /index.html =404;
}
location ~ ^/app1/api/(.*)$ {
rewrite /app1/api/(.*)$ /$1 break;
proxy_pass http://localhost:8877;
}
location ~ ^/app2/web {
alias /path/to/vue/app2/dist;
try_files $uri $uri/ /index.html =404;
}
location ~ ^/app2/api/(.*)$ {
rewrite /app2/api/(.*)$ /$1 break;
proxy_pass http://localhost:8888;
}
}
|
资料
官方指南
vue test utils