프로젝트

Vue 3 + Vuex 로 Todo-List 만들어보기 (4) + Composition API로 바꾸기

김쨔뿌 2022. 5. 3. 03:43

이전에 올린 1, 2, 3을 보면 Vue3에 추가된 Composition API를 제대로 안쓰고 기존 방식으로 진행했었는데,

그제어제오늘 Compositon API를 쓸 때랑 안쓸때랑 비교해보니 쓰는게 확실히 편함.

그래서 미완코드를 Composition API로 바꿀거쉽니다. Composition API 관련된건 새글 파서 여기에 곧 링크를 걸을 예정.

 

대충 어떻게 바뀌는지만 보이자면

 

App.vue 기존 코드

<script>
import AddCard from "./components/AddCard.vue";
import ButtonElement from "./components/ButtonElement.vue";
import TodoMain from "./components/TodoMain.vue";
export default {
  name: "App",
  components: { TodoMain, AddCard, ButtonElement },
  data() {
    return { addCard: false };
  },
  methods: {
    showCard() {
      this.addCard = true;
    },
  },
};
</script>

 

App.vue Composition API script setup 사용한 코드

<script>
import AddCard from "./components/AddCard.vue";
import ButtonElement from "./components/ButtonElement.vue";
import TodoMain from "./components/TodoMain.vue";
export default {
  name: "App",
  components: { TodoMain, AddCard, ButtonElement },
};
</script>
<script setup>
import { ref } from "vue";
const addCard = ref(false);
const showCard = () => {
  addCard.value = true;
};
</script>

그릏다. script setup에서는 선언한 녀석들을 그냥 template에 때려박을 수 있다!!! 근데 반응형인 값은 ref나 reactive 를 써야하는데 reactive 는 좀 킹받는 부분이 있어서 ref 쓰는게 편혀. 그리고 name 이나 component 부분은 그냥 script에 써줘야..함. component 부분은 헷갈리는데 name의 경우 아직 script setup 에서 쓸 방법이 없다는듯. 

 

암튼 저렇게 다 바꿔준다. 

 

이제 추가하기 버튼 보여주는것 바꿀거임. 방법은 App.vue에서 만든 showCard 함수를 props 로 AddCard 컴포넌트에 내려주고 close에 클릭이벤트로 넣는것! 그리고 addCard.value = true; 를 addCard.value = !addCard.value 로 바꿔주고 addCard가 false 일때 Add card 버튼이 안보이게 할 거쉬다.

 

 

//App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <todo-main></todo-main>
  <button-element @click="showCard" v-if="!addCard">Add card</button-element>
  <add-card v-if="addCard" :closeCard="showCard"></add-card>
</template>

<script>
import AddCard from "./components/AddCard.vue";
import ButtonElement from "./components/ButtonElement.vue";
import TodoMain from "./components/TodoMain.vue";
export default {
  name: "App",
  components: { TodoMain, AddCard, ButtonElement },
};
</script>
<script setup>
import { ref } from "vue";
const addCard = ref(false);
const showCard = () => {
  addCard.value = !addCard.value;
};
</script>
//AddCard.vue
<template>
  <section>
    <button-element @click="props.closeCard">close</button-element>
    <input-element title="title"></input-element>
    <input-element title="comment"></input-element>
    <button-element>save</button-element>
  </section>
</template>
<script>
import ButtonElement from "./ButtonElement.vue";
import InputElement from "./InputElement.vue";
export default {
  components: { InputElement, ButtonElement },
  name: "AddCard",
};
</script>
<script setup>
import { defineProps } from "vue";
const props = defineProps({
  closeCard: Function,
});
</script>

script setup에서는 defineProps 를 이용하여 props 를 받을 수 있다. const 변수명 = defineProps({ 명칭: 타입, 명칭: 타입, ... }); 의 형식으로 적어주고 사용할때는 변수명.명칭 으로 쓰면 된다. 

그리고 v-if를 사용해서 Add card의 노출여부를 정해주었다. 

 

 

잘된당~

그리고 스토어에서 뮤테이션 함수를 만들어준다. 딱히 아직 모듈분리 안해도 될 것 같아서 안함.

 

// store/index.js
import { createStore } from "vuex";

export default createStore({
  state: {
    todoList: [
      {
        id: 0,
        title: "제목이다!",
        comment:
          "마라탕에 푸주, 두부피, 죽순, 어묵만 가득 채워서 먹어보고싶다.",
        done: false,
      },
      {
        id: 1,
        title: "제목이다!제목이다!",
        comment: "영어공부를 좀 혀야하는디 에혀 우짜냐",
        done: false,
      },
      {
        id: 2,
        title: "제목이다!제목이다!제목이다!",
        comment: "뷰 생각보다 재밌다 하지만 여전히 리액트가 너무 좋은.",
        done: false,
      },
    ],
  },
  getters: {},
  mutations: {
    addItem(state, object) {
      let item = {
        id: state.todoList.length,
        ...object,
      };
      state.todoList.push(item);
    },
    removeItem(state, id) {
      state.todoList = state.todoList.filter((x) => id !== x);
    },
    editItem(state, object) {
      state.todoList[object.id - 1] = object;
    },
    doneItem(state, id) {
      state.todoList[id - 1].done = !state.todoList[id - 1].done;
    },
  },
  actions: {},
  modules: {},
});

