几个vue的概念
- vue是单项数据流,不是双向数据流
- Vue的双向绑定仅仅是语法糖,实际是绑定数据和事件(v-model实际是:value和@input的结合)
- Object.defineProperty是用来做响应式更新的,于双向绑定无关
组件注册
- 全局注册
Vue.component('test-comp', { })
Vue.component('testComp', { })
// 使用
<div>
<test-comp></test-comp>
<test-comp></test-comp>
<test-comp></test-comp>
</div>
- 局部
import testComp from 'xxx';
new Vue({
el: '#app',
components: {
testComp;
}
})
父子传值:Props和$emit
- 通过v-bind:xxx来传递数据,组件内部通过props接收,同时可以验证(type/required/validator)。
props: {
// fooA只接受数值类型的参数
fooA: Number,
// fooB可以接受字符串和数值类型的参数
fooB: [String, Number],
// fooC可以接受字符串类型的参数,并且这个参数必须传入
fooC: {
type: String,
required: true
},
// fooD接受数值类型的参数,如果不传入的话默认就是100
fooD: {
type: Number,
default: 100
},
// fooE接受对象类型的参数
fooE: {
type: Object,
// 当为对象类型设置默认值时必须使用函数返回
default: function(){
return { message: 'Hello, world' }
}
},
// fooF使用一个自定义的验证器
fooF: {
validator: function(value){
return value>=0 && value<=100;
}
}
}
- props属于单项数据流,只能通过父级修改,组件不能自己修改父级传入的props。
- 只能修改data定义内的值。如果需要修改需要通过自定义事件通知父级,由父级来修改。
// k-button
<template>
<button @click="handleClick">test</button>
</template>
<script>
export default {
methods: {
handleClick() {
this.$emit('toParent', {msg:'hello'})
}
},
}
</script>
// parents
<template>
<div>
<k-button @toParent="handleClick"></k-button>
</div>
</template>
<script>
import KButton from './components/Button.vue';
export default {
name: "app",
components: {
KButton,
},
methods: {
handleClick(msg){
console.log(msg);
}
}
};
</script>
v-model示例以及实现原理=>多v-model绑定实现(.sync)
// v-model是一个特殊的属性,相当于绑定了 :value和@input事件
<custom-input v-model="searchText"></custom-input>
<custom-input :value="searchText" @input="searchText = $event"></custom-input>
// PersonalInfo.vue 子组件
<template>
<div>
<select
:value="phoneInfo.areaCode"
placeholder="区号"
@change="handleAreaCodeChange"
>
<option value="+86">+86</option>
<option value="+60">+60</option>
</select>
<input
:value="phoneInfo.phone"
type="number"
placeholder="手机号"
@input="handlePhoneChange"
/>
<input
:value="zipCode"
type="number"
placeholder="邮编"
@input="handleZipCodeChange"
/>
</div>
</template>
<script>
export default {
name: "PersonalInfo",
model: {
prop: "phoneInfo", // 默认 value
event: "change" // 默认 input
},
props: {
phoneInfo: Object,
zipCode: String
},
methods: {
handleAreaCodeChange(e) {
this.$emit("change", {
...this.phoneInfo,
areaCode: e.target.value
});
},
handlePhoneChange(e) {
this.$emit("change", {
...this.phoneInfo,
phone: e.target.value
});
},
handleZipCodeChange(e) {
this.$emit("update:zipCode", e.target.value);
}
}
};
</script>
// index.vue 父组件
<template>
<div>
<PersonalInfo v-model="phoneInfo" :zip-code.sync="zipCode" />
<PersonalInfo
:phone-info="phoneInfo"
:zip-code="zipCode"
@change="val => (phoneInfo = val)"
@update:zipCode="val => (zipCode = val)"
/>
phoneInfo:
<br />
zipCode:
</div>
</template>
<script>
import PersonalInfo from "./PersonalInfo";
export default {
components: {
PersonalInfo
},
data() {
return {
phoneInfo: {
areaCode: "+86",
phone: ""
},
zipCode: ""
};
}
};
</script>
provide and inject
- 一般是上级组件provide,下级组件inject=>跨组件通信
- 主要用于高阶组件和组件库提供用例,不推荐在组件外使用
// componet A=>provide是提供数据
// 注意这里的form并不是响应式的
provide(){
return {
// 这里如果是form:this可以做到响应式,因为将当前组件的实例进行传递,子组件就可以获取到provide的内容
form:"test"
}
},
data () {
return {
form: "test"
}
}
// inject注入数据
// 子组件注入
var Child = {
// 如果provide是this,这里获取到的就是form的整个实例
inject: ['form'],
created () {
console.log(this.form) // => "test"
}
// ...
}
- vue中的this.$on监听当前实例上的自定义事件。事件可以由this.$emit触发
插槽
// index.vue
<a-tab-pane key="slot" tab="插槽">
<h2>2.6 新语法</h2>
<SlotDemo>
// 普通插槽
<p>default slot</p>
// 具名插槽
<template v-slot:title>
<p>title slot1</p>
<p>title slot2</p>
</template>
// 作用域插槽
<template v-slot:item="props">
<p>item slot-scope </p>
</template>
</SlotDemo>
<br />
<h2>老语法</h2>
<SlotDemo>
// 普通插槽
<p>default slot</p>
// 具名插槽
<p slot="title">title slot1</p>
<p slot="title">title slot2</p>
// 作用域插槽
<p slot="item" slot-scope="props">item slot-scope </p>
</SlotDemo>
</a-tab-pane>
// slot.vue
<template>
<div>
<slot />
<slot name="title" />
<slot name="item" v-bind="{ value: 'vue' }" />
</div>
</template>
<script>
export default {
name: "SlotDemo"
};
</script>
dispatch and $broadcast (vue 2.0已弃用,但是组件库开发中依然可用)
- 原理就是可以通过this.$parent和this.$children来获取父组件和子组件,递归一下
- 缺点:和其他基于组件树结构的事件流方式一样,缺乏扩展性
- dispatch=>递归获取$parent,向上触发
<button @click="dispatch('dispatch','哈喽 我是GrandGrandChild1')">dispatch</button>
methods: {
dispatch(eventName, data) {
let parent = this.$parent
// 查找父元素
while (parent ) {
if (parent) {
// 父元素用$emit触发
parent.$emit(eventName,data)
// 递归查找父元素
parent = parent.$parent
}else{
break
}
}
}
}
注意只向上传递了,并没有影响别的元素
- boardcast,递归获取$children 来向所有子元素广播
<button @click="$boardcast('boardcast','我是Child1')">广播子元素</button>
function boardcast(eventName, data){
this.$children.forEach(child => {
// 子元素触发$emit
child.$emit(eventName, data)
if(child.$children.length){
// 递归调用,通过call修改this指向 child
boardcast.call(child, eventName, data)
}
});
}
{
methods: {
$boardcast(eventName, data) {
boardcast.call(this,eventName,data)
}
}
}
- 全局挂载dispatch和boardcast=>便于全局使用,将其挂载到Vue的原型链上
- main.js
Vue.prototype.$dispatch = function(eventName, data) {
let parent = this.$parent
// 查找父元素
while (parent ) {
if (parent) {
// 父元素用$emit触发
parent.$emit(eventName,data)
// 递归查找父元素
parent = parent.$parent
}else{
break
}
}
}
Vue.prototype.$boardcast = function(eventName, data){
boardcast.call(this,eventName,data)
}
function boardcast(eventName, data){
this.$children.forEach(child => {
// 子元素触发$emit
child.$emit(eventName, data)
if(child.$children.length){
// 递归调用,通过call修改this指向 child
boardcast.call(child, eventName, data)
}
});
}