目录
- 1 Vue 实例
- 2 Vue 的生命周期方法
- 3 Vue 的数据绑定
- 4 computed 和 watch 使用场景和方法
- 5 Vue 的原生指令
- 6 Vue 的组件之组件的定义
- 7 Vue 的组件之组件的继承 extends
- 8 Vue 的组件之自定义双向绑定
- 9 Vue 的组件之高级属性
9.1 插槽 slot
9.2 跨级组件数据传递 provide 和 inject - 10 Vue 的组件之 render function
- 11 vue-loader 相关配置
1 Vue 实例
官网:https://cn.vuejs.org/v2/api/index.html#vm-listeners
实例:也就是通过 new 创建出来的对象。也就是在各个组件中的 this 对象。
1:实例相关属性:
$data
$props
$el
$options
$parent
$root
$children
$refs
$slots
$scopedSlots
$isServer
$attrs
$listeners
const app = new Vue({
// el: '#root',
template: '<div ref="div">{{text}} {{obj.a}}</div>',
data: {
text: 0,
obj: {}
}
})
// app 即为 vue 的实例
app.$mount('#app')
组件:
<template>
<div class="tab-container"></div>
</template>
<script>
export default {
props: {
todos: {
type: Array,
required: true
},
state: {
type: String,
required: true
}
},
inheritAttrs: true,
mounted() {
const vm = this; // !!!this 即为 vue 实例
console.log(vm.$attrs);
console.log(vm.$listeners);
}
};
</script>
例子
Todos.vue 父组件:
<template>
<div class="main-content" ref="todos">
<input
class="ipt-text"
autofocus="autofocus"
@keyup.enter="addItem"
v-model="iptText"
ref="todos-input"
/>
<item
v-for="todo in filteredTodos"
:key="todo.id"
:todo="todo"
@checkCompleted="checkCompleted"
@removeItem="removeItem"
/>
<tab
:todos="todos"
@clearNoActive111="clearNoActive"
@filterItem="filterItem"
:state="state"
v-bind="$attrs"
/>
<hr/>
<blog-post>
<template v-slot:header>
<h1>About Me</h1>
</template>
<p>Here's some page content, which will be included in vm.$slots.default, because it's not inside a named slot.</p>
<template v-slot:footer>
<p>Copyright 2016 Evan You</p>
</template>
<p>If I have some content down here, it will also be included in vm.$slots.default.</p>.
</blog-post>
</div>
</template>
<script>
import Vue from 'vue';
import Item from './Item.vue';
import Tab from './Tab.vue';
Vue.component('blog-post', {
render: function(createElement) {
var header = this.$slots.header;
var body = this.$slots.default;
var footer = this.$slots.footer;
return createElement('div', [
createElement('header', header),
createElement('main', body),
createElement('footer', footer)
]);
},
mounted() {
const vm = this;
console.log(vm.$slots);
console.log(vm.$scopedSlots);
}
});
export default {
name: 'main',
components: { Item, Tab },
data() {
return {
iptText: '',
index: 0,
state: 'all',
todos: [],
cacheTodos: []
};
},
methods: {
addItem(e) {
this.todos.unshift({
id: this.index++,
name: e.target.value,
completed: false
});
this.iptText = '';
}
},
mounted() {
const vm = this;
console.log(vm.$data);
console.log(vm.$props);
console.log(vm.$el);
console.log(vm.$options);
console.log(vm.$parent);
console.log(vm.$root);
console.log(vm.$children);
console.log(vm.$refs);
console.log(vm.$isServer);
}
};
</script>
<style scoped>
</style>
Tab.vue 子组件:
<template>
<div class="tab-container">
<div class="tab-content">
<span>{{isCheckTodoNum}} item check</span>
<div class="btn-group">
<span :class="{active: state === 'all', btn : true}" @click="$emit('filterItem', 'all')">all</span>
<span :class="{active: state === 'active', btn : true}" @click="$emit('filterItem', 'active')">active</span>
<span :class="{active: state === 'noActive', btn : true}" @click="$emit('filterItem', 'noActive')">noActive</span>
</div>
<button @click="$emit('clearNoActive')">clear noActive</button>
</div>
</div>
</template>
<script>
export default {
props: {
todos: {
type: Array,
required: true
},
state: {
type: String,
required: true
}
},
computed: {
isCheckTodoNum: function() {
return this.$parent.todos.filter(todo => todo.completed).length;
}
},
inheritAttrs: true,
mounted() {
const vm = this;
console.log(vm.$attrs);
console.log(vm.$listeners);
}
};
</script>
<style scoped>
</style>
说明:
- $attrs
1:$attrs 即为父组件传递给子组件,而子组件没有使用过的 props 属性;
2:如果父组件中没有使用过的属性,想向下深层传递,例如:向孙组件传递没有使用过的 props 属性,则子组件需要使用 v-bind="$attrs"
3:$attrs 结合 inheritAttrs: true
例如:
父组件:
<template>
<div class="hello">
<Todos
:dd="'dd'"
:ss="'ss'"
/>
</div>
</template>
子组件:Todos.vue
<template>
<tab
:todos="todos"
@clearNoActive="clearNoActive"
@filterItem="filterItem"
:state="state"
v-bind="$attrs" // !!!
/>
</template>
<script>
import Tab from './Tab.vue';
</script>
孙组件:Tab .vue
<script>
export default {
props: {
todos: {
type: Array,
required: true
},
state: {
type: String,
required: true
}
},
inheritAttrs: true,
mounted() {
const vm = this;
console.log(vm.$attrs);
}
};
</script>
参考:https://www.jianshu.com/p/ce8ca875c337
2:实例方法
-
$watch
(监听事件) -
$on
(注册事件) -
$emit
(触发事件$emit
和$on
触发的事件必须为同一个实例对象) -
$forceUpdate
(强制重新更新渲染) -
$set
(设置数据,Vue 会监听该属性) -
$delete
(删除数据,会自动删除 Vue 绑定监听该属性上的事件) -
$nextTick
(获取真实DOM,回调)
mounted() {
const unWatch = this.$watch('iptText', (newText, oldText) => {
// 组件销毁的时候,要手动注销
console.log(`${newText} : ${oldText}`);
});
setTimeout(() => {
unWatch();
}, 5000);
}
也可以通过 watch 方法。
<template>
<div class="main-content" ref="todos">
<input
class="ipt-text"
autofocus="autofocus"
@keyup.enter="addItem"
v-model="iptText"
ref="todos-input"
/>
</div>
</template>
<script>
export default {
name: 'main',
components: { Item, Tab },
data() {
return {
iptText: ''
}
},
watch: { // 主动注销
iptText(newText, oldText) {
console.log(`${newText} : ${oldText}`);
}
}
};
</script>
import Vue from 'vue'
const app = new Vue({
// el: '#root',
template: '<div ref="div">{{text}} {{obj.a}}</div>',
data: {
text: 0,
obj: {}
}
// watch: { // 主动注销
// text (newText, oldText) {
// console.log(`${newText} : ${oldText}`)
// }
// }
})
app.$mount('#root')
let i = 0
setInterval(() => {
i++
// app.text += 1
// app.text += 1
app.$set(app.obj, 'a', i)
// app.obj.a = i
// app.$forceUpdate()
// app.$delete(app.obj, 'a')
}, 1000)
// $emit 和 $on 触发的事件必须为同一个实例对象
app.$on('test', (a, b) => {
console.log(`test emited ${a} ${b}`)
})
// app.$once('test', (a, b) => {
// console.log(`test emited ${a} ${b}`)
// })
setInterval(() => {
app.$emit('test', 1, 2)
}, 1000)
// app.$forceUpdate() // 强制组件重新渲染
// $nextTick
2 Vue 的生命周期方法
生命周期import Vue from 'vue'
const app = new Vue({
// el: '#root',
// template: '<div>{{text}}</div>',
data: {
text: 0
},
beforeCreate() {
console.log(this.$el, 'beforeCreate')
},
created() {
console.log(this.$el, 'created')
},
beforeMount() { // 服务端渲染不会触发,因为服务端渲染,根本就跟 DOM 挂载无关
console.log(this.$el, 'beforeMount')
},
mounted() { // 服务端渲染不会触发,因为服务端渲染,根本就跟 DOM 挂载无关
console.log(this.$el, 'mounted')
},
beforeUpdate() {
console.log(this, 'beforeUpdate')
},
updated() {
console.log(this, 'updated')
},
activated() { // 在组件章节讲解
console.log(this, 'activated')
},
deactivated() { // 在组件章节讲解
console.log(this, 'deactivated')
},
beforeDestroy() {
console.log(this, 'beforeDestroy')
},
destroyed() {
console.log(this, 'destroyed')
},
render(h) {
throw new TypeError('render error')
// console.log('render function invoked')
// return h('div', {}, this.text)
},
renderError(h, err) { // 上线打包不能被调用
return h('div', {}, err.stack)
},
errorCaptured() {
// 如果在根组件定义,所有子组件报错都可以捕获到,除非子组件取消冒泡事件。
// 会向上冒泡,并且正式环境可以使用
}
})
app.$mount('#root')
// setInterval(() => {
// app.text = app.text += 1
// }, 1000)
setTimeout(() => {
app.$destroy() // 自动销毁组件
}, 1000)
3 Vue 的数据绑定
:class、:style、v-html、@click
参考 list-todo 例子:https://www.jianshu.com/writer#/notebooks/37329580/notes/48268401
import Vue from 'vue'
new Vue({
el: '#root',
// template: `
// <div :id="aaa" @click="handleClick">
// <p v-html="html"></p>
// </div>
// `,
template: `
<div
:class="[{ active: isActive }]"
:style="[styles, styles2]"
>
<p>{{getJoinedArr(arr)}}</p>
</div>
`,
data: {
isActive: false,
arr: [1, 2, 3],
html: '<span>123</span>',
aaa: 'main',
styles: {
color: 'red',
appearance: 'none'
},
styles2: {
color: 'black'
}
},
methods: {
handleClick () {
alert('clicked') // eslint-disable-line
},
getJoinedArr (arr) {
return arr.join(' ')
}
}
})
4 computed 和 watch 使用场景和方法
参考:https://cn.vuejs.org/v2/guide/computed.html
import Vue from 'vue'
new Vue({
el: '#root',
template: `
<div>
<p>Name: {{name}}</p>
<p>Name: {{getName()}}</p>
<p>Number: {{number}}</p>
<p>FullName: {{fullName}}</p>
<p><input type="text" v-model="number"></p>
<p>FirstName: <input type="text" v-model="firstName"></p>
<p>LastName: <input type="text" v-model="lastName"></p>
<p>Name: <input type="text" v-model="name"></p>
<p>Obj.a: <input type="text" v-model="obj.a"></p>
</div>
`,
data: {
firstName: 'Jokcy',
lastName: 'Lou',
number: 0,
fullName: '',
obj: {
a: 0
}
},
computed: { // 属性需要通过计算再显示,一般 computed 不要设置值,computed 就是通过计算重新组合需要显示的值。
// name() {
// console.log('new name')
// return `${this.firstName} ${this.lastName}`
// },
name: {
get() {
console.log('new name')
return `${this.firstName} ${this.lastName}`
},
set(name) {
const names = name.split(' ')
this.firstName = names[0]
this.lastName = names[1]
}
}
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
},
// watch: { // 主要场景:监听某个属性
// 'obj.a': {
// handler() { // 实际执行就是 handler 方法
// console.log('obj.a changed')
// this.obj.a += 1
// },
// immediate: true // 声明该属性之后,立即执行,而不是等到属性变化后才执行
// // deep: true // 如果直接监听 obj,使用 deep: true,则对象的所有属性都会被监听,性能开销比较大;可以直接监听对应的属性 'obj.a'
// }
// },
methods: {
getName() {
console.log('getName invoked')
return `${this.firstName} ${this.lastName}`
}
}
})
说明:
-
1:computed:属性需要通过计算再显示,一般 computed 不要设置值,computed 就是通过计算重新组合需要显示的值。
-
2:watch:主要场景是监听某个属性,当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
watch 在首次加载时并不会马上触发,如果希望首次触发,可以配置:
immediate: true
watch 监听对象时,如果对象的某个属性发生改变是,是不会触发监听事件的,因为对象的地址并没有改变,监听的是对象的地址,但是,可以配置:
deep: true
如果直接监听 obj,使用 deep: true,则对象的所有属性都会被监听,性能开销比较大;可以直接监听对应的属性 'obj.a'
<p>Reversed message: "{{ reversedMessage() }}"</p>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
- 4: 计算属性 vs 侦听属性
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch
——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch
回调。
<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
上面代码是命令式且重复的。将它与计算属性的版本进行比较:
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
好得多了,不是吗?
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
5 Vue 的原生指令
v-pre
v-once
v-html
v-if
v-else-if
v-else
v-model
-
v-model.number
(输入为number) -
v-model.trim
(自动去除首尾空格) -
v-model.lazy
(input 事件变为 change,懒加载) v-for
data 数组绑定复选框、以及单选框等。
import Vue from 'vue'
new Vue({
el: '#root',
template: `
<div>
<div v-pre>Text: {{text}}</div>
<div v-once>Text: {{text}}</div>
<div>Text: {{text}}</div>
<div v-if="text === 0">Else Text: {{text}}</div>
<div v-else>else content</div>
<div v-html="html"></div>
<input text="text" v-model="text">
<input text="text" v-model.number="text">
<input text="text" v-model.trim="text">
<input text="text" v-model.lazy="text">
<input type="checkbox" v-model="active">
<div>
<input type="checkbox" :value="1" v-model="arr">
<input type="checkbox" :value="2" v-model="arr">
<input type="checkbox" :value="3" v-model="arr">
</div>
<div>
<input type="radio" value="one" v-model="picked">
<input type="radio" value="two" v-model="picked">
</div>
<ul>
<li v-for="(item, index) in arr" :key="item">{{item}}:{{index}}</li>
</ul>
<ul>
<li v-for="(val, key, index) in obj">{{val}}:{{key}}:{{index}}</li>
</ul>
</div>
`,
data: {
arr: [2, 3],
obj: {
a: '123',
b: '456',
c: '789'
},
picked: '',
text: 0,
active: false,
html: '<span>this is html</span>'
}
})
6 Vue 的组件之组件的定义
- 方式一:全局注册
Vue.component('CompOne', compoent)
规范:定义大写驼峰 CompOne,使用的时候,小写横杆 comp-one
- 方式二:组件内部
components: {
CompOne: compoent
},
例子:
import Vue from 'vue'
const compoent = {
props: {
// active: Boolean,
active: {
// type: Boolean,
// required: true,
validator(value) { // !!!自定义校验
return typeof value === 'boolean'
}
// default() { // !!!默认值必须和 data 一样通过函数返回值
// return false
// }
},
propOne: String
},
template: `
<div>
<input type="text" v-model="text">
<span @click="handleChange">{{propOne}}</span>
<span v-show="active">see me if active</span>
</div>
`,
data() { // 必须是函数 return
return {
text: 0
}
},
methods: {
handleChange() {
this.$emit('change')
}
}
}
// Vue.component('CompOne', compoent) // 方式一,全局;规范:定义大写驼峰 CompOne,使用的时候,小写横杆comp-one
/* eslint-disable no-new */
new Vue({
components: { // 方式二
CompOne: compoent
},
data: {
prop1: 'text1'
},
methods: {
handleChange() {
this.prop1 += 1
}
},
mounted() {
console.log(this.$refs.comp1)
},
el: '#app',
template: `
<div>
<comp-one ref="comp1" :active="true" :prop-one="prop1" @change="handleChange"></comp-one>
<comp-one :active="true" propOne="text2"></comp-one>
</div>
`
})
** 说明:组件 props 属性定义和校验**
props: {
// active: Boolean,
active: {
// type: Boolean,
// required: true,
validator(value) { // !!!自定义校验
return typeof value === 'boolean'
}
// default() { // !!!默认值必须和 data 一样通过函数返回值
// return false
// }
},
propOne: String
},
7 Vue 的组件之组件的继承 extends
extends: compoent,
const CompVue = Vue.extend(compoent)
组件继承之后,数据会覆盖,但是,组件各自的生命周期不会覆盖;如果要获取继承组件的 props,不能通过 props 获取,应该通过 propsData 属性。
例子:
import Vue from 'vue'
const compoent = {
props: {
active: Boolean,
propOne: String
},
template: `
<div>
<input type="text" v-model="text">
<span @click="handleChange">{{propOne}}</span>
<span v-show="active">see me if active</span>
</div>
`,
data() {
return {
text: 0
}
},
mounted() {
console.log('comp mounted')
},
methods: {
handleChange() {
this.$emit('change')
}
}
}
const componet2 = {
extends: compoent,
data() {
return {
text: 1
}
},
mounted() {
// this.$parent.text = 12345; // 可以改变父组件的数据,但是不要修改,会混乱!!!
console.log(this.$parent.$options.name) // Root
}
}
// const CompVue = Vue.extend(compoent)
// new CompVue({
// el: '#root',
// propsData: { // 通过 props 拿不到
// propOne: 'xxx'
// },
// data: { // 合并属性会覆盖
// text: '123'
// },
// mounted () { // 组件生命周期不会覆盖
// console.log('instance mounted')
// }
// })
const parent = new Vue({
name: 'parent111'
})
/* eslint-disable no-new */
new Vue({
parent: parent,
name: 'Root',
el: '#app',
mounted() {
console.log(this.$parent.$options.name) // parent111
},
components: {
Comp: componet2
},
data: {
text: 23333
},
template: `
<div>
<span>{{text}}</span>
<comp></comp>
</div>
`
})
8 Vue 的组件之自定义双向绑定
- 定义组件,给 input 输入框,绑定 @input 事件 和 :value 值
const component = {
template: `
<div>
<input type="text" @input="handleInput" :value="value">
</div>
`,
methods: {
handleInput(e) {
this.$emit('input', e.target.value)
}
}
}
使用组件:
import Vue from 'vue'
/* eslint-disable no-new */
new Vue({
components: {
CompOne: component
},
el: '#app',
data() {
return {
value: '123'
}
},
template: `
<div>
<comp-one :value="value" @input="value = arguments[0]">默认 value + input 事件</comp-one>
</div>
`
})
- 等同于 v-model:( v-model 其实就是 value 值 + input 事件的绑定)
import Vue from 'vue'
/* eslint-disable no-new */
new Vue({
components: {
CompOne: component
},
el: '#app',
data() {
return {
value: '123'
}
},
template: `
<div>
<comp-one v-model="value"></comp-one>
</div>
`
})
- input 事件改为 change:
const component = {
model: {
event: 'change' // 默认为 input
},
template: `
<div>
<input type="text" @change="handleInput" :value="value">
</div>
`,
methods: {
handleInput(e) {
this.$emit('change', e.target.value)
}
}
}
<comp-one :value="value" @change="value = arguments[0]"></comp-one>
- 更改绑定的 value 属性为自定义属性,需要配置 model、props
const component = {
model: {
prop: 'value1',
event: 'change' // 默认为 input
},
props: ['value1'],
// props: ['value'], // 默认绑定为 value
template: `
<div>
<input type="text" @input="handleInput" :value="value1">
</div>
`,
methods: {
handleInput(e) {
this.$emit('change', e.target.value)
// this.$emit('input', e.target.value)
}
}
}
<comp-one :value1="value" @change="value = arguments[0]"></comp-one>
例子:
import Vue from 'vue'
const component = {
model: {
prop: 'value1',
event: 'change' // 默认为 input
},
props: ['value1'],
// props: ['value'], // 默认绑定为 value
template: `
<div>
<input type="text" @input="handleInput" :value="value1">
</div>
`,
methods: {
handleInput(e) {
this.$emit('change', e.target.value)
// this.$emit('input', e.target.value)
}
}
}
/* eslint-disable no-new */
new Vue({
components: {
CompOne: component
},
el: '#app',
data() {
return {
value: '123'
}
},
template: `
<div>
<comp-one :value="value" @input="value = arguments[0]">默认 value + input 事件</comp-one>
<comp-one :value1="value" @change="value = arguments[0]"></comp-one>
<comp-one v-model="value"></comp-one>
</div>
`
})
v-model 其实就是 value 值 + input 事件的绑定。
9 Vue 的组件之高级属性
9.1 插槽 slot
- 通过 name 指定对应的 slot
- 在组件配置 slot-scope 获取插槽 slot 属性
/* eslint-disable no-new */
import Vue from 'vue'
const component = {
name: 'comp',
// template: `
// <div :style="style">
// <div class="header">
// <slot name="header"></slot>
// </div>
// <div clas s="body">
// <slot name="body"></slot>
// </div>
// </div>
// `,
template: `
<div :style="style">
<slot></slot>
<slot name="header"></slot>
<slot name="footer"></slot>
<slot :value="value" aaa="111"></slot>
</div>
`,
data() {
return {
style: {
width: '200px',
height: '200px',
border: '1px solid #aaa'
},
value: 'component value'
}
}
}
new Vue({
components: {
CompOne: component
},
el: '#app',
data() {
return {
value: '123'
}
},
mounted() {
console.log(this.$refs.comp.value, this.$refs.span) // 一般不要使用 ref,避免滥用
},
template: `
<div>
<comp-one>
<span>默认</span>
</comp-one>
<comp-one>
<span slot="header">name 指定 header 位置</span>
</comp-one>
<comp-one>
<span slot="footer">name 指定 footer 位置</span>
</comp-one>
<comp-one>
<span slot="footer">name 指定 footer 位置,变量: {{value}}</span>
</comp-one>
<comp-one ref="comp">
<span slot-scope="props" ref="span">{{props.value}} {{props.aaa}} {{value}}</span>
</comp-one>
<input type="text" v-model="value" />
</div>
`
})
9.2 跨级组件数据传递 provide 和 inject
这种方式,孙组件通过 provide 注入的 value 值并不会跟随父组件实时渲染。
/* eslint-disable no-new */
import Vue from 'vue'
const ChildComponent = {
template: '<div>child component: {{value}}</div>',
inject: ['yeye', 'value'], // 孙子获取父辈实例,需要有上下级关系!!!
mounted() {
console.log(this.yeye, this.value)
}
}
const component = {
name: 'comp',
components: {
ChildComponent
},
template: `
<div :style="style">
<slot :value="value" aaa="111"></slot>
<child-component />
</div>
`,
data() {
return {
style: {
width: '200px',
height: '200px',
border: '1px solid #aaa'
},
value: 'component value'
}
}
}
new Vue({
components: {
CompOne: component
},
provide() {
return { // value 默认不会提供渲染
yeye: this, // !!!
value: this.value
}
},
el: '#app',
data() {
return {
value: '123'
}
},
mounted() {
console.log(this.$refs.comp.value, this.$refs.span) // 一般不要使用 ref,避免滥用
},
template: `
<div>
<comp-one ref="comp">
<span slot-scope="props" ref="span">{{props.value}} {{props.aaa}} {{value}}</span>
</comp-one>
<input type="text" v-model="value" />
</div>
`
})
provide 和 inject 改进,数据跟随实时渲染
/* eslint-disable no-new */
import Vue from 'vue'
const ChildComponent = {
template: '<div>child component: {{data.value}}</div>',
inject: ['yeye', 'data'], // 孙子获取父辈实例,需要有上下级关系!!!
mounted() {
console.log(this.yeye, this.data.value)
}
}
const component = {
name: 'comp',
components: {
ChildComponent
},
template: `
<div :style="style">
<slot :value="value" aaa="111"></slot>
<child-component />
</div>
`,
data() {
return {
style: {
width: '200px',
height: '200px',
border: '1px solid #aaa'
},
value: 'component value'
}
}
}
new Vue({
components: {
CompOne: component
},
provide() {
const data = {}
Object.defineProperty(data, 'value', {
get: () => this.value,
enumerable: true
})
return {
yeye: this, // !!!
data
}
},
el: '#app',
data() {
return {
value: '123'
}
},
mounted() {
console.log(this.$refs.comp.value, this.$refs.span) // 一般不要使用 ref,避免滥用
},
template: `
<div>
<comp-one ref="comp">
<span slot-scope="props" ref="span">{{props.value}} {{props.aaa}} {{value}}</span>
</comp-one>
<input type="text" v-model="value" />
</div>
`
})
10 Vue 的组件之 render function
在组件中,template 模板最终会被 Vue 编译成 render function,render 方法接收一个 createElement 方法参数,也可以通过内部属性 this.$createElement;
createElement 方法接收三个参数,第一个为对应的节点名称,第二个为节点的属性,第三个为数组,可以是节点的数据或者内容,也可以继续通过 createElement 方法创建子节点。
import Vue from 'vue'
const component = {
props: ['props1'],
name: 'comp',
// template: `
// <div :style="style">
// <slot></slot>
// </div>
// `,
render(createElement) {
return createElement('div', {
style: this.style
// on: {
// click: () => { this.$emit('click') }
// }
}, [
this.$slots.header, // this.$slots.default
this.props1
])
},
data() {
return {
style: {
width: '200px',
height: '200px',
border: '1px solid #aaa'
},
value: 'component value'
}
}
}
new Vue({
components: {
CompOne: component
},
el: '#root',
data() {
return {
value: '123'
}
},
mounted() {
console.log(this.$refs.comp.value, this.$refs.span)
},
methods: {
handleClick() {
console.log('clicked')
}
},
// template: ` 编译成js函数render方法;
// <comp-one ref="comp">
// <span ref="span">{{value}}</span>
// </comp-one>
// `,
render(createElement) { // this.$createElement;template编辑成render,整个流程是通过vue-loader处理的
return createElement(
'comp-one',
{
ref: 'comp',
props: {
props1: this.value
},
// on: {
// click: this.handleClick
// },
nativeOn: { // 绑定在组件根节点原生DOM上,自动处理
click: this.handleClick
}
},
[
createElement('span', {
ref: 'span',
slot: 'header',
// domProps: {
// innerHTML: '<span>sssssss</span>'
// },
attrs: {
id: 'test-id'
}
}, this.value)
]
)
}
})
11 vue-loader 相关配置
配置参考:
网友评论