# ๋ทฐ์—‘์Šค ํƒ€์ž… ์ •์˜ ๋ฐฉ๋ฒ•

DANGER

โš ๏ธ ์ฃผ์˜! ์ด ๊ธ€์€ Vuex์™€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ๊ธฐ๋ณธ ๊ฐœ๋…์„ ๋ชจ๋‘ ์•„์‹œ๋Š” ๋ถ„๋“ค์ด ์ฝ์œผ์‹ค ์ˆ˜ ์žˆ๋Š” ๊ธ€์ž…๋‹ˆ๋‹ค. ์ธํ”„๋Ÿฐ Vue.js ํ•™์Šต ๋กœ๋“œ๋งต (opens new window)์„ ๋ชจ๋‘ ์ˆ˜๊ฐ•ํ•˜์‹  ๋ถ„๋“ค๊ป˜ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค ๐Ÿ˜ƒ

Vue.extend() ๋ฐฉ์‹์„ ์ด์šฉํ•˜์—ฌ ๋ทฐ์—‘์Šค๋ฅผ ํƒ€์ดํ•‘ํ•˜๋ ค๋ฉด ๋ทฐ์—‘์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋‚ด๋ถ€์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ํƒ€์ž…์„ ์•ฝ๊ฐ„ ๋ณ€ํ˜•ํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ ์ž‘์„ฑ ๋ฐฉ์‹์„ ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด ํ† ํฐ์„ ์„ค์ •ํ•˜๋Š” ๋ทฐ์—‘์Šค ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

# Vuex ๊ธฐ๋ณธ ์ฝ”๋“œ

๋จผ์ € store/index.ts์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

// store/index.ts
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = {
  state: {
    token: ''
  }
};

export default new Vuex.Store(store);

# state ์ •์˜

์œ„ ๊ธฐ๋ณธ ์ฝ”๋“œ์—์„œ state๋ฅผ ๋ณ„๋„์˜ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค. store/state.ts์— ๋†“๊ฒ ์Šต๋‹ˆ๋‹ค.

// store/state.ts
export const state = {
  token: '',
}

export type RootState = typeof state;

state๋ฅผ ์ •์˜ํ•œ ๋‹ค์Œ ํ•ด๋‹น ๊ฐ์ฒด ๊ตฌ์กฐ์˜ ํƒ€์ž…์„ RootState์˜ ํƒ€์ž… ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.

# mutations ์ •์˜

๋ฎคํ…Œ์ด์…˜ ์ฝ”๋“œ๋„ store/mutations.ts ํŒŒ์ผ์— ๋ณ„๋„๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

// store/mutations.ts
import { RootState } from "./state";

// ๋ฎคํ…Œ์ด์…˜ ํƒ€์ž…
export enum MutationTypes {
  SET_TOKEN = "SET_TOKEN",
}

// ๋ฎคํ…Œ์ด์…˜ ์†์„ฑ ํ•จ์ˆ˜
export const mutations = {
  [MutationTypes.SET_TOKEN](state: RootState, token: string) {
    state.token = token;
  },
};

export type Mutations = typeof mutations;

์ถ”ํ›„ ๋ฎคํ…Œ์ด์…˜ ์†์„ฑ ํ•จ์ˆ˜์˜ ํƒ€์ž… ์ถ”๋ก ์„ ์œ„ํ•ด ๋ฎคํ…Œ์ด์…˜ ํ•จ์ˆ˜์˜ ์ด๋ฆ„์€ ๋ชจ๋‘ enum ๊ฐ’์œผ๋กœ ์„ ์–ธํ•˜๊ณ  ํ•ด๋‹น ๊ฐ’์„ ํ•จ์ˆ˜์˜ ์ด๋ฆ„์œผ๋กœ ์ •์˜ํ•ด ์ค๋‹ˆ๋‹ค. ์•ž์—์„œ ์ •์˜ํ•œ state์˜ ํƒ€์ž…์ธ RootState๋ฅผ ๋“ค๊ณ  ์™€์„œ ๋ฎคํ…Œ์ด์…˜ ์†์„ฑ ํ•จ์ˆ˜์˜ ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…์œผ๋กœ ์—ฐ๊ฒฐํ•ด ์คฌ์Šต๋‹ˆ๋‹ค.

# ๋ทฐ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ทฐ์—‘์Šค ์ปค์Šคํ…€ ํƒ€์ž… ์ •์˜

๊ธ€ ์„œ๋‘์— ์–ธ๊ธ‰ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ทฐ์—‘์Šค์˜ ๋‚ด๋ถ€ ํƒ€์ž… ๋ฐฉ์‹์œผ๋กœ๋Š” ์œ„์—์„œ ์ •์˜ํ•œ state์™€ mutations๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”๋ก ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด store/types.ts์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

// store/types.ts
import { CommitOptions, Store } from "vuex";
import { Mutations } from "./mutations";
import { RootState } from "./state";

type MyMutations = {
  commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
    key: K,
    payload?: P,
    options?: CommitOptions
  ): ReturnType<Mutations[K]>;
};

export type MyStore = Omit<
  Store<RootState>,
  "commit"
> &
  MyMutations

์œ„ ์ฝ”๋“œ๋Š” ๋ทฐ์—‘์Šค ๋‚ด๋ถ€์ ์œผ๋กœ ์ •์˜๋œ ํƒ€์ž…์— ์šฐ๋ฆฌ๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ž‘์„ฑํ•œ state, mutations ํƒ€์ž… ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์ƒˆ๋กœ ์ •์˜๋œ MyStore ํƒ€์ž…์„ ์ด์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๊ฒŒ๋งŒ ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

TIP

