当两个兄弟组件(拥有同一个父组件)需要共享同一份数据并保持同步时,一种直接而清晰的方案是将共享状态“提升”到它们共同的父组件中,然后通过 Props 向下传递,通过 Emits 向上通知变更。这就是“状态提升”。
为什么需要状态提升
假设一个页面中有两个独立组件:一个商品筛选栏 FilterBar,一个商品列表 ProductList。用户在筛选栏中选择分类后,列表需要刷新。这两个组件是兄弟关系,彼此无法直接通信。如果它们各自维护一份“当前分类”状态,就会出现两个独立的状态源,极易造成数据不一致。
状态提升的思路是:让“当前分类”这个状态只有一个来源——父组件。筛选栏操作时会通知父组件更新状态,父组件把最新值传给列表,从而保证两边永远同步。
如何实现
以一个简易的计数器示例来说明。页面上有两个组件:Display.vue 负责展示数值,Controls.vue 负责操作按钮。它们需要共享同一个 count。
父组件 App.vue(状态所有者):
<template>
<Display :count="count" />
<Controls @increment="count++" @decrement="count--" />
</template>
<script setup>
import { ref } from 'vue'
import Display from './Display.vue'
import Controls from './Controls.vue'
const count = ref(0)
</script>
展示组件 Display.vue(纯展示,无内部状态):
<template>
<div>当前计数:{{ count }}</div>
</template>
<script setup>
defineProps(['count'])
</script>
操作组件 Controls.vue(触发事件,自身不维护状态):
<template>
<button @click="$emit('increment')">+1</button>
<button @click="$emit('decrement')">-1</button>
</template>
<script setup>
defineEmits(['increment', 'decrement'])
</script>
现在,count 只存在于父组件中,两个子组件只是它的“投影”和“遥控器”。操作反馈和数据展示完全一致,不会出现各说各话的情况。
状态提升的适用场景与边界
- 适合:兄弟组件共享状态、且状态逻辑相对简单时,这是最符合 Vue 单向数据流设计的选择。代码结构清晰,调试时只需关注父组件就能追踪到状态变更路径。
- 不适合:当共享状态被多层级、远距离的组件使用时,逐层传递 Props 会演变成“Props 钻井”——中间组件被迫接收并转发无关数据,代码变得啰嗦且难以维护。此时应考虑 provide/inject(跨层级注入)或引入 Pinia(全局状态管理)来解耦。
状态提升本质上是一种让数据流保持单向和可预测的架构习惯。它的价值不在于用了什么特殊的 API,而在于强制你思考“这个状态到底属于谁”,从而避免随意乱放状态导致的混乱。