likes
comments
collection
share

Vuejs vs Reactjs:组件之间如何通信

作者站长头像
站长
· 阅读数 21

不管是在 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 的值还是无法修改

通过 propsemit

在父组件中修改 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 像链条一样传递到最后的组件

Vuejs vs Reactjs:组件之间如何通信

vue中使用 provideinject

provide 和 inject 可以帮助我们解决这一问题。一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。

Vuejs vs Reactjs:组件之间如何通信

provideinject 的基本的使用方式

<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 的形式,一般用在小型的项目中,如果是大型的项目请使用第三方库 piniavuex,以获取更好的性能

react 中也有同样的问题,在 react 中使用 ReducerContext,来处理这种深度嵌套通信问题

react 使用 ReducerContext

  • 使用 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透传问题
  1. 定义 Context CounterContext.js
import { createContext } from "react";
export const CounterContext = createContext(null)
  1. 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>
  )
}
  1. 任意深度嵌套的组件中使用 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 中使用 refdefineExpose 访问组件暴露的属性和方法

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 节点的实例

  1. 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>
  )
}

  1. 配合 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 中还可以通过 eventbusv-model slot的方式,还可以通过状态管理框架 pinia vuex,在 react 中也可以通过第三方的的状态管理框架 redux zustand等进行通信