state-management

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

State Management

状态管理

Table of Contents

目录

Vue components are the building blocks of Vue apps by allowing us to couple markup (HTML), logic (JS), and styles (CSS) within them.
Vue组件是Vue应用的构建块,它允许我们在组件内将标记(HTML)、逻辑(JS)和样式(CSS)结合在一起。

When to Use

适用场景

  • Use this when you need to share reactive data between sibling or deeply nested components
  • This is helpful for managing global application state beyond simple parent-child prop passing
  • 当你需要在同级组件或深度嵌套组件之间共享响应式数据时使用
  • 这有助于管理超出简单父子组件props传递范围的全局应用状态

Instructions

操作指南

  • Use props for parent-to-child data flow and custom events for child-to-parent communication
  • Create a simple store using
    reactive()
    for small applications
  • Use Pinia (the official Vue state management library) for larger apps needing devtools, plugins, and TypeScript support
  • Choose the state management approach based on your app's complexity — don't over-engineer small apps
  • 使用props实现父组件到子组件的数据传递,使用自定义事件实现子组件到父组件的通信
  • 对于小型应用,使用
    reactive()
    创建简单的store
  • 对于需要开发者工具、插件和TypeScript支持的大型应用,使用Pinia(Vue官方状态管理库)
  • 根据应用的复杂度选择状态管理方案——不要过度设计小型应用

Details

详细说明

Here's an example of a Single-File component that displays a series of numbers from a data property:
html
<template>
  <div>
    <h2>The numbers are {{ numbers }}!</h2>
  </div>
</template>

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

  const numbers = ref([1, 2, 3]);
</script>
The
ref()
function prepares the component to be reactive. If a reactive property value that's being used in the template changes, the component view will re-render to show the change.
What if
numbers
was a data value that needed to be accessed from another component? If we want to share
numbers
between multiple components,
numbers
doesn't only become component-level data but also application-level data. This brings us to the topic of State Management - the management of application level data.
以下是一个单文件组件示例,它展示了来自数据属性的一系列数字:
html
<template>
  <div>
    <h2>The numbers are {{ numbers }}!</h2>
  </div>
</template>

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

  const numbers = ref([1, 2, 3]);
</script>
ref()
函数用于让组件具备响应式特性。如果模板中使用的响应式属性值发生变化,组件视图会重新渲染以展示变化后的内容。
如果
numbers
是需要被其他组件访问的数据值呢?如果我们想要在多个组件之间共享
numbers
,那么
numbers
就不再仅仅是组件级数据,同时也成为了应用级数据。这就引出了状态管理的话题——即对应用级数据的管理。

Props

Props

Vue gives us the ability to use props to pass data from the parent down to the child. Using props is fairly simple. All we essentially need to do is bind a value to the prop attribute where the child component is being rendered.
ParentComponent:
html
<template>
  <div>
    <ChildComponent :numbers="numbers" />
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import ChildComponent from "./ChildComponent";

  const numbers = ref([1, 2, 3]);
</script>
ChildComponent:
html
<template>
  <div>
    <h2>{{ numbers }}</h2>
  </div>
</template>

<script setup>
  const { numbers } = defineProps(["numbers"]);
</script>
Vue提供了props功能,用于从父组件向下传递数据给子组件。使用props非常简单,我们只需要在渲染子组件时,将值绑定到prop属性上即可。
ParentComponent
html
<template>
  <div>
    <ChildComponent :numbers="numbers" />
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import ChildComponent from "./ChildComponent";

  const numbers = ref([1, 2, 3]);
</script>
ChildComponent
html
<template>
  <div>
    <h2>{{ numbers }}</h2>
  </div>
</template>

<script setup>
  const { numbers } = defineProps(["numbers"]);
</script>

Component Events

组件事件

What if we needed to find a way to communicate information in the opposite direction? We can't use
props
since
props
can only be used to pass data in a uni-directional format (from parent down to child). To facilitate having the child component notify the parent about something, we can use custom events.
Custom events in Vue are dispatched as native CustomEvents and are used for communication between components.
ChildComponent:
html
<template>
  <div>
    <h2>{{ numbers }}</h2>
    <input v-model="number" type="number" />
    <button @click="$emit('number-added', Number(number))">
      Add new number
    </button>
  </div>
</template>

<script setup>
  const { numbers } = defineProps(["numbers"]);
</script>
ParentComponent:
html
<template>
  <div>
    <ChildComponent :numbers="numbers" @number-added="(n) => numbers.push(n)" />
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import ChildComponent from "./ChildComponent";

  const numbers = ref([1, 2, 3]);
