티스토리 뷰

Dev

Vue3 + typescript + pinia

hjhj97 2022. 11. 12. 20:02

최근에 개발중인 프로젝트들은 모두 Vue3의 composition api를 기반으로 하고있다. 다만 아직 typescript가 아닌 javascript를 사용하고, 상태관리는 pinia가 아닌 vuex를 사용 하고 있다. composition api를 사용하면 typescript와 pinia를 사용하는데 있어서 큰 이점을 얻을 수 있다보니 조만간 도입을 시도하려고 한다.
하지만 아직은 아쉽게도 한국어로 된 자료가 많지는 않은 실정이다. 아무래도 한국은 Vue보다는 React의 점유율이 더 높은데다가, 그나마 있는 Vue 자료도 option api로 되어있는 경우가 많다. 어쩔 수 없이 내가 스스로 공부해나가면서 길을 만들어가는 중이다.

src/views/TodoList.vue

템플릿

<template>
    <h1>Todo List</h1>
    <h3>using interface,pinia</h3>
    <div>
        <input type="text" placeholder="title" v-model="inputTitle" />
        <input type="text" placeholder="name" v-model="inputName" />
        <button @click="onClickTodo">Add</button>
    </div>
    <div v-for="todo in todos" :key="todo.id">
        <TodoItem :item="todo" />
    </div>
</template>

ts와 pinia를 적용할지라도 템플릿부분은 크게 바뀌지 않는다. 굳이 꼽자면 vuex를 사용했을 때의 템플릿에서 접근할 수 있었던 $store키워드를 pinia에서는 사용할 수 없다는 점이다.

스크립트

<script lang="ts">
    import { defineComponent, ref } from 'vue';
    import { useTodoListStore } from '@/stores/todoList';
    import { Todo } from '@/types/Todo';
    import TodoItem from './TodoItem.vue';

    export default defineComponent({
        components: { TodoItem },
        setup() {
            const todoStore = useTodoListStore();
            const todos = ref<Todo[]>(todoStore.todos);

            const inputTitle = ref<string>('');
            const inputName = ref<string>('');
            const onClickTodo = (): void => {
                todoStore.addTodo({
                    title: inputTitle.value,
                    name: inputName.value,
                    id: todoStore.todos.length + 1,
                    status: 'yet',
                });
                inputTitle.value = '';
                inputName.value = '';
            };

            return {
                todos,
                onClickTodo,
                inputTitle,
                inputName,
            };
        },
    });
</script>

스크립트부터 이제 변화가 생긴다. 일단 기존<script>태그를 사용한 것과 달리 타입스크립트에서는 <script lang="ts">를 사용한다. 또한 기존 export default{}에서 export default defineComponent({})를 사용해야 한다.
그리고 composition api에서 변수에 반응성을 부여해주기 위해 사용하는 ref()함수도 괄호 앞에 제네릭문법을 적용하여 ref<'type'>() 이런 식으로 적어주게 된다. 예를 들어 문자열 데이터를 선언하기 위해서는 ref<string>('Hello') 처럼 적어주면 된다.

src/types/Todo.ts

type Status = 'done' | 'ongoing' | 'yet';

interface Todo {
    id: number;
    title: string;
    name: string;
    status: Status;
}

export { Todo, Status };

타입스크립트를 사용하기 위해서 별도의 types 디렉토리를 만들고, interface를 정의하였다.

src/stores/index.ts

import { createPinia } from 'pinia';
const pinia = createPinia();

export default pinia;

pinia의 index파일은 단 3줄이면 된다. vuex에서는 각종 모듈을 모두 index에서 선언해주어야 하는 것과 달리, pinia에서는 각자 따로 선언하는 방식이다. 아래 코드를 보자.

src/stores/todoList.ts

import { defineStore } from 'pinia';
import { Todo, Status } from '@/types/Todo';

export const useTodoListStore = defineStore('todoList', {
    state: () => {
        return {
            todos: [] as Todo[],
        };
    },
    actions: {
        addTodo(item: Todo) {
            this.todos.push(item);
        },
        deleteTodo(id: number) {
            const idx = this.todos.findIndex((item) => item.id === id);
            this.todos.splice(idx, 1);
        },
        changeStatus(changed: Todo, st: Status) {
            this.todos.forEach((item) => {
                if (item.id === changed.id) {
                    item.status = st;
                }
            });
        },
    },
});

pinia에서는 defineStore()라는 함수 안에서 기능을 정의하고 이를 useTodoListStore라는 이름으로 사용할 수 있다. 그래서 컴포넌트에서 접근할 때는 useTodoListStore.todos로 state에 접근하고, useTodoListStore.addTodo()식으로 함수를 사용하면 된다.. 또한 vuex와 다르게 mutations가 존재하지 않는다. 비동기처리를 actions에서 수행하고, state를 변경하기 위해서는 mutations안의 메소드를 사용한 것과 달리 pinia에서는 actions에서 비동기 처리와 값의 변경을 모두 처리한다.

'Dev' 카테고리의 다른 글

vue 에서 스크롤 위치 저장  (0) 2023.03.09
프로젝트 회고  (0) 2023.02.18
vue 커스텀 디렉티브(custom directive)  (0) 2022.12.05
Vue3 타이머 구현  (0) 2022.11.12
css, js를 이용한 룰렛 이벤트 구현(Vue3)  (0) 2022.11.12
댓글