Vue组件通讯(一)

393 阅读3分钟

在Vue中它的数据传输方式与React的概念是一样的,都是遵循单向数据流原则。关于单向数据流的概念是指数据只能沿着一个方向流动,通常是从父组件到子组件。这种设计模式的优点是数据流动路径清晰,状态管理简单,容易调试和维护。这里具体可以入门React——项目实战TodoList - 掘金 (juejin.cn)来到这篇文章了解一下。

而正是因为单向数据流的原因,导致了一些通信需求。这里我们模仿一个简单的TodoList来先聊两种方法。

<template>
  <div class="header">
    <input type="text" v-model="newMsg">
    <button @click="add">确定</button>
  </div>

  <div class="body">
    <ul>
      <li v-for="item in list">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const newMsg = ref('')
const list = ref(['html', 'css'])

const add = () => {
  list.value.push(newMsg.value)
}
</script>

下面我们都基于这段代码来解释。

父子通信(props)

这种通信方法是父组件通过props将数据传递给子组件。子组件不能直接修改这些props。这里我们创建一个父组件一个子组件代码分别为:

<template>
  <div class="header">
    <input type="text" v-model="newMsg">
    <button @click="add">确定</button>
  </div>
  <Child :list="list" />
</template>

<script setup>
import { ref } from 'vue';
import Child from './child.vue';

const newMsg = ref('')
const list = ref(['html', 'css'])

const add = () => {
  list.value.push(newMsg.value)
}
</script>
<template>
  <div class="body">
    <ul>
      <li v-for="item in list">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
const props = defineProps({
  list: {
    type: Array,
    default: () => []
  }
})
</script>

这里就是通过给add函数将我们输入的内容添加到list数组中再通过props方法向子组件传递list这个数组子组件通过props接收通过v-for循环数组展示在页面上。

子父通信

在Vue.js中,理论上确实可以在子组件中直接修改父组件传递过来的数据,但这违背了Vue的设计理念鼓励数据单向流动的原则。因此,在Vue中并不推荐子组件直接修改父组件的数据。如果多个子组件都直接修改同一个父组件的数据,就会导致状态不一致,难以追踪哪个子组件做了修改,从而增加调试的难度。

所以子父通信使用的是一种事件驱动模型,在这个模型中,子组件通过触发事件(发布)来通知父组件(订阅者),而父组件通过监听这些事件来进行响应。

$emit

父子组件的代码如下:

<template>
    <Child @addMsg="handle" />
    <div class="body">
        <ul>
            <li v-for="item in list">{{ item }}</li>
        </ul>
    </div>
</template>

<script setup>
import Child from './child.vue';
import { ref } from 'vue';

const list = ref(['html', 'css'])
const handle = (e) => {
    list.value.push(e)
}
</script>
<template>
    <div class="header">
        <input type="text" v-model="newMsg">
        <button @click="add">确定</button>
    </div>
</template>

<script setup>
import { ref } from 'vue'
const newMsg = ref('')

const emits = defineEmits(['addMsg'])   // 定义事件
const add = () => {
    emits('addMsg', newMsg.value)    // 发布
}
</script>

这里我们在子组件使用defineEmits定义可以触发的自定义事件,定义一个点击事件用来触发'addMsg'事件,并传递输入框的值,然后在父组件定义一个处理函数,用于将子组件传递的数据添加到列表中。

v-model

<template>
    <Child v-model:list="list" />
    <div class="body">
        <ul>
            <li v-for="item in list">{{ item }}</li>
        </ul>
    </div>
</template>

<script setup>
import Child from './child.vue';
import { ref } from 'vue';

const list = ref(['html', 'css'])
</script>
<template>
    <div class="header">
        <input type="text" v-model="newMsg">
        <button @click="add">确定</button>
    </div>
</template>

<script setup>
import { ref } from 'vue';

const props = defineProps({
    list: []
})
const newMsg = ref('')
const emits = defineEmits(['update:list'])
const add = () => {
    const arr = props.list
    arr.push(newMsg.value)
    emits('update:list', arr)
}
</script>

这里我们在子组件使用defineEmits定义可以触发的自定义事件,定义一个点击事件用来触发'update:list'事件,并传递输入框的值。然后在父组件中,通过v-model:list="list"实现了双向数据绑定,这样当子组件触发'update:list'事件时,父组件中的list数组会自动更新为子组件传递的新数组。

ref

<template>
    <Child ref="childRef" />
    <div class="body">
        <ul>
            <li v-for="item in childRef?.list">{{ item }}</li>
        </ul>
    </div>
</template>

<script setup>
import Child from './child.vue';
import { ref } from 'vue';

const childRef = ref(null)
</script>
<template>
    <div class="header">
        <input type="text" v-model="newMsg">
        <button @click="add">确定</button>
    </div>
</template>

<script setup>
import { ref } from 'vue';

const newMsg = ref('')
const list = ref(['html', 'css'])

const add = () => {
    list.value.push(newMsg.value)
}
defineExpose({ list })
</script>

这里我们在子组件使用ref初始化一个响应式的数组list,包含初始的两个字符串'html''css'。定义了一个点击事件用来将输入框的值添加到list数组中。然后在父组件中,使用ref初始化一个引用childRef,指向子组件的实例,这样父组件可以通过childRef访问到子组件的list属性,并在模板中使用v-for遍历childRef?.list数组,显示每个元素。

这里的?.表示可选链操作符。它的作用是在尝试访问childRef.list之前先检查childRef是否为nullundefined。如果childRef存在,则继续访问list属性;如果childRef不存在,则返回undefined,避免了运行时错误。