Vuejs vs Reactjs:组件之间如何通信
不管是在 vue
还是在 react
中,父组件是通过 props
向子组件传递数据,但 props
的特性是单向数据流,一般不能在子组件中直接修改 props
的值,那如果要修改呢?该如何处理呢?
vue
中组件的通信
在 vue
中,我们定义的父子组件如下
父组件
<template>
<div>
<div>父组件</div>
<div class="parent">
父组件中定义的信息: {{ msg }}
</div>
<Child :msg="msg" />
</div>
</template>
<script setup>
import {ref} from 'vue'
import Child from './Child.vue';
const msg = ref('msg at parent')
</script>
子组件
<template>
<div>
<div>子组件</div>
<div class="child">
<div>
来自父组件的信息: {{ msg }}
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
msg: String
})
</script>
子组件通过 :msg
属性接收父组件的数据
<Child :msg="msg" />
但是不能在子组件中直接修改接收到的值
props.msg = "new msg" ,无法通过这种方式修改,这种方式不会报错,但是修改之后不会生效,在控制台中有警告信息。那如果要修改接收到的值该如何处理?
定义一个本地变量接收者值
const msg1 = ref(props.msg)
可以修改 msg1
的值,但 props.msg
的值还是无法修改
通过 props
或 emit
在父组件中修改 msg
的值,对应的子组件的值也会跟着修改,那在子组件中如何修改父组件中的值呢?
1. 通过 `props` 传递一个 `Function` 方法\
在父组件中定义一个方法修改 `msg` 的值,子组件中用一个属性接收这个方法\
**父组件**
```vue
<Child :msg="msg" :chg-msg="chgMsg" />
const chgMsg = () => {
msg.value = "new msg"
}
```
在子组件中接收这个方法,并把这个方法绑定到一个 `button` 上\
**子组件**
```vue
const props = defineProps({
msg: String,
chgMsg: Function
})
<button @click="chgMsg">修改信息</button>
```
通过这种方式,相当于子组件调用父组件的方法修改 `msg` 的值,子组件的值也会跟着改变
2. 通过自定义事件
在子组件中定义一个事件\
**子组件**
```vue
const emit = defineEmits(['emitChg'])
const emitChg = () => {
emit('emitChg')
}
<button @click="emitChg">修改信息</button>
```
**父组件**
```vue
<Child :msg="msg" @emit-chg="chgMsg" />
```
**请注意这两种方式的区别**
```vue
<!--传递一个props-->
<Child :msg="msg" :chg-msg="chgMsg" />
<!--自定义一个事件-->
<Child :msg="msg" @emit-chg="chgMsg" />
```
react
中组件的通信
在 react
中,只能通过在 props
传递方法的方式来处理,在react
中不支持自定义事件的方式
父组件
const Parent = () => {
const [msg, setMsg] = useState('msg at parent')
const chgMsg = () => {
setMsg('new msg')
}
return (
<div>
<div>父组件</div>
<div>在父组件中定义的信息: {msg}</div>
<Child msg={msg} chgMsg={chgMsg} />
</div>
)
}
子组件
const Child = ({ msg, chgMsg }) => {
return (
<div>
<div>子组件</div>
<div>来自父组件的信息: {msg}</div>
<button onClick={chgMsg}>修改信息</button>
</div>
)
}
嵌套的组件之间也通过这种
props
的方式进行通信,兄弟组件之间也可以通过这种方式,但需要通过他们的共同的父组件然后再通过props
的方式进行通信
Prop 逐级透传问题
通常情况下,我们需要通过父组件将数据传递到子组件,如果嵌套很深,就需要把 props
像链条一样传递到最后的组件
vue
中使用 provide
和 inject
provide
和 inject
可以帮助我们解决这一问题。一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
provide
和 inject
的基本的使用方式
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
<script setup>
import { inject } from 'vue'
const message = inject('message')
// message 的值是 hello
</script>
配合响应式数据,可以实现一个小型的响应式的数据仓库 store
- 定义
store
store.js 使用vue
的响应式函数reactive
import { reactive, readonly } from "vue";
const initState = reactive({
count: 0,
})
export const store = {
state: readonly(initState),
increment: (v) => {
initState.count += v
},
decrement: (v) => {
initState.count -= v
}
}
- 提供依赖
provide
main.js ,可以在一个组件中提供依赖,我们还可以在整个应用层面提供依赖:
import {store} from './store'
const app = createApp(App)
app.provide('store', store)
app.mount('#app')
- 注入依赖
inject
DeepChild.vue , 就可以在应用中的任何一个组件中使用
const {state, increment} = inject('store') // 可以使用 析构
// 或者
const store = inject('store')
// 在模板中使用
<div>{{ state.count }}</div>
<button @click="() => increment(2)">增加</button>
这种自定义 store
的形式,一般用在小型的项目中,如果是大型的项目请使用第三方库 pinia 或 vuex,以获取更好的性能
在 react
中也有同样的问题,在 react
中使用 Reducer
和 Context
,来处理这种深度嵌套通信问题
react
使用 Reducer
和 Context
- 使用
useReducer
提取数据操作逻辑 CounterReducer.js
export const initState = {
count: 0
}
export function counterReducer(state, action) {
switch(action.type) {
case 'increment':
return {
count: state.count + action.payload
}
case 'decrement':
return {
count: state.count - action.payload
}
}
}
父组件 Parent.jsx
import { initState, counterReducer } from './CounterReducer'
import { useReducer, useCallback } from 'react'
import Counter from './Counter'
<div>{state.count}</div>
<Counter
increment={() => dispatch({ type: 'increment', payload: 2 })}
decrement={() => dispatch({ type: 'decrement', payload: 1 })}
/>
子组件 Counter.jsx
const Counter = ({increment, decrement}) => {
return (
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
- 结合
Context
,解决props
透传问题
- 定义
Context
CounterContext.js
import { createContext } from "react";
export const CounterContext = createContext(null)
- 在
App.jsx
中,提供数据
import { CounterContext } from './CouterContext'
import { initState, counterReducer } from './CounterReducer'
import { useCallback } from 'react'
import { useReducer } from 'react'
function App() {
const reducer = useCallback(counterReducer, [])
const [state, dispatch] = useReducer(reducer, initState)
return (
<CounterContext.Provider value={{state: state, dispatch: dispatch}}>
<Parent />
</CounterContext.Provider>
)
}
- 任意深度嵌套的组件中使用 DeepCounter.jsx
import React from 'react'
import { useContext } from 'react'
import {CounterContext} from './CouterContext'
const DeepCounter = () => {
const {state, dispatch} = useContext(CounterContext)
return (
<div>
<div>{state.count}</div>
<button onClick={() => dispatch({ type: 'increment', payload: 2})}>+</button>
</div>
)
}
使用组件实例访问数据
vue3
中使用 ref
和 defineExpose
访问组件暴露的属性和方法
Expose.vue,暴露组件的 increment
方法
<template>
<div>
expose: {{ count }}
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0)
const increment = () => {
count.value += 1
}
defineExpose({
increment
})
</script>
在其他地方可以通过这个组件的实例调用这个方法
Parent.vue , 通过组件实例调用
<Expose ref="expose" />
const expose = ref(null)
const onIncrement = () => {
expose.value.increment() // 通过组件实例调用组件内部的方法
}
在 react
可以使用 ref
获取 DOM 节点的实例
ref
配合useRef
可以获取到DOM
节点的实例,然后就可以调DOM
节点的方法和属性 MyInput.jsx,
import { useRef } from 'react'
const MyInput = () => {
const inputRef = useRef(null)
const handleClick = () => {
inputRef.current.focus()
console.log(inputRef.current.value)
}
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleClick}>value</button>
</div>
)
}
- 配合
forwardRef
可以获取其他组件的DOM
节点的实例
MyInput.jsx 使用 forwardRef
封装一下, 然后在父组件中可以通过 useRef
获取到组件内部的 DOM
节点的实例,然后就获取 DOM
节点的方法和属性
const MyInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} />;
});
在 父组件中 Parent.jsx
const inputRef = useRef(null)
const handleClick = () => {
inputRef.current.focus()
console.log(inputRef.current.value)
}
<MyInput ref={inputRef} />
<button onClick={handleClick}> focus</button>
其他的通信方式
这里只是列举了基本的通信方式,还其他的通信方式,如在 vue
中还可以通过 eventbus
、 v-model
slot
的方式,还可以通过状态管理框架 pinia
vuex
,在 react
中也可以通过第三方的的状态管理框架 redux
zustand
等进行通信
转载自:https://juejin.cn/post/7249193764886708283