简介
Vue
和React
是目前前端最火的两个框架。不管是面试还是工作可以说是前端开发者们都必须掌握的。
今天我们通过对比的方式来学习Vue
和React
的组件传值这一部分。
本文首先讲述Vue2
、Vue3
、React
的组件传值方式,然后具体介绍了每种传值的应用场景以及具体的使用。最后对比总结了Vue
和React
在组件传值这部分的相同点和不同点。
希望通过这种对比方式的学习能让我们学习的时候印象更深刻,希望能够帮助到大家。
Vue2
Vue2
组件通信共有12种
props
$emit / v-on
.sync
v-model
ref
$children / $parent
$attrs / $listeners
provide / inject
EventBus
Vuex
$root
slot
应用场景
Vue2
组件通信方式虽然有很多种,但是不同方式有不同的应用场景。
父子组件通信
props
$emit / v-on
$attrs / $listeners
ref
.sync
v-model
$children / $parent
slot
兄弟组件通信
EventBus
Vuex
$parent
跨层级组件通信
provide/inject
EventBus
Vuex
$attrs / $listeners
$root
具体使用
props
props
用于父组件向子组件传送数据。
子组件接收到数据之后,不能直接修改父组件的数据。会报错。
当需要修改props
的时候应该在父组件修改。父组件修改后子组件会同步渲染。
如果子组件内一定要修改props
的话推荐使用 computed
,计算一个新属性给子组件使用,而不是直接修改。
// 父组件
<template>
<child :msg="msg"></child>
</template>
// 子组件
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
// 写法一 用数组接收
props:['msg'],
// 写法二 用对象接收
props: {
msg: String
},
// 写法三 用对象接收,可以限定接收的数据类型、设置默认值、验证等
props:{
msg:{
type:String,
default:'这是默认数据',
// 必传
required: true
}
},
props: {
user:{
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: () => return {},
// 自定义验证
validator: () => {
return true/false
}
}
}
}
</script>
$emit / v-on
我们知道Vue
是单向数据流,父组件传递给子组件的数据是不能直接修改的。
所以子组件想要修改父组件数据的时候需要先暴露事件并传递值给父组件,父组件监听该事件来完成值的修改。
// 子组件
<template>
<div>
<div>{{ msg }}</div>
<button @click="handleClick">change</button>
</div>
</template>
<script>
export default {
props: ["msg"],
methods: {
handleClick() {
const newMsg = "我被修改了";
this.$emit("changeMsg", newMsg);
},
},
};
</script>
// 父组件
<template>
<child :msg="msg" v-on:changeMsg="handleChangeMsg" ></child>
</template>
<script>
export default {
data() {
return {msg: "我会传递给子组件"}
},
methods:{
handleChangeMsg(newMsg){
this.msg = newMsg
}
}
}
</script>
.sync
需要修改父组件传递过来的属性的值,每次都需要暴露一个方法过去,然后在父组件监听然后再修改,是不是感觉特别麻烦。
.sync
就是用来简化这一过程的。
// 子组件
<template>
<div>
<div>{{ msg }}</div>
<button @click="handleClick">change</button>
</div>
</template>
<script>
export default {
props: ["msg"],
methods: {
handleClick() {
const newMsg = "我被修改了";
this.$emit("update:msg", newMsg);
},
},
};
</script>
// 父组件
<template>
<child :msg.sync="msg"></child>
</template>
<script>
export default {
data() {
return {msg: "我会传递给子组件"}
},
}
</script>
父组件不需要再监听事件然后修改值了。
父组件只需要在传递属性的时候加上.sync
修饰符,然后子组件再修改完值的时候暴露update:属性名
的事件就可以啦。
v-model
.sync
我们知道,是用来简化数据修改的,那还有没有更简单的方法呢?有,那就是v-model
v-model
默认传递名为value的属性,并自动监听input
事件。
我们来看个例子
// 子组件
<template>
<div>
<input type="text" :value="value" @input="handleInput" />
</div>
</template>
<script>
export default {
props: {
value: String,
},
methods: {
handleInput(e) {
this.$emit("input", e.target.value);
},
},
};
</script>
// 父组件
<template>
<div>{{msg}}</div>
<child v-model="msg"></child>
</template>
<script>
export default {
data() {
return {msg: "我会传递给子组件"}
},
}
在上面的例子中,我们input
框默认值是我会传递给子组件
,当我们修改input
框值的时候父组件值也会变。这其实就是双向绑定。
但有时候我们不可能就一直传递属性为value
,然后监听input
事件,这显示不满足我们的需求。
需要修改属性和事件怎么办呢?这就需要用到我们的model
参数了。该属性定义在子组件。包含两个属性,prop
用来定义属性名,event
用来定义事件名。
比如我们想传递过来的属性名为customValue
,监听的事件为customInput
。
// 子组件
<template>
<div>
<input type="text" :value="customValue" @input="handleInput" />
</div>
</template>
<script>
export default {
model: {
prop: "customValue",
event: "customInput",
},
props: {
customValue: String,
},
methods: {
handleInput(e) {
this.$emit("customInput", e.target.value);
},
},
};
</script>
// 父组件
<template>
<div>{{msg}}</div>
<child v-model="msg"></child>
</template>
<script>
export default {
data() {
return {msg: "我会传递给子组件"}
},
}
其它的事件或者属性类似,这里笔者就不再赘述了。但是需要注意一个组件只能使用一个v-model
。
ref
如果需要直接操作子组件我们就可以使用ref
了。
ref
如果在普通的DOM元素上,引用指向的就是该DOM元素;
如果在子组件上,引用的指向就是子组件实例。
// 子组件
<template>
<div>
<div>{{ msg }}</div>
</div>
</template>
<script>
export default {
props: ["msg"],
data() {
return {
name: "child6",
};
},
methods: {
say() {
console.log("say child6");
},
},
computed: {
title() {
return this.name + "title";
},
},
};
</script>
// 父组件
<template>
<child :msg="msg" ref="childRef"></child>
</template>
<script>
export default {
data() {
return {msg: "我会传递给子组件"}
},
mounted() {
// 通过$refs获取子组件
console.log(this.$refs.childRef)
// 接下来我们就可以直接操作子组件了
console.log(this.$refs.childRef.$data)
console.log(this.$refs.childRef.$props)
console.log(this.$refs.childRef.$el)
this.$refs.childRef.say()
console.log(this.$refs.childRef.name)
console.log(this.$refs.childRef.title)
// ...
}
}
需要注意,如果ref
定义在循环中,this.$refs.xxx
会是一个数组。
$children / $parent
类似ref,我们可以通过$children / $parent
直接获取到子组件或父组件。
$children
:获取到一个包含所有子组件(不包含孙子组件)的 VueComponent 对象数组,可以直接拿到子组件中所有数据和方法。
$parent
:获取到一个父节点的 VueComponent 对象,同样包含父节点中所有数据和方法。
// 父组件
export default{
mounted(){
this.$children[0].someMethod() // 调用第一个子组件的someMethod
this.$children[0].name // 获取第一个子组件中的name属性
}
}
// 子组件
export default{
mounted(){
this.$parent.someMethod() // 调用父组件的someMethod
this.$parent.name // 获取父组件中的name属性
}
}
$root
和$children / $parent
相似,$root
可以获取到根实例里。
也就是我们main.js
创建的Vue
实例
new Vue({
router,
store,
render: (h) => h(App),
data: {
name: "根组件",
},
}).$mount("#app");
provide / inject
provide / inject
需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React
,这与 React
的context
特性很相似。
provide
选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。
inject
选项应该是:
- 一个字符串数组,或
一个对象,对象的 key 是本地的绑定名,value 是:
- 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
一个对象,该对象的:
from
property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)default
property 是降级情况下使用的 value
提示:provide
和inject
绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 后代组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
在 2.5.0+ 的注入可以通过设置默认值使其变成可选项:
const Child = {
inject: {
foo: { default: 'foo' }
}
}
如果它需要从一个不同名字的 property 注入,则使用 from
来表示其源 property:
const Child = {
inject: {
foo: {
from: 'bar',
default: 'foo'
}
}
}
与 prop 的默认值类似,你需要对非原始值使用一个工厂方法:
const Child = {
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3]
}
}
}
$attrs / $listeners
多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用$attrs / $listeners
,比如父组件向孙子组件传递数据时。
$attrs
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class
和 style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和 style
除外)。
$listeners
包含了父作用域中的 (不含 .native
修饰器的) v-on
事件监听器。
可能有些小伙伴不懂,下面举个例子就明白了
//子组件
props: ['msg'],
created() {
// 获取没在props中定义的但是传递过来的属性,除去 class、style
console.log(this.$attrs); // {id: 'childId', name: 'randy'}
// 获取除.native修饰的原生事件
console.log(this.$listeners); // {customChange(){}}
// 如果单纯只传递属性给子组件,不需要处理,我们可以直接传递$attrs、$listeners
},
// 父组件
<template>
<child :msg="msg" @click.native="handleClick" @customChange="handleChange" class="child" id="childId"></child>
</template>
<script>
export default {
data() {
return {msg: "我会传递给子组件", name: 'randy'}
},
}
slot
slot
可以用在父组件直接向子组件传递内容。我们在子组件标签内的内容都可以传递过去。
没命名的slot
默认名称是default
。
// 父组件
<h3>默认插槽 default</h3>
<Slot1>
<div>slot1</div>
<div>哈哈</div>
</Slot1>
<h3>老具名插槽</h3>
<Slot1>
<div>slot传递过来的 2.6已被废弃但是还能使用 vue3彻底移除</div>
<div slot="header">header</div>
<div slot="footer">footer</div>
<div slot="content">content</div>
</Slot1>
<h3>新具名插槽</h3>
<Slot1>
<template v-slot:header>header</template>
<template v-slot:footer>footer</template>
<template v-slot:content>content</template>
</Slot1>
<h3>新具名插槽缩写 #</h3>
<Slot1>
<div>v-slot传递过来的</div>
<template #header>header2</template>
<template #footer>footer2</template>
<template #content>content2</template>
</Slot1>
// 子组件 Slot1
<!-- 默认插槽 -->
<slot>我是后备内容,没有传递的时候展示</slot>
<slot name="header"></slot>
<slot name="content"></slot>
<slot name="footer"></slot>
不单父组件传递内容给子组件,子组件还可以通过作用域插槽传递数据给父组件。
// 父组件
<h3>老作用域插槽</h3>
<!-- scope 被 2.5.0 新增的 slot-scope 取代 -->
<!-- 除了 scope 只可以用于 <template> 元素,其它和 slot-scope 都相同。 -->
<Slot2>
<template slot="main" slot-scope="slotProps">
<div>user name: {{ slotProps.user.name }}</div>
<div>user age: {{ slotProps.user.age }}</div>
<div></div>
</template>
<template slot-scope="slotProps">
<div>user name: {{ slotProps.user.name }}</div>
<div>user age: {{ slotProps.user.age }}</div>
</template>
<template slot="footer" slot-scope="{ user: { name, age } }">
<div>user name: {{ name }}</div>
<div>user age: {{ age }}</div>
</template>
</Slot2>
<h3>新作用域插槽</h3>
<Slot2>
<template v-slot:main="slotProps">
<div>user name: {{ slotProps.user.name }}</div>
<div>user age: {{ slotProps.user.age }}</div>
<div></div>
</template>
<template v-slot:default="slotProps">
<div>user name: {{ slotProps.user.name }}</div>
<div>user age: {{ slotProps.user.age }}</div>
</template>
<template v-slot:footer="{ user: { name, age } }">
<div>user name: {{ name }}</div>
<div>user age: {{ age }}</div>
</template>
</Slot2>
// 子组件 Slot2
<template>
<slot v-bind:user="user1"> </slot>
<slot name="main" v-bind:user="user2"> </slot>
<slot name="footer" :user="user3"> </slot>
</template>
export default {
data() {
return {
user1: {
name: "randy",
age: 27,
},
user2: {
name: "demi",
age: 24,
},
user3: {
name: "jack",
age: 21,
},
};
},
};
顺便说一说this.$slots
和this.$scopedSlots
。
this.$slots
this.$slots
用来访问被插槽分发的内容。每个具名插槽有其相应的 property (例如:v-slot:foo
中的内容将会在 vm.$slots.foo
中被找到)。default
property 包括了所有没有被包含在具名插槽中的节点,或 v-slot:default
的内容。
每个属性包含一个VNode
的数组。
请注意插槽不是响应性的。如果你需要一个组件可以在被传入的数据发生变化时重渲染,我们建议改变策略,依赖诸如 props
或 data
等响应性实例选项。
this.$scopedSlots
this.$scopedSlots
用来访问作用域插槽。对于包括 默认 slot
在内的每一个插槽,该对象都包含一个返回相应 VNode 的函数。
我们通过调用对应的方法,传递响应的数据,就能得到VNode
数组。类似this.$slots
的返回值。
console.log(
this.$scopedSlots.default({
user: this.user1,
})
);
就类似于前面的,不过有$scopedSlots
,$slots
就不会有值了。
this.$slots.default
EventBus
EventBus
主要利用了Vue
实例 $on、$once、$off、$emit
这四个方法来进行数据的传递。
首先我们定义一个EventBus
// Bus.js
import Vue from "vue"
export default new Vue()
import Bus from "./Bus.js"
// 暴露事件,传递数据
Bus.$emit("sendMsg", "这是要向外部发送的数据")
// 监听事件,进行处理
Bus.$on("sendMsg", data => {
console.log("这是接收到的数据:", data)
})
// 监听事件,进行处理,但是只监听一次,后面会自动取消监听
Bus.$once("sendMsg", data => {
console.log("这是接收到的数据:", data)
})
// 取消监听
Bus.$off("sendMsg")
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
这个笔者就不多说了,大家应该都知道,不知道的可以查看Vue2版本官方文档自行学习。
Vue3
Vue3
组件通讯有9中
props
emit
ref / expose
attrs
v-model
provide / inject
mitt
Vuex
Pinia
应用场景
Vue3
组件通信方式虽然有很多种,但是不同方式有不同的应用场景。
父子组件通信
props
emit
attrs
ref / expose
v-model
兄弟组件通信
mitt
Vuex
Pinia
跨层级组件通信
provide/inject
attrs
mitt
Vuex
Pinia
具体使用
props
props
用于父组件向子组件传值,使用方式和vue2
没什么差别。
// 父组件
<child :msg="msg"></child>
// 子组件
<template>
<div>{{ msg }}</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
props: {
msg: String,
},
setup() {
return {};
},
});
</script>
// 父组件
emit
emit
和vue2
的$emit
使用方式是一样的,但是需要注意:
vue3
中,emit
是在setup
函数的第二个参数上。vue3
中,子组件暴露的方法需要在emits
定义。
// 父组件
<template>
<child :msg="msg" @changeMsg="handleChangeMsg"></child>
</template>
<script>
import { defineComponent, ref } from "vue";
export default defineComponent({
setup(props, { emit }) {
const msg = ref("message");
const handleChangeMsg = (newValue) => {
msg.value = newValue;
};
return { msg, handleChangeMsg };
},
});
</script>
// 子组件
<template>
<div class="">
<div>{{ msg }}</div>
<button @click="handelClick">changeMsg</button>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
props: {
msg: String,
},
emits: { changeMsg: null },
setup(props, { emit }) {
const handelClick = () => {
emit("changeMsg", "我被改变了");
};
return { handelClick };
},
});
</script>
ref / expose
vue3
中定义ref
需要使用ref()
定义。
使用的时候需要注意:
- 当组件没定义
expose
暴露内容的时候,通过ref获取到的就是组件自身的内容,也就是setup
函数return
的内容。 - 当定义了
expose
暴露内容的时候,通过ref获取到的就是组件expose
暴露内容,并且setup
函数return
的内容会失效,也就是会被覆盖。
// 父组件
<template>
<child :msg="msg" ref="ref1"></child>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
export default defineComponent({
setup(props, { emit }) {
const msg = ref("message");
const ref1 = ref(null)
onMounted(() => {
console.log(ref1.value)
console.log(ref1.value.name)
console.log(ref1.value.user)
ref1.value.say()
})
return { msg, ref1 };
},
});
</script>
// 子组件
<template>
<div class="">{{ msg }}</div>
</template>
<script>
import { defineComponent, ref, reactive } from "vue";
export default defineComponent({
props: ["msg"],
emits: {},
setup(props, { expose }) {
const say = () => {
console.log("RefChild say");
};
const name = ref("RefChild");
const user = reactive({ name: "randy", age: 27 });
// 如果定义了会覆盖return中的内容
// expose({
// user,
// });
return {
name,
user,
say,
};
},
});
</script>
attrs
在vue2
中,我们知道this.$attrs
包含了父作用域中不作为 prop
被识别 (且获取) 的 attribute
绑定 (class
和 style
除外)。
在vue3
中有了更改,attrs
被转移到setup
的第二个参数context
上,context.attrs
。并且class
和 style
也都不再忽略了。也就是说class
和 style
也会在attrs
里面。
使用方式和vue2
还是一样的,多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用attrs
,比如父组件向孙子组件传递数据时。
//子组件
props: ['msg'],
setup(props, {attrs}) {
// 获取没在props中定义的但是传递过来的属性,包括 class、style
console.log(attrs); // {id: 'childId', name: 'randy', customChange(){}, class: 'child'}
// 如果单纯只传递属性给子组件,不需要处理,我们可以直接传递attrs
},
// 父组件
<template>
<child :msg="msg" @customChange="handleChange" class="child" id="childId"></child>
</template>
<script>
export default {
data() {
return {msg: "我会传递给子组件", name: 'randy'}
},
}
v-model
vue3
的v-model
我们只需要注意两点:
- 在
vue2
中,一个组件只能绑定一个v-model
,但是vue3
中可以绑定多个。 - 在
vue2
中如果不指明model
属性,那么它的值默认是value
,事件默认是input
事件。但是在vue3
中不需要model
属性了,并且它的值默认是modelValue
,事件默认是update:modelValue
事件。
// 父组件
<Child1 v-model="name1" />
<!--比使用默认名,自定义名字为name-->
<!-- <Child1 v-model:name="name1" /> -->
// 子组件
<template>
<div class="child1">
<button @click="changeName">改变值</button>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
props: {
modelValue: String,
// name: String,
},
setup(props, context) {
const changeName = () => {
context.emit("update:modelValue", "demi");
// context.emit("update:name", "demi");
};
return {
changeName,
};
},
});
</script>
如果不想用默认名,我们可以在v-model:
后面加上自定义的名字。
provide / inject
provide / inject
需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
原理和vue2
一样,可能就是方法有所改变。
// 父组件
<script setup>
import { provide } from "vue"
provide("name", "沐华")
</script>
// 子组件
<script setup>
import { inject } from "vue"
const name = inject("name")
console.log(name) // 沐华
</script>
上面的写法是setup
的语法糖写法,更简洁,小伙伴不要陌生哦。
mitt
我们知道vue3
去除了$on $once $off
方法,所以没办法再使用 EventBus
跨组件通信了。但是现在有了一个替代的方案 mitt.js,原理还是 EventBus
。
先安装 npm i mitt -S
然后像以前封装 bus 一样,封装一下
import mitt from 'mitt'
const mitt = mitt()
export default mitt
然后两个组件之间通信的使用
// 组件 A
<script setup>
import mitt from './mitt'
const handleClick = () => {
mitt.emit('handleChange')
}
</script>
// 组件 B
<script setup>
import mitt from './mitt'
import { onUnmounted } from 'vue'
const someMethed = () => { ... }
mitt.on('handleChange',someMethed)
onUnmounted(()=>{
mitt.off('handleChange',someMethed)
})
</script>
Vuex
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
这个笔者就不多说了,大家应该都知道,不知道的可以查看Vue3版本官方文档自行学习。
Pinia
如果你之前使用过 vuex
进行状态管理的话,那么 pinia
就是一个类似的插件。它是最新一代的轻量级状态管理插件。按照尤雨溪的说法,vuex
将不再接受新的功能,建议将 Pinia
用于新的项目。
这个笔者就不多说了,大家可以自行查看官方文档进行学习。
React
React
组件通讯有7中
props
context
ref
children
EventEmitter
Redux
Mobx
React
组件通信方式虽然有很多种,但是不同方式有不同的应用场景。
应用场景
父子组件通信
props
context
ref
children
兄弟组件通信
EventEmitter
Redux
跨层级组件通信
context
EventEmitter
Redux
具体使用
props
props
用于父组件向子组件传值,可以传递属性或者方法甚至组件。
// 父组件
import React from "react";
import Children3 from "../components/Children3";
class Parent1 extends React.Component {
constructor() {
super();
this.state = {
name: "randy",
};
}
changeName = () => {
this.setState({
name: "demi",
});
};
render() {
return (
<div>
<Children3
name={this.state.name}
changeName={this.changeName}
></Children3>
</div>
);
}
}
export default Parent1;
// 类子组件
import React, { Component } from "react";
export class Children3 extends Component {
render() {
return (
<div>
<div>{this.props.name}</div>
<button onClick={this.props.changeName}>changeName</button>
</div>
);
}
}
export default Children3;
// 函数子组件
export default function Children3(props) {
return (
<div>
<div>{props.name}</div>
<button onClick={props.changeName}>changeName</button>
</div>
);
}
我们可以发现,在react
中子组件向父组件传递消息是通过回调函数的方式,也就是父组件把方法以props
的形式传递给子组件来调用。这一块和vue
还是有很大差别的。
context
context
上下文,用于父子或子孙组件之间通信,类似 vue
的 provide/inject
// 父组件
import React from "react";
// 创建带默认值的 context
const UserContext = React.createContext("randy");
// 传递值
<UserContext.Provider value="demi">
<Context1></Context1>
</UserContext.Provider>
// 子组件/孙组件
// 类组件方式1
class Context1 extends React.Component {
static contextType = UserContext;
// 获取值
render() {
return (<div>{this.context}</div>)
}
}
// 类组件方式2
Context1.contextType = UserContext;
// 类组件方式3
<UserContext.Consumer>
{ name => (<div>{name}</div>)}
</UserContext.Consumer>
// 函数式组件方式1
import { useContext } from "react";
function Context1() {
const context = useContext(UserContext)
return (<div>{context}</div>)
}
// 函数式组件方式2
function Context1() {
return (
<UserContext.Consumer>
{ name => (<div>{name}</div>)}
</UserContext.Consumer>
)
}
可以看到 context
使用方式有很多种。传递单个context
使用哪种方式都可以,但是当传递多个context
的时候只能使用Consumer
的方式。使用Consumer
嵌套可以获取多个context
。这里笔者就不再细说了。
ref
通过ref
可以获取到子组件实例,然后使用组件上的属性或方法,这个和vue
是类似的。
// 类 父组件 使用createRef创建
import React from "react";
class RefTest extends React.Component {
constructor() {
super();
this.ref1 = React.createRef();
}
componentDidMount() {
// 获取的是组件
console.log(this.ref1.current);
// 回调的方式不需要.current
console.log(this.ref2);
}
render() {
return (
<div>
<Ref2 ref={this.ref1}></Ref2>
<Ref3 ref={(el) => (this.ref2 = el)}></Ref3>
</div>
);
}
}
export default RefTest;
// 函数 父组件 使用useRef创建
import { useRef } from "react";
const RefTest2 = () => {
const ref1 = useRef();
let ref3 = null;
const outputRefs = () => {
console.log(ref2.current);
console.log(ref3);
};
return (
<div>
<Ref2 ref={ref2}></Ref2>
<Ref3 ref={(el) => (ref3 = el)}></Ref3>
<button onClick={outputRefs}>输出refs</button>
</div>
);
};
export default RefTest2;
创建ref
的方式有createRef useRef
和回调函数的方式。但是有一点我们需要注意,你不能在函数组件上使用 ref 属性,因为它们没有实例。如果想要获取函数式组件的ref,需要使用forwardRef
并配合useImperativeHandle
才能使用。这里笔者就不细说了。
children
在react
中,写在组件中的内容都是children
,类似vue
里面的插槽slot
。
根据插入内容的多少children
可能是一个数组也可能是一个对象。当只有一个内容的时候是一个对象,当有多个内容的时候是一个数组。
// 父组件
<Children1>
<div>child内容1</div>
<div>child内容2</div>
</Children1>
// 子组件
// 通过 this.props.children 获取内容
// 上面传递了两个内容 所以this.props.children是一个数组。
当props
和插槽同时存在的时候,插槽为准。
// 父组件
<Children2 children="哈哈">我会被覆盖吗</Children2>
// 子组件
this.props.children 等于 我会被覆盖吗
EventEmitter
react
并不支持自定义事件,所以没有vue的$on $off $emit $once
等方法。所以需要借助外部插件。
EventEmitter 就是一种,类似mitt
。
npm install events
const EventEmitter = require('events')
const ee = new EventEmitter()
ee.on('message', function (text) {
console.log(text)
})
ee.emit('message', 'hello world')
Redux
Redux
是一个状态管理工具,类似 vuex
,可以自行查看官方文档学习。
Mobx
Mobx
是一个状态管理工具,类似 Redux
,可以自行查看官方文档学习。
对比总结
相同点
- 都支持父子、兄弟、跨层级通信。
- 都推崇单向数据流,子组件不能直接修改父组件传递过来的数据。
- 都支持自定义事件来通信,比如
Vue
的EventBus
、mitt
,React
的EventEmitter
。 - 都支持通过
ref
获取子组件实例来操作子组件。 - 都支持通过插槽的方式直接传递数据。
- 都有各自的状态管理库,
Vue
的vuex、pinia
,React
的redux、mobx
。
不同点
- 在
Vue
中,子组件如果需要修改父组件数据,需要暴露方法,父组件监听该方法,然后修改数据。但是在React
中,是通过属性传递回调函数给子组件调用的方式来修改的。 React
的ref相较于Vue
使用方式更多并且更强大。可以通过创建和回调的方式创建ref。并且在获取元素方面,不但能获取子组件,还能通过ref转发获取到子组件的DOM
元素。React
的插槽相较于Vue
稍微逊色一点点,没有具名插槽、作用域插槽那么多种类,都是通过children
作为插槽传递内容进行通讯。
系列文章
Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)
Vue和React对比学习之组件传值(Vue2 12种、Vue3 9种、React 7种)
Vue和React对比学习之路由(Vue-Router、React-Router)
Vue和React对比学习之状态管理 (Vuex和Redux)
Vue和React对比学习之条件判断、循环、计算属性、属性监听
后记
感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!