Vue3, Firebase 프로젝트 - 채팅앱 VSignal

7. Pinia Store에서 ref와 reactive를 사용하는 방법

그랜파 개발자 2025. 3. 30. 20:58

Pinia Store에서 ref와 reactive를 사용하는 방법

Pinia Store에서 ref와 reactive를 사용하는 방법을 알아보겠습니다.
Pinia에서는 Composition API 스타일 Options API 스타일을 모두 사용할 수 있지만,
여기서는 Composition API 스타일을 중점적으로 설명합니다.

 

1. Vue가 뭐야? 
2. 프레임워크란?
3. Vue3 기본 문법
4. Vue Router (<router-link>, <router-view>)
5. Pinia (상태 관리 라이브러리)
6. Vue3 Composition API
7. Pinia Store에서 ref와 reactive를 사용하는 방법
8. Pinia에서의 Composition API 스타일과 Options API 스타일

1. ref와 reactive 차이

  • ref(): 기본 타입(숫자, 문자열, 불리언 등)이나 객체를 반응형으로 만들 때 사용하며, .value를 통해 값을 접근해야 합니다.
  • reactive(): 객체나 배열을 반응형으로 만들 때 사용합니다. .value 없이 바로 속성에 접근할 수 있습니다.

Pinia에서는 state를 ref() 또는 reactive()로 선언하여 사용할 수 있습니다.

2. ref() 사용하기 (Composition API 스타일)

✅ defineStore() 안에서 ref() 사용

ref()를 사용하면 Pinia store의 상태를 반응형으로 만들 수 있습니다.

// src/stores/counter.js
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0);

  const increment = () => {
    count.value++;
  };

  const decrement = () => {
    count.value--;
  };

  return { count, increment, decrement };
});

✅ 컴포넌트에서 사용하기

<!-- src/components/Counter.vue -->
<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <button @click="counter.increment">Increment</button>
    <button @click="counter.decrement">Decrement</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '../stores/counter';

const counter = useCounterStore();
</script>

📌 설명

  • defineStore() 내부에서 ref()를 사용하면, count는 반응형 데이터가 됩니다.
  • increment()와 decrement()는 count.value를 수정합니다.
  • 컴포넌트에서 counter.count를 직접 사용하면 반응형이 자동으로 적용됩니다.

3. reactive() 사용하기 (Composition API 스타일)

✅ defineStore() 안에서 reactive() 사용

reactive()를 사용하면 객체의 여러 속성을 한 번에 반응형으로 만들 수 있습니다.

// src/stores/user.js
import { defineStore } from 'pinia';
import { reactive } from 'vue';

export const useUserStore = defineStore('user', () => {
  const user = reactive({
    name: 'John Doe',
    age: 30,
  });

  const updateName = (newName) => {
    user.name = newName;
  };

  const incrementAge = () => {
    user.age++;
  };

  return { user, updateName, incrementAge };
});

✅ 컴포넌트에서 사용하기

<!-- src/components/UserProfile.vue -->
<template>
  <div>
    <p>Name: {{ userStore.user.name }}</p>
    <p>Age: {{ userStore.user.age }}</p>
    <button @click="userStore.incrementAge">Increase Age</button>
    <button @click="userStore.updateName('Alice')">Change Name</button>
  </div>
</template>

<script setup>
import { useUserStore } from '../stores/user';

const userStore = useUserStore();
</script>

📌 설명

  • reactive()를 사용하면 user 객체 전체가 반응형이 됩니다.
  • updateName()을 호출하면 user.name이 변경됩니다.
  • incrementAge()를 호출하면 user.age가 증가합니다.

4. ref() vs reactive() 비교

ref()reactive()

데이터 타입 기본 값(숫자, 문자열, 불리언, 객체 등) 객체, 배열
접근 방법 변수.value 변수.속성
속성 추가 가능 여부 O (가능) X (초기 정의된 속성만 반응형)
객체 구조분해 (destructure) 반응형 유지 안됨 반응형 유지됨

💡 주의: reactive()로 만든 객체를 구조 분해하면 반응성을 잃을 수 있습니다.

예제:

const store = useUserStore();
const { name } = store.user; // ❌ 반응형이 깨짐

➡️ 해결 방법: computed()를 사용하거나 store.user.name을 직접 사용.

5. ref()와 reactive() 함께 사용하기

✅ reactive() + ref() 조합

reactive() 내부에서 ref()를 사용할 수도 있습니다.

// src/stores/todo.js
import { defineStore } from 'pinia';
import { reactive, ref } from 'vue';

export const useTodoStore = defineStore('todo', () => {
  const todos = reactive([]);
  const newTodo = ref('');

  const addTodo = () => {
    if (newTodo.value.trim()) {
      todos.push({ text: newTodo.value, completed: false });
      newTodo.value = '';
    }
  };

  return { todos, newTodo, addTodo };
});

✅ 컴포넌트에서 사용하기

<!-- src/components/TodoList.vue -->
<template>
  <div>
    <input v-model="todoStore.newTodo" placeholder="Add a task" />
    <button @click="todoStore.addTodo">Add</button>

    <ul>
      <li v-for="(todo, index) in todoStore.todos" :key="index">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { useTodoStore } from '../stores/todo';

const todoStore = useTodoStore();
</script>

📌 설명
- reactive([])로 빈 배열 todos를 생성했습니다.
- ref('')로 newTodo를 생성하여 input과 연결했습니다.
- addTodo()를 실행하면 todos 배열에 새로운 항목이 추가됩니다.

6. persist (상태 저장) 기능과 함께 사용하기

Pinia의 persist 옵션을 사용하면, localStorage 또는 sessionStorage에 상태를 저장할 수 있습니다.

// src/stores/settings.js
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useSettingsStore = defineStore('settings', () => {
  const darkMode = ref(false);

  const toggleDarkMode = () => {
    darkMode.value = !darkMode.value;
  };

  return { darkMode, toggleDarkMode };
}, {
  persist: true,  // localStorage에 저장
});

이제 페이지를 새로고침해도 darkMode 상태가 유지됩니다.

🔹 정리

✅ ref()

  • 기본 타입 (숫자, 문자열, 불리언) 및 객체를 반응형으로 만들 때 사용.
  • .value를 사용하여 값을 변경하거나 읽어야 함.

✅ reactive()

  • 객체, 배열을 반응형으로 만들 때 사용.
  • .value 없이 속성에 바로 접근 가능.
  • 새로운 속성을 추가하면 반응형이 깨질 수 있음.

✅ reactive()와 ref()를 함께 사용할 수도 있음.

  • reactive()는 객체에 적합하고, ref()는 개별 값에 적합함.

✅ persist 옵션을 사용하면 Pinia 상태를 로컬 스토리지에 저장 가능.

이제 Pinia에서 ref()와 reactive()를 자유롭게 활용해볼 수 있습니다! 🚀

 

이전 : 6. Vue3 Composition API

다음 : 8. Pinia에서의 Composition API 스타일과 Options API 스타일