mutations 부분을 보면 됨. 솔직히 뇌에 힘 안주고 쭉쭉적어서 제대로 돌아갈 진 모르겠다. 이제 각 버튼에 넣어주고 확인해봐야함.

각각 닉값하게 만들었다. vuex의 편한점이 state.어쩌구 = 어쩌구 이런 식으로 쓰면 바로 바꿔주는게.. 너무 좋다. 리덕스 그넘은 액션에서 리듀서로 거쳐야 허는디 으휴 쓸것두많은넘 ㅉㅉ 그래도 좋아해...

 

*며칠 쉬다가 여기부터 글 다시 적기...

 

이제 추가하기 기능 넣을거임.

그 전에 나는 input 컴포넌트를 분리했는데 이렇게되면 input컴포넌트에서 상위컴포넌트인 AddCard로 input 데이터를 쏘아주어야함.

//App.vue

<template>
    <input-element v-model="title"></input-element>
</template>
<script>
    import { ref } from "vue";
    const title = ref("");
</script>

만약 App.vue 에서 조금이라도 복잡해진 하위 컴포넌트의 input 값을 가져오려고 v-model을 쓸 때, 위의 예시처럼 저렇게만 하면 ref에 값이 저장이 안됨. 

상위컴포넌트가 하위컴포넌트의 input 값을 가져오게 해주려면 우선 하위컴포넌트를 아래와 같이 변경해주어야 함.

//InputElement.vue
<template>
  <label>
    <h3>{{ props.title }}</h3>
    <input :value="value" @input="emits('update:value', $event.target.value)" />
  </label>
</template>
<script>
export default {
  name: "InputElement",
};
</script>
<script setup>
import { defineProps, defineEmits } from "vue";

const props = defineProps({
  title: String,
  value: String,
});

const emits = defineEmits(["update:value"]);
</script>

하위컴포넌트인 InputElement에서 props와 emit을 지정해주자. props.value 가 input의 value가 될거고 v-model이 이 값을 내려다줄것임. 그리고 emits 를 통해 업데이트 된 값을 올려다줘야함. "update:어쩌구" 로 만들어주고 @input="emits('update:어쩌구',$event.target.value)" 하면 됨.

그리고 상위 컴포넌트의 v-model 을 v-model:value 이렇게 바꿔주면 끝.

//App.vue

<template>
    <input-element v-model:value="title"></input-element>
</template>
<script>
    import { ref } from "vue";
    const title = ref("");
</script>

위의 코드들에서 중요한 부분은 

 

<input :value="value" @input="emits('update:value', $event.target.value)" />

 

const props = defineProps({
  value: String,
});


const emits = defineEmits(["update:value"]);

 

<input-element v-model:value="title"></input-element>

 

이 세개임. 여기서 원하는대로 색칠된 부분 명칭 바꿔서 써주면 된다. title 부분은 바꾸려면 ref 명칭도 바꿔줘야함.

 

아무튼 위의 방식을 이용해서 input의 값을 잘 가져온다면 저장기능을 이렇게 만들어준다. 

 

//AddCard.vue

<template>
  <section>
    <button-element @click="props.closeCard">close</button-element>
    <input-element
      type="text"
      title="title"
      v-model:value="title"
    ></input-element>
    <input-element
      type="text"
      title="comment"
      v-model:value="comment"
    ></input-element>
    <button-element @click="saveItem">save</button-element>
  </section>
</template>

<script>
import ButtonElement from "./ButtonElement.vue";
import InputElement from "./InputElement.vue";
export default {
  components: { InputElement, ButtonElement },
  name: "AddCard",
};
</script>
<script setup>
import { defineProps, ref } from "vue";
import { useStore } from "vuex";
const props = defineProps({
  closeCard: Function,
});

const store = useStore();
const title = ref("");
const comment = ref("");

const saveItem = () => {
  if (!title.value) {
    alert("제목을 적어주세요");
    return;
  } else {
    store.commit("addItem", {
      title: title.value,
      comment: comment.value,
      done: false,
    });
    title.value = "";
    comment.value = "";
  }
};
</script>

 

 

<template> 안의 <button-element @click="saveItem">save</button-element> 는 save 버튼을 누르면 saveItem 을 실행시켜준다.

 

saveItem 은 title, comment 각각의 ref에 저장된 값을 가져오고 commit 을 이용하여 스토어의 mutations 함수 중 addItem을 사용한다. addItem은 스토어에 넘어온 객체를 저장해준다. 그 후 title, comment 를 공란으로 비워주면 끝. 예외처리로는 제목이 비었을 경우만 해놓았음. 투두리스트니까 제목만 간결하게 적어서 쓸 수 있으니까! 

 

잘 된당...

이거 하나 됐으면 다른 기능 추가하는건 어렵지 않을 테니까....잘거임 20000