</script>
如果我们需要反向传递信息该怎么办?我们不能使用
props
,因为
props
只能用于单向数据传递(从父组件到子组件)。为了让子组件能够通知父组件某些事件,我们可以使用自定义事件。
Vue中的自定义事件以原生CustomEvents的形式触发,用于组件之间的通信。
ChildComponent
html
<template>
  <div>
    <h2>{{ numbers }}</h2>
    <input v-model="number" type="number" />
    <button @click="$emit('number-added', Number(number))">
      Add new number
    </button>
  </div>
</template>

<script setup>
  const { numbers } = defineProps(["numbers"]);
</script>
ParentComponent
html
<template>
  <div>
    <ChildComponent :numbers="numbers" @number-added="(n) => numbers.push(n)" />
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import ChildComponent from "./ChildComponent";

  const numbers = ref([1, 2, 3]);
</script>

Simple State Management

简单状态管理

We can use props to pass data downwards and custom events to send messages upwards. How would we be able to either pass data or facilitate communication between two different sibling components?
A simple way to manage application-level state is to create a store pattern that involves sharing a data store between components. The store can manage the state of our application as well as the methods that are responsible for changing the state.
js
import { reactive } from "vue";

export const store = reactive({
  numbers: [1, 2, 3],
  addNumber(newNumber) {
    this.numbers.push(newNumber);
  },
});
The store contains a
numbers
array and an
addNumber
method that accepts a payload and directly updates the store's
numbers
value.
With Vue 3.x, we're able to import and use the
reactive()
function to declare reactive state from a JavaScript object. When this reactive state gets changed with the
addNumber()
method, any component that uses this reactive state will automatically update!
NumberDisplay:
html
<template>
  <div>
    <h2>{{ store.numbers }}</h2>
  </div>
</template>

<script setup>
  import { store } from "../store.js";
</script>
NumberSubmit:
html
<template>
  <div>
    <input v-model="numberInput" type="number" />
    <button @click="store.addNumber(numberInput)">Add new number</button>
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import { store } from "../store.js";

  const numberInput = ref(0);
</script>
When we say components interact with one another here, we're using the term 'interact' loosely. The components aren't going to do anything to each other but instead invoke changes to one another through the store.
If we take a closer look at all the pieces that directly interact with the store, we can establish a pattern:
  • The method in
    NumberSubmit
    has the responsibility to directly act on the store method, so we can label it as a store action.
  • The store method has a certain responsibility as well - to directly mutate the store state. So we'll say it's a store mutation.
  • NumberDisplay
    doesn't really care about what type of methods exist in the store or in
    NumberSubmit
    , and is only concerned with getting information from the store. So we'll say
    NumberDisplay
    is a store getter of sorts.
An action commits to a mutation. The mutation mutates state which then affects the view/components. View/components retrieve store data with getters. We're starting to get closer to a more structured manner to handling application-level state.
我们可以使用props向下传递数据,使用自定义事件向上传递消息。那我们该如何在两个同级组件之间传递数据或实现通信呢?
一种管理应用级状态的简单方法是创建一个store模式,即在组件之间共享一个数据store。store可以管理应用的状态,以及负责修改状态的方法。
js
import { reactive } from "vue";

export const store = reactive({
  numbers: [1, 2, 3],
  addNumber(newNumber) {
    this.numbers.push(newNumber);
  },
});
这个store包含一个
numbers
数组和一个
addNumber
方法,该方法接收一个参数并直接更新store中的
numbers
值。
在Vue 3.x中,我们可以导入并使用
reactive()
函数,从JavaScript对象声明响应式状态。当通过
addNumber()
方法修改这个响应式状态时,任何使用该响应式状态的组件都会自动更新!
NumberDisplay
html
<template>
  <div>
    <h2>{{ store.numbers }}</h2>
  </div>
</template>

<script setup>
  import { store } from "../store.js";
</script>
NumberSubmit
html
<template>
  <div>
    <input v-model="numberInput" type="number" />
    <button @click="store.addNumber(numberInput)">Add new number</button>
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import { store } from "../store.js";

  const numberInput = ref(0);
</script>
这里我们所说的组件之间的“交互”是广义的。组件之间不会直接相互操作,而是通过store来触发彼此的变化。
如果我们仔细观察所有直接与store交互的部分,可以总结出一个模式:
  • NumberSubmit
    中的方法负责直接调用store的方法,我们可以将其标记为store action(store动作)
  • store中的方法负责直接修改store状态,因此我们称其为store mutation(store变更)
  • NumberDisplay
    并不关心store或
    NumberSubmit
    中存在什么类型的方法,它只关注从store获取信息。因此我们可以将
    NumberDisplay
    视为一种store getter(store获取器)
