Vue和React对比学习之组件传值(Vue2 12种、Vue3 9种、React 7种)

简介: 组件传值

简介

VueReact是目前前端最火的两个框架。不管是面试还是工作可以说是前端开发者们都必须掌握的。

今天我们通过对比的方式来学习VueReact的组件传值这一部分。

本文首先讲述Vue2Vue3React的组件传值方式,然后具体介绍了每种传值的应用场景以及具体的使用。最后对比总结了VueReact在组件传值这部分的相同点和不同点。

希望通过这种对比方式的学习能让我们学习的时候印象更深刻,希望能够帮助到大家。

IMG_0387.jpeg

Vue2

Vue2 组件通信共有12种

  1. props
  2. $emit / v-on
  3. .sync
  4. v-model
  5. ref
  6. $children / $parent
  7. $attrs / $listeners
  8. provide / inject
  9. EventBus
  10. Vuex
  11. $root
  12. 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,这与 Reactcontext特性很相似。

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.$slotsthis.$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中

  1. props
  2. emit
  3. ref / expose
  4. attrs
  5. v-model
  6. provide / inject
  7. mitt
  8. Vuex
  9. 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

emitvue2$emit使用方式是一样的,但是需要注意:

  1. vue3中,emit是在setup函数的第二个参数上。
  2. 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()定义。

使用的时候需要注意:

  1. 当组件没定义expose暴露内容的时候,通过ref获取到的就是组件自身的内容,也就是setup函数return的内容。
  2. 当定义了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

vue3v-model我们只需要注意两点:

  1. vue2中,一个组件只能绑定一个v-model,但是vue3中可以绑定多个。
  2. 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中

  1. props
  2. context
  3. ref
  4. children
  5. EventEmitter
  6. Redux
  7. Mobx

image.png

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上下文,用于父子或子孙组件之间通信,类似 vueprovide/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,可以自行查看官方文档学习。

对比总结

image.png

相同点

  1. 都支持父子、兄弟、跨层级通信。
  2. 都推崇单向数据流,子组件不能直接修改父组件传递过来的数据。
  3. 都支持自定义事件来通信,比如VueEventBusmittReactEventEmitter
  4. 都支持通过ref获取子组件实例来操作子组件。
  5. 都支持通过插槽的方式直接传递数据。
  6. 都有各自的状态管理库,Vuevuex、piniaReactredux、mobx

不同点

  1. Vue中,子组件如果需要修改父组件数据,需要暴露方法,父组件监听该方法,然后修改数据。但是在React中,是通过属性传递回调函数给子组件调用的方式来修改的。
  2. React的ref相较于Vue使用方式更多并且更强大。可以通过创建和回调的方式创建ref。并且在获取元素方面,不但能获取子组件,还能通过ref转发获取到子组件的DOM元素。
  3. React的插槽相较于Vue稍微逊色一点点,没有具名插槽、作用域插槽那么多种类,都是通过children作为插槽传递内容进行通讯。

系列文章

Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)

Vue和React对比学习之组件传值(Vue2 12种、Vue3 9种、React 7种)

Vue和React对比学习之Style样式

Vue和React对比学习之Ref和Slot

Vue和React对比学习之Hooks

Vue和React对比学习之路由(Vue-Router、React-Router)

Vue和React对比学习之状态管理 (Vuex和Redux)

Vue和React对比学习之条件判断、循环、计算属性、属性监听

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!

相关文章
|
2月前
|
缓存 JavaScript PHP
斩获开发者口碑!SnowAdmin:基于 Vue3 的高颜值后台管理系统,3 步极速上手!
SnowAdmin 是一款基于 Vue3/TypeScript/Arco Design 的开源后台管理框架,以“清新优雅、开箱即用”为核心设计理念。提供角色权限精细化管理、多主题与暗黑模式切换、动态路由与页面缓存等功能,支持代码规范自动化校验及丰富组件库。通过模块化设计与前沿技术栈(Vite5/Pinia),显著提升开发效率,适合团队协作与长期维护。项目地址:[GitHub](https://github.com/WANG-Fan0912/SnowAdmin)。
379 5
|
2月前
|
缓存 前端开发 数据安全/隐私保护
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
166 68
|
2月前
|
缓存 前端开发 Java
在 React 中,组合组件和高阶组件在性能方面有何区别?
在 React 中,组合组件和高阶组件在性能方面有何区别?
147 67
|
2月前
|
前端开发 JavaScript 安全
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
152 62
|
2月前
|
JavaScript API 容器
Vue 3 中的 nextTick 使用详解与实战案例
Vue 3 中的 nextTick 使用详解与实战案例 在 Vue 3 的日常开发中,我们经常需要在数据变化后等待 DOM 更新完成再执行某些操作。此时,nextTick 就成了一个不可或缺的工具。本文将介绍 nextTick 的基本用法,并通过三个实战案例,展示它在表单验证、弹窗动画、自动聚焦等场景中的实际应用。
169 17
|
3月前
|
JavaScript 前端开发 算法
Vue 3 和 Vue 2 的区别及优点
Vue 3 和 Vue 2 的区别及优点
|
3月前
|
存储 JavaScript 前端开发
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
183 6
|
2月前
|
JavaScript 前端开发 API
Vue 2 与 Vue 3 的区别:深度对比与迁移指南
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架,在过去的几年里,Vue 2 一直是前端开发中的重要工具。而 Vue 3 作为其升级版本,带来了许多显著的改进和新特性。在本文中,我们将深入比较 Vue 2 和 Vue 3 的主要区别,帮助开发者更好地理解这两个版本之间的变化,并提供迁移建议。 1. Vue 3 的新特性概述 Vue 3 引入了许多新特性,使得开发体验更加流畅、灵活。以下是 Vue 3 的一些关键改进: 1.1 Composition API Composition API 是 Vue 3 的核心新特性之一。它改变了 Vue 组件的代码结构,使得逻辑组
415 0
|
4月前
|
JavaScript 前端开发 UED
vue2和vue3的响应式原理有何不同?
大家好,我是V哥。本文详细对比了Vue 2与Vue 3的响应式原理:Vue 2基于`Object.defineProperty()`,适合小型项目但存在性能瓶颈;Vue 3采用`Proxy`,大幅优化初始化、更新性能及内存占用,更高效稳定。此外,我建议前端开发者关注鸿蒙趋势,2025年将是国产化替代关键期,推荐《鸿蒙 HarmonyOS 开发之路》卷1助你入行。老项目用Vue 2?不妨升级到Vue 3,提升用户体验!关注V哥爱编程,全栈开发轻松上手。
231 2
|
4月前
|
JavaScript 前端开发 算法
高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图
mermaid是一款非常优秀的基于 JavaScript 的图表绘制工具,可渲染 Markdown 启发的文本定义以动态创建和修改图表。非常适合新手学习或者做一些弱交互且自定义要求不高的图表 除了流程图以外,mermaid还支持序列图、类图、状态图、实体关系图等图表可供探索。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~