1. 1. 使用ref来主动操作子组件
    1. 1.1. 背景代码
    2. 1.2. 取值
    3. 1.3. 调用方法
  2. 2. 子组件直接调用父组件的方法
    1. 2.1. 通过props给子组件传个函数
  3. 3. 通过provide / inject 来进行父子组件传值
    1. 3.1. 父组件
    2. 3.2. 子组件: pchild.vue
  4. 4. 把你的组件改为通过JS方法来调用
  5. 5. 在Vue中使用JSX

使用ref来主动操作子组件

背景代码

这是从element-ui复制的代码自定义校验规则 的代码,删除了提交和重置

  • 子组件:form.vue
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model.number="ruleForm.age"></el-input>
</el-form-item>
</el-form>

<script>
export default {
data() {
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('年龄不能为空'));
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error('请输入数字值'));
} else {
if (value < 18) {
callback(new Error('必须年满18岁'));
} else {
callback();
}
}
}, 1000);
};
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.ruleForm.pass) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
ruleForm: {
pass: '',
checkPass: '',
age: ''
},
rules: {
pass: [
{ validator: validatePass, trigger: 'blur' }
],
checkPass: [
{ validator: validatePass2, trigger: 'blur' }
],
age: [
{ validator: checkAge, trigger: 'blur' }
]
}
};
},
methods: {}
}
</script>

假设你的一个表单提交页面分为了基础信息,常规信息,扩展信息,附加信息四大块,一般来说我们会将四块做成四个组件,每个组件都是一个表单,当用户点击提交按钮的时候我们拿取四个组件传出来得值,整理之后进行提交。

但此处的问题是,这个表单提交页面整体只有一个提交按钮。Vue的父子组件通信是父组件通过props传值进去,子组件通过emit 向外触发事件,父组件监听子组件的事件来获取子组件的值。四个子组件没有按钮,也就是无法向外触发事件,这个时候用户点页面的提交按钮,没办法拿到子组件的值,这就要了命了。

当然可以使用input或者change 什么事件来向外触发,但总体来说都不完美。所以我们更希望的是父组件主动的拿子组件的值,调用子组件的方法,所以我们这里的核心就是借用Vue的 ref API。

先引入父组件,正常使用

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="container">
<p-form ref="p_form"></p-form>
<el-button type="primary">提交</el-button>
</div>
</template>

<script>
import PForm from './form.vue';
export default {
components: {PForm},
}
</script>

取值

痛过ref 直接去拿子组件data的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="container">
<p-form ref="p_form"></p-form>
<el-button type="primary" @click="handleSubmit">提交</el-button>
</div>
</template>

<script>
import PForm from './form.vue';
export default {
components: {PForm},
methods: {
handleSubmit() {
const {ruleForm: {pass,checkPass,age}} = this.$refs.p_form;
console.log(pass,checkPass,age,this.$refs.p_form); // 可以打印出来看一下都是些什么东西;其实通过this.$refs.p_form就拿到了子组件的实例,就可以对子组件进行任意操作了
}
}
}
</script>

调用方法

如果在取值之前我想先进行校验,校验通过再取值往下执行,校验不通过就标红,提示用户信息不完整

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
<template>
<div class="container">
<p-form ref="p_form"></p-form>
<el-button type="primary" @click="handleSubmit">提交</el-button>
</div>
</template>

<script>
import PForm from './form.vue';
export default {
components: {PForm},
methods: {
async handleSubmit() {
const {ruleForm: {pass,checkPass,age},$refs: {ruleForm}} = this.$refs.p_form;
try {
await ruleForm.validate();
// PForm子组件的form表单上也有一个ref,ref的name就是ruleForm
// validate这个方法就是element-ui在表单这个子组件上提供的方法
// 这里我用了async/await,validate校验通过会返回resolve,失败会返回reject,也就失败了会走catch语句
// 调用PForm组件的其他方法,是同样的原理,PForm实例打印出来看起一下也就知道怎么用了
console.log(pass,checkPass,age,this.$refs.p_form); // 可以打印出来看一下都是些什么东西;其实通过this.$refs.p_form就拿到了子组件的实例,就可以对子组件进行任意操作了
} catch(err) {
throw new Error('表单校验不通过');
}
}
}
}
</script>

子组件直接调用父组件的方法

上文说了父组件主动拿子组件的数据,调用子组件的方法,那这里就说下子组件主动去调父组件的方法。

正常情况父子组件通信都是父组件props 把数据传进去,子组件通过$emit 触发事件,将数据传出去,父组件监听向外触发的事件,接收子组件向外传的值,然后父组件拿到值之后在做后续处理。

现在我们要实现的就是绕过$emit ,子组件直接调用父组件的方法。

流程就是我们通过props 给子组件传个父组件的方法让子组件直接调用

通过props给子组件传个函数

  • 父组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="container">
<!-- text给子组件传进去的是一个函数 -->
<p-child :text="updateText"></p-child>
{{text}}
</div>
</template>

<script>
import PChild from './child';
export default {
components: {PChild},
data() {
return {
text: '默认值'
}
},
methods: {
updateText(content) {
this.text = content;
}
}
}
</script>
  • 子组件

    这里用了iVew-ui

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="container">
<Input v-model="value11">
<Button slot="append" @click="handleBtnClick">确认</Button>
</Input>
</div>
</template>

<script>
export default {
data() {
return {
value11: '',
}
},
methods: {
handleBtnClick() {
// 父组件通过text传进了一个父组件的方法,但子组件并没有通过props接收,此时父组件传进了的内容就在$attrs中
this.$attrs.text(this.value11);
}
}
}
</script>

这个代码示例比较极端,但如果当你感觉props/emit 的流程比较麻烦的时候,可以考虑这么来玩一下,可以少写部分代码。

通过provide / inject 来进行父子组件传值

provideinject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。官方文档

有没有遇到过这种业务场景,用户下单但是未支付,半小时未支付自动取消订单,前端在这个时候就需要做一个倒计时,在倒计时结束的时候刷新下页面,使订单状态更新。

固然,当倒计时结束的时候你可以使用window.location.reload() 来使页面更新,但这样会造成整个页面的刷新,而我们只希望某一块区域小范围刷新。所以这个时候其实我们就可以借助provide / inject 来玩下(其实有其他更好的方法,这里主要为了演示provide / inject 使用)。

父组件

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
<template>
<div class="page">
<p-child v-if="isShow"></p-child>
</div>
</template>

<script>
import PChild from 'pchild';
export default {
components: {PChild},
// 通过provide向子组件提供一个函数,这个函数来源于methods
provide() {
return {
reload: this.reload
};
},
data() {
return {
isShow: true
}
},
methods: {
// 这个方法被调用的时候子组件首先会被销毁,然后等待销毁之后也就是dom更新后,重新渲染这个组件(相当于组件重启)
reload() {
this.isShow = false;
this.$nextTick(function() {
this.isShow = true;
});
}
}
}
</script>

子组件: pchild.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="page">
<button @click="handleReload">
刷新
</button>
</div>
</template>

<script>
export default {
// 将父组件提供的函数通过inject注入到子组件中
inject: ['reload'],
methods: {
// 被点击的时候执行父组件的方法(其实可以直接点击的时候执行reload,没必要通过handleReload来调用)
handleReload() {
this.reload();
}
}
}
</script>

把你的组件改为通过JS方法来调用

在Vue中使用JSX