动作提交给变更变更修改状态,进而影响视图/组件。视图/组件通过获取器获取store数据。我们正在逐步形成一种更结构化的应用级状态处理方式。

Pinia

Pinia

Pinia is a state management pattern and library for Vue.js that provides a more structured and scalable way to handle application-level state.
Pinia is an alternative to other state management solutions like Vuex and is now the official state management library for Vue. It provides a simple and efficient way to create and manage stores, which encapsulate state, actions, and getters.
In Pinia, we can define a store using the
defineStore()
function. Here we're using the Composition API syntax to define a
useNumbersStore()
function to create a
numbers
store.
js
import { ref } from "vue";
import { defineStore } from "pinia";

export const useNumbersStore = defineStore("numbers", () => {
  const numbers = ref([1, 2, 3]);

  function addNumber(newNumber) {
    this.numbers.push(newNumber);
  }

  return { numbers, addNumber };
});
We can then create a Pinia instance and install it in our Vue app.
js
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.mount("#app");
In the
NumberDisplay
component:
html
<template>
  <div>
    <h2>{{ store.numbers }}</h2>
  </div>
</template>

<script setup>
  import { useNumbersStore } from "../store";

  const store = useNumbersStore();
</script>
In the
NumberSubmit
component:
html
<template>
  <div>
    <input v-model="numberInput" type="number" />
    <button @click="store.addNumber(numberInput)">Add new number</button>
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import { useNumbersStore } from "../store";

  const store = useNumbersStore();
  const numberInput = ref(0);
</script>
For such a simple implementation like this, a Pinia store may not really be necessary and behaves very similarly to just using a store created with the
reactive()
function. With that said, Pinia offers additional capabilities for more complex use-cases such as the ability to extend Pinia features with plugins, have devtools support, and have more appropriate TypeScript support and server-side rendering support.
Pinia是Vue.js的状态管理模式和库,它提供了一种更结构化、可扩展的方式来处理应用级状态。
Pinia是Vuex等其他状态管理解决方案的替代方案,现在是Vue的官方状态管理库。它提供了一种简单高效的方式来创建和管理store,这些store封装了状态、动作和获取器。
在Pinia中,我们可以使用
defineStore()
函数定义一个store。这里我们使用Composition API语法定义了一个
useNumbersStore()
函数来创建一个
numbers
store。
js
import { ref } from "vue";
import { defineStore } from "pinia";

export const useNumbersStore = defineStore("numbers", () => {
  const numbers = ref([1, 2, 3]);

  function addNumber(newNumber) {
    this.numbers.push(newNumber);
  }

  return { numbers, addNumber };
});
然后我们可以创建Pinia实例并将其安装到Vue应用中。
js
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.mount("#app");
NumberDisplay
组件中:
html
<template>
  <div>
    <h2>{{ store.numbers }}</h2>
  </div>
</template>

<script setup>
  import { useNumbersStore } from "../store";

  const store = useNumbersStore();
</script>
NumberSubmit
组件中:
html
<template>
  <div>
    <input v-model="numberInput" type="number" />
    <button @click="store.addNumber(numberInput)">Add new number</button>
  </div>
</template>

<script setup>
  import { ref } from "vue";
  import { useNumbersStore } from "../store";

  const store = useNumbersStore();
  const numberInput = ref(0);
</script>
对于这样简单的实现,Pinia store可能并不是必需的,它的行为与使用
reactive()
创建的store非常相似。尽管如此,Pinia为更复杂的用例提供了额外的功能,比如可以通过插件扩展Pinia特性、支持开发者工具、提供更完善的TypeScript支持和服务端渲染支持。

What's the correct way?

哪种方式才是正确的?

Each method for managing application-level state comes with its advantages and disadvantages.
每种应用级状态管理方法都有其优缺点。

Simple Store

简单Store

  • Pro: Relatively easy to establish.
  • Con: State and possible state changes aren't explicitly defined.
  • 优点:相对容易实现。
  • 缺点:状态和可能的状态变更没有明确定义。

Pinia

Pinia

  • Pro: Devtools support, plugins + typescript + server-side rendering support
  • Con: Additional boilerplate.
At the end of the day, it's up to us to understand what's needed in our application and what the best approach may be.
  • 优点:支持开发者工具、插件、TypeScript和服务端渲染。
  • 缺点:需要额外的模板代码。
归根结底,我们需要了解应用的需求,从而选择最合适的方案。

Source

来源

References

参考资料