文章可能会再更新,具体查看github
子组件向父组件传值: 一、ref、$refs的方式: 首先,假设我们有两个组件,父组件叫HelloAnother.vue,子组件叫childProps.vue。父组件里面有一个router路由,父组件的结构如下所示。
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<child-props :message = msg ref="childp">
</child-props>
<router-link to="/HelloWorld">
<span>switch</span>
</router-link>
</div>
</template>
<script>
import childProps from '@/base/childProps'
export default {
name: 'HelloAnother',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
components:{
childProps
},
mounted: function() {
console.log(this.$refs.childp.childPro);
console.log(this._data)
this.$refs.childp.tryChild.call(this.$refs.childp)
}
}
</script>
复制代码
在这个父组件中,child-props(注意:vue.js文档中建议我们组件名可以用驼峰法,但是当写在template的时候,需要转换成child-props,这是因为html的标签一般都是没有连字符的,为了预防与未来某个标准标签名重名)和router-link都是HelloAnother.vue的子组件。我们先看看childProps.vue的结构:
<template>
<div>
{{message}}
{{data}}
</div>
</template>
<script>
export default {
name: "childProps",
data(){
return {
test:'hello',
hello:"yes",
childPro: "enenen"
}
},
props:{
message: {
type: String,
default: ''
},
data: {
type: Array,
default: () => []
}
},
methods:{
}
}
</script>
复制代码
我们再来看看childProps.vue打印出来的属性: 我们看到,它的Ctor(constructor)指向的是VueComponent构造函数,说明childProps.vue是一个注册后的component。instances指向的是它这个实例。下面这个图是展开后的instances,每个component的el指向的是div,这个也没毛病,就是渲染后的html的div节点。 接下来我们重点看一下childProps.vue的$vnode节点属性:它指向的是VNode构造函数,它长下面这个样子:
var VNode = function VNode (tag, data, children, text, elm, context, componentOptions, asyncFactory) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
this.ns = undefined;
this.context = context;
//.............................
this.isAsyncPlaceholder = false;
};
复制代码
这里我们需要重点关注的是,parent或者parent都是指向它的父组件的): 接下来,是父组件获取子组件值的方法: 我们看到了父组件的refs里面就有了一个属性this["childp"]指向子组件。
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<child-props :message = msg ref="childp">
</child-props>
<router-link to="/HelloWorld">
<span>switch</span>
</router-link>
</div>
</template>
复制代码
以上是怎么实现的呢?是通过registerRef方法,将子组件的vnode.data.ref注册给了它的父组件vnode.context.$refs。
function registerRef (vnode, isRemoval) {
// 获得这个子组件的ref名,比如"childp"
var key = vnode.data.ref;
if (!isDef(key)) { return }
var vm = vnode.context;
// 将vnode(当前子组件)的componentInstance(子组件实例VueComponent)缓存为ref
// 这里也就是childProps.vue
var ref = vnode.componentInstance || vnode.elm;
var refs = vm.$refs;
if (isRemoval) {
if (Array.isArray(refs[key])) {
remove(refs[key], ref);
} else if (refs[key] === ref) {
refs[key] = undefined;
}
} else {
if (vnode.data.refInFor) {
if (!Array.isArray(refs[key])) {
refs[key] = [ref];
} else if (refs[key].indexOf(ref) < 0) {
// $flow-disable-line
refs[key].push(ref);
}
} else {
refs[key] = ref;
}
}
}
复制代码
那么,对于vnode.componentInstance,我们是怎么去获取这个实例的呢?是通过createComponentInstanceForVnode方法赋值给的vnode.componentInstance:
vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
复制代码
而createComponentInstanceForVnode方法内部会最终返回一个vnode.componentOptions.Ctor(options),这个Ctor其实就是VueComponent方法。
二、触发事件的方法: 我们在父组件中绑定一个方法:
<template>
<div class="hello">
<child-props :messages = msg ref="childp" @msgFunc = "func">
</child-props>
</div>
</template>
<script>
import childProps from '@/base/childProps'
export default {
name: 'HelloAnother',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
components:{
childProps
},
mounted: function() {
//...
},
methods: {
func (message) {
console.log(message)
}
}
}
</script>
复制代码
然后在子组件childProps.vue里面,绑定一个点击事件handleParent,在子组件的methods里面,this.$emit('msgFunc',this.message) 触发这个方法:
<template>
<div>
<button @click = "handleParent">try me</button>
</div>
</template>
<script>
export default {
name: "childProps",
data(){
return {
}
},
props:{
message: {
type: String,
default: 'default'
}
},
methods: {
handleParent () {
this.$emit('msgFunc',this.message);
}
}
}
</script>
复制代码
接下来是父组件向子组件传值: 使用props的方式: 我们来看看父组件如何向子组件传值: 首先我们的childProps.vue里面可以写:
<template>
<div>I am {{message}}</div>
</template>
<script>
export default {
name: "child",
props: {
"message": {
type: String,
default: ''
}
},
data(){
return {
text: "from child"
}
}
}
</script>
复制代码
然后我们在HelloAnother.vue里面引入子组件childProps.vue, 然后通过v-bind传入mes数据(也可以直接message = "字符串")
<template>
<child-co :message = "mes"></child-co>
</template>
<script>
import childCo from './child'
export default {
name: "childCo",
data(){
mes: "parents love their children"
}
components: {
child
}
}
</script>
复制代码
我们在子组件里面添加props属性的时候,一般有两种写法:
// 简单的数组(不推荐)
export default {
props:['message','inform']
}
// 对象的形式(推荐)
// 因为这样显示属性的类型
export default {
props: {
message: {
type: String,
default: ''
},
inform: {
type: Array,
// 因为是对象
// 所以default需要是一个返回该类型的函数
default: () => []
}
}
}
复制代码
其实我们写成第一种方式的话,Vue也会自动帮我们把props(Array)属性,转换为Object {},即第二种的形式,只不顾没有default属性,message的type值为null,具体实现是通过normalizeProps实现的:
function normalizeProps (options, vm) {
var props = options.props;
if (!props) { return }
var res = {};
var i, val, name;
if (Array.isArray(props)) {
i = props.length;
while (i--) {
val = props[i];
if (typeof val === 'string') {
name = camelize(val);
// 类型值为null
res[name] = { type: null };
} else {
warn('props must be strings when using array syntax.');
}
}
}
// 判断是否为{}对象
// return _toString.call(obj) === '[object Object]
else if (isPlainObject(props)) {
for (var key in props) {
val = props[key];
name = camelize(key);
res[name] = isPlainObject(val)
? val
: { type: val };
}
} else {
//........
}
// 将对象化后的props给了options.props
options.props = res;
}
复制代码
**this.children的形式访问子组件。但是这种情况下,由于一个父组件可能有多个子组件,比如HelloAnother.vue就有router和childProps.vue两个子组件,.$children返回的就是一个长度为2的数组: [VueComponent, VueComponent]
需要注意 children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源