๋ทฐ์—‘์Šค ๋‚ด๋ถ€ ํƒ€์ž…์ด ๊ถ๊ธˆํ•˜์‹  ๋ถ„๋“ค์€ Store ํƒ€์ž…์„ ์ซ“์•„์„œ ๋“ค์–ด๊ฐ€๋ณด์„ธ์š” ๐Ÿ˜ƒ

# ํ”„๋กœ์ ํŠธ ํƒ€์ž… ์ •์˜ ํ™•์žฅํ•˜๊ธฐ

์ด์ œ ์œ„์—์„œ ์ •์˜ํ•œ MyStore ํƒ€์ž…์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ปดํฌ๋„ŒํŠธ ์˜ต์…˜ ์†์„ฑ์—์„œ ์ถ”๋ก ๋  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

store-inference-error

๋ทฐ + ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋ ˆ๋ฒจ์— src/types/project.d.ts ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ์•„๋ž˜ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

// src/types/project.d.ts
import Vue from "vue";
import { MyStore } from "../store/types";

declare module "vue/types/vue" {
  interface Vue {
    $store: MyStore;
  }
}

declare module "vue/types/options" {
  interface ComponentOptions<V extends Vue> {
    store?: MyStore;
  }
}

๋‹ค์Œ์œผ๋กœ ํ”„๋กœ์ ํŠธ์˜ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์„ค์ • ํŒŒ์ผ์— ์•„๋ž˜ ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.








ย 





// ...
"include": [
  "src/**/*.ts",
  "src/**/*.tsx",
  "src/**/*.vue",
  "tests/**/*.ts",
  "tests/**/*.tsx",
  "src/types/**.d.ts",
],
"exclude": [
  // ...
]

๊ทธ๋ฆฌ๊ณ  node_modules/vuex/types/vue.d.ts ํŒŒ์ผ์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ ์‚ฌ์šฉ์ค‘์ธ ๊ฐœ๋ฐœ ํˆด์ด๋‚˜ ์ฝ”๋“œ ํŽธ์ง‘๊ธฐ๋ฅผ ์ข…๋ฃŒํ•˜๊ณ  ๋‹ค์‹œ ์‹คํ–‰ํ•˜์—ฌ ์ถ”๋ก ์ด ์ž˜ ๋˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

store-infer

TIP

Vue 2์—์„œ๋Š” node_modules ๋ฐ‘์˜ ํƒ€์ž… ์„ ์–ธ ํŒŒ์ผ์„ ์ง€์›Œ์ค˜์•ผ ํ•˜์ง€๋งŒ, Vue 3์—์„œ๋Š” ๋‚ด๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฑด๋“ค์ง€ ์•Š๊ณ ๋„ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค ๐Ÿ˜ƒ Vuex 4 ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ (opens new window)

# actions ์ •์˜

actions ํ•จ์ˆ˜๋„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// store/actions.ts
import { ActionContext } from "vuex";
import { Mutations } from "./mutations";
import { RootState } from "./state";

export enum ActionTypes {
  FETCH_NEWS = "FETCH_NEWS"
}

interface News {
  title: string;
  id: number;
}

type MyActionContext = {
  commit<K extends keyof Mutations>(
    key: K,
    payload?: Parameters<Mutations[K]>[1]
  ): ReturnType<Mutations[K]>;
} & Omit<ActionContext<RootState, RootState>, "commit">;

export const actions = {
  async [ActionTypes.FETCH_NEWS](context: MyActionContext, payload?: number) {
    const res = await fetch("https://jsonplaceholder.typicode.com/users/1");
    const user: News[] = await res.json();
    return user;
  }
};

export type Actions = typeof actions;

์Šคํ† ์–ด ์ปค์Šคํ…€ ํƒ€์ž…์ด ์ •์˜๋œ ํŒŒ์ผ์— ์•„๋ž˜ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

ย 
ย 












ย 
ย 
ย 
ย 
ย 
ย 
ย 


ย 


ย 


// store/types.ts
import { CommitOptions, DispatchOptions, Store } from "vuex";
import { Actions } from "./actions";
import { Mutations } from "./mutations";
import { RootState } from "./state";

type MyMutations = {
  commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
    key: K,
    payload?: P,
    options?: CommitOptions
  ): ReturnType<Mutations[K]>;
};

type MyActions = {
  dispatch<K extends keyof Actions>(
    key: K,
    payload?: Parameters<Actions[K]>[1],
    options?: DispatchOptions
  ): ReturnType<Actions[K]>;
};

export type MyStore = Omit<
  Store<RootState>,
  "commit" | "dispatch"
> &
  MyMutations &
  MyActions;

# getters ์ •์˜

getters ์†์„ฑ ํ•จ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

// store/getters.ts
import { RootState } from "./state";

export const getters = {
  getToken(state: RootState) {
    return state.token + "!";
  }
};

export type Getters = typeof getters;

์Šคํ† ์–ด ์ปค์Šคํ…€ ํŒŒ์ผ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.



ย 



















ย 
ย 
ย 
ย 
ย 



ย 



ย 

import { Action, CommitOptions, DispatchOptions, Store } from "vuex";
import { Actions } from "./actions";
import { Getters } from "./getters";
import { Mutations } from "./mutations";
import { RootState } from "./state";

type MyMutations = {
  commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
    key: K,
    payload?: P,
    options?: CommitOptions
  ): ReturnType<Mutations[K]>;
};

type MyActions = {
  dispatch<K extends keyof Actions>(
    key: K,
    payload?: Parameters<Actions[K]>[1],
    options?: DispatchOptions
  ): ReturnType<Actions[K]>;
};

type MyGetters = {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>;
  };
};

export type MyStore = Omit<
  Store<RootState>,
  "getters" | "commit" | "dispatch"
> &
  MyMutations &
  MyActions &
  MyGetters;