# 저번 시간은 부모 / 자식 컴포넌트간 데이터 전달을 배웠다.

 

Root컴포넌트에서

Header, Main, Footer, DeepChild 컴포넌트에 데이터를 전달하고 싶다면?

 

prop은 변경할 수 없다? => 부모에서 전달한 prop값은 읽기는 가능하지만, 값을 변경할 순 없다!

저번 시간의 Ch05DataDelivery - Exam01Props를 살펴보자!

 

 

# 실습해보자.

 

1. Ch05DataDelivery 폴더 아래 Exam03ProvideInject 폴더를 만들고 index.vue 생성 (기초 코드)

2. routes 등록

3. AppMenu 리스트 하나 추가

4. Ch05DataDelivery 폴더 아래 Exam03ProvideInject 폴더를 만들고 ChildA.vue 생성

5. Ch05DataDelivery 폴더 아래 Exam03ProvideInject 폴더를 만들고 ChildB.vue 생성

 

index의 자식 ChildA, ChildA의 자식 ChildB로 실습

 

* index.vue

<template>
    <div class="card">
        <div class="card-header">Exam03ProvideInject</div>
        <div class="card-body">
            <div>
                <p>data1 : {{ data1 }}</p>
                <p>data2 : {{ data2.name1 }}</p>
                <p>data2 : {{ data2.name2 }}</p>
                <button class="btn btn-secondary btn-sm mt-2" @click="changeData">데이터 변경</button>
                <hr>
                <ChildA />
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, provide } from 'vue';
import ChildA from './ChildA.vue';

// 상태 생성
const data1 = ref("parent-value1");
const data2 = ref({
    name1: "parent-value2",
    name2: "parent-value3"
});

// 하위 컴포넌트로 데이터를 제공 (provide 함수 import하기)
provide("message", { data1: data1, data2 }); // 속성의 이름과 값의 이름이 같을 경우는 하나만 써도 된다.

// 데이터 변경 함수
function changeData() {
    data1.value += "0";
    data2.value.name1 += "0";
    data2.value.name2 += "0";
}
</script>

<style scoped></style>

 

* ChildA.vue

<template>
    <div class="card">
        <div class="card-header">ChildA</div>
        <div class="card-body">
            <div>
                <p>message: {{ message }}</p>
                <p>data1: {{ message.data1.value }}</p>
                <p>data2.name1: {{ message.data2.value.name1 }}</p>
                <p>data2.name2: {{ message.data2.value.name2 }}</p>
            </div>
            <hr>
            <ChildB />
        </div>
    </div>
</template>

<script setup>
import { inject, provide } from 'vue';
import ChildB from './ChildB.vue';

const message = inject("message"); // 부모의 provide 함수가 전달 한 것을 자식에선 inject로 받을 수 있다.

console.group("message로 넘긴 값 확인");
console.log("message : " + message);
console.log("message.data1.value: " + message.data1.value); // 상태값이기 때문에 .value를 붙여준다.
console.log("message.data2.value.name1: " + message.data2.value.name1);
console.log("message.data2.value.name2: " + message.data2.value.name2);
console.groupEnd();

</script>

<style scoped></style>

 

* ChildB.vue

<template>
    <div class="card">
        <div class="card-header">ChildB</div>
        <div class="card-body">
            <div>
                <p>message: {{ message }}</p>
                <p>data1: {{ message.data1.value }}</p>
                <p>data2.name1: {{ message.data2.value.name1 }}</p>
                <p>data2.name2: {{ message.data2.value.name2 }}</p>
                <button class="btn btn-danger btn-sm mt-2" @click="changeData">데이터 변경</button>
            </div>
        </div>
    </div>
</template>

<script setup>
import { inject } from 'vue';

const message = inject("message"); // 부모의 provide 함수가 전달 한 것을 자식에선 inject로 받을 수 있다.

console.group("message로 넘긴 값 확인");
console.log("message : " + message);
console.log("message.data1.value: " + message.data1.value); // 상태값이기 때문에 .value를 붙여준다.
console.log("message.data2.value.name1: " + message.data2.value.name1);
console.log("message.data2.value.name2: " + message.data2.value.name2);
console.groupEnd();

// 데이터 변경 함수
function changeData() {
    message.data1.value += "1";
    message.data2.value.name1 += "1";
    message.data2.value.name2 += "1";
}

</script>

<style scoped></style>

 


 

Ch06. Lifecycle Hook

: 컴포넌트의 생명 주기

위의 오른쪽 그림은 수정해야함.

제일 먼저 setup 함수 호출 ( <script setup> 부분) (컴포넌트를 만들기 전에 setup이라는 자바스크립트를 먼저 실행)

생명 주기 함수 created, beforCreate

 

onUpdate 상태에서 상태를 변경하면 안됨!

 

 

# 실습해보자.

 

1. Ch06LifecycleHook폴더 아래 Exam01LifecycleHook폴더를 만들고 index.vue 생성 (기초 코드)

2. router폴더 - Ch06LifecycleHook.js 작성.

3. router폴더 - index.js에 구조분해할당식으로 등록

4. AppMenu 아코디언리스트, 리스트 하나 추가

5. Ch06LifecycleHook폴더 아래 Exam01LifecycleHook폴더 - MountHook.vue 생성

6. Ch06LifecycleHook폴더 아래 Exam01LifecycleHook폴더 - UpdateHook.vue 생성

 

★ 이해

Mounted와 Unmounted를 이용하여 DOM에 마운트가 되는지, 다른 컴포넌트로 이동시 언마운트가 되는지 확인

 

* index.vue 

<template>
    <div class="card">
        <div class="card-header">Exam01LifecycleHook</div>
        <div class="card-body">
            <MountHook />
            <UpdateHook class="mt-3" />
        </div>
    </div>
</template>

<script setup>
import MountHook from './MountHook.vue'
import UpdateHook from './UpdateHook.vue'

</script>

<style scoped></style>

 

*  MountHook.vue 

<template>
    <div class="card">
        <div class="card-header">MountHook</div>
        <div class="card-body">
            
        </div>
    </div>
</template>

<script setup>
import { onMounted, onUnmounted } from 'vue';

onMounted(() => {
    console.log("MountHook 컴포넌트가 마운트됨");
});

onUnmounted(() => {
    console.log("MountHook 컴포넌트가 언마운트됨");
});
</script>

<style scoped></style>

 

* UpdateHook.vue 

<template>
    <div class="card">
        <div class="card-header">UpdateHook</div>
        <div class="card-body">
            <p>count: {{ count }}</p>
            <button class="btn btn-info btn-sm mt-2" @click="count++">상태 변경</button>
        </div>
    </div>
</template>

<script setup>
import { onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref } from 'vue';

// 상태 생성
const count = ref(0);

// 컴포넌트가 마운트 될 때 (브라우저 화면에 보이게 될 때를 의미 : Html Dom에 올라갈 때.)
onMounted(() => {
    console.log("UpdateHook 컴포넌트가 마운트 됨");
});

onUnmounted(() => {
    console.log("UpdateHook 컴포넌트가 언마운트 됨");
});

onBeforeUpdate(() => {
    console.log("UpdateHook 컴포넌트가 업데이트 직전임");
});

onUpdated(() => {
    console.log("UpdateHook 컴포넌트가 업데이트 됨");
    //  count.value += 1; --> 업데이트 함수에 상태 변경 코드를 넣으면 안된다! // 무한반복. 에러남.
    // 자기 자신의 상태 변경에도 또 일어나기 때문에 무한반복이 일어난다는 것이다.
});
</script>

<style scoped></style>

 

 


 

# 실습해보자.

 

1. Ch06LifecycleHook폴더 아래 Exam02DomElementRef 폴더를 만들고 index.vue 생성 (기초 코드)

2. router폴더 - Ch06LifecycleHook.js에 routes 코드 추가

3. AppMenu 리스트 하나 추가

 

* index.vue 

<template>
    <div class="card">
        <div class="card-header">Exam02DomElementRef</div>
        <div class="card-body">
            <div>
                <h6>[DOM 요소 참조]</h6>
                <p>data : {{ data }}</p>
                <div class="d-flex">
                    <!-- v-model을 사용하여(양방향) 값을 변경하는 것이 아닌
                        input의 value값을 이용하여 data의 값을 변경해보자!! -->
                    <input id="input1" ref="input1Ref" type="text" style="line-height: 25px;" />
                    <button class="btn btn-info btn-sm ms-3" @click="setData">입력 양식값 변경</button>
                </div>
            </div>
        </div>
    </div>
</template>

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

// 상태 생성
const data = ref("summer");
const input1Ref = ref(null);

// setData 함수는 Mounted된 이후에 실행이 되는 것일까, Mounted되기 전에 실행이 된 상태일까??
// setData는 마운트되고 난 후 UI를 구성하고, 사용자가 값을 입력할 때 일어나는 것이기 때문에
// 마운트 되고 난 후에 실행되는 것이라 해석할 수 있다.
function setData() {
    // 방법 1
    // const input1 = document.querySelector('#input1');
    // data.value = input1.value;

    // 방법 2
    // input1Ref은 상태를 나타낸다. input1Ref.value는 상태의 값을 나타내고
    // input1Ref.value.value는 상태의 값의 value속성 값을 나타낸다. (???)
    data.value = input1Ref.value.value;
}

</script>

<style scoped></style>

 


Ch07. Vuex 상태 관리

 

컴포넌트에서의 상태란?

 

Vuex를 이용하면 이러한 상태(중첩)를 관리하기 편리함.

# 사실 우리가 프로젝트를 설치할 때, Vuex를 체크하여 설치할 수 있었다!

(순차적 실습으로 인한 교육과 이를 학습하기 위해)

 

# 이제부터는 Vuex를 추가하여 할 것이다.

 

1. 실행 중인 서버 중지 (ctrl + c) - Y

2. 터미널에 vue add vuex

설치하고 나면

src / store / index.js가 생성되고,

src / main.js의 코드가 변화되어 있다.

import { createApp } from 'vue' // createApp은 export로 내보낸 것.
import App from './App.vue' // App은 export default로 지정되어 있기 때문에 {}로 나타내지 않음
import router from './router/index.js'

// Bootstrap 관련 JavaScript와 CSS 로딩
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import store from './store'

// App컴포넌트를 <div id="app"></div>에 내용으로 추가
createApp(App).use(store).use(router).mount('#app') // index.html에서 id가 app인 태그에 적용

 

왼쪽 - 비동기적

오른쪽 - 동기적 (상태 추적이 가능하도록 변화를 커밋)

Devtools -- developer

 

 

getUserId 안의 매개값 4개는 상위의 ???

 

payload값은 단일 값일 수도 있고, 객체의 값일 수도 있다.

 

# 실습해보자.

 

1. views 폴더 아래 Ch07VuexStateManagement 폴더 생성 후 Exam01RootState.vue 생성

2. router 폴더 아래 Ch07VuexStateManagement.js 작성

3. router 폴더 아래 index.js에 구조할당분해 방식으로 추가

4. components 폴더 아래 AppMenu.vue에 아코디언과 리스트 추가.

 

## store 폴더 아래 index.js의 state값을 변경해보며 컴포넌트에 출력해보자.

 

# store 폴더 - index.js

import { createStore } from 'vuex'

/*export default createStore({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})*/

// Store 객체를 생성
const store = createStore({
  // 루트 상태를 정의
  state: {
    userId: "znznfltm@gmail.com",
  },
  //루트 상태 값을 읽는 메소드(Getter) 정의
  getters: {
    getUserId(state, getters, rootState, rootGetters){
      /*
      root의 개념
      */
      return state.userId;
    },
  },
  //루트 상태 값을 변화시키는 메소드(Setter) 정의(동기 방식)
  mutations: {
    setUserId(state, payload){ // payload는 변경할 값을 가지고 있는 객체 (전달될 데이터)
      state.userId = payload;
    },
  },
  //비동기 작업을 실행하고 결과에 따라 상태 값을 변화시키는 메소드 정의 
  actions: { // 서버 통신 작업을 많이 할 것이다.
    loginAction(context, payload) { // login작업이니, payload에는 아이디와 비밀번호의 값을 가져올 것이다.
      // 로그인을 한다치면 서버와 통신 작업을 할 것이다.
      // ... 3초를 소요한다면? 다른 작업을 생각하여 비동기로 처리해야 한다. promise 개념
      new Promise((resolve, reject) => {
        // 이 자리에 서버 통신 코드가 들어갈 것이다.
        // 이것을 이용하여 3초라는 시간이 소요되는 시간에 다른 작업을 할 수 있는 것이다.
        
        if(true){
          // 로그인 성공을 했을 경우에
          resolve ({result:"success", userId: payload.userId});
        } else {
          // 로그인 실패를 했을 경우
          resolve ({result:"fail", reason:"password is wrong"});
        }

        // 성공했을 때 resolve함수 내의 매개값들이 변수 data로 들어가게 되고,
        // 실패했을 경우 resolve함수 내의 매개값들이 error로 들어가게 된다.
      })
      .then((data) => { // data는? : 
        // 작업이 성공적으로 처리가 되었을 경우 실행
        // 상태 변경 작성 작업을 한다.
        console.log("login 성공");
        context.commit("setUserId", data.userId); // mutations의 셋터함수와 userId
      })
      .catch((error) => { 
        // 작업이 실패처리가 될 경우 실행
        console.log("login 실패");

      });
    }
  },
  //루트 하위 상태 모듈 추가
  modules: {
  }
});

// Store 객체를 내보내기
export default store;

 

# Exam01RootState.vue

<template>
    <div class="card">
        <div class="card-header">Exam01RootState</div>
        <div class="card-body">
            <h4><b>[루트 상태 읽기]</b></h4>
            <p>userId 단방향 바인딩 : {{ $store.state.userId }}</p>
            <p>userId 단방향 바인딩 : {{ $store.getters.getUserId }}</p> <!-- {{ $store.getters.getUserId() }} 함수호출코드로 쓰면 에러남 -->
            <p>userId 단방향 바인딩 : {{ getUserId() }}</p>
            <!-- computed를 이용하여 바인딩 해보자. 이것이 더 효율적? -->
            <p>userId 단방향 바인딩 : {{ computedUserId }}</p>
            <p>userId 양방향 바인딩 : <input type="text" v-model="$store.state.userId"/></p>

            <hr>

            <h4><b>[루트 상태 변경]</b></h4>
            <button class="btn btn-secondary btn-sm me-3" @click="changeByMutation">userId 변경(Mutation을 이용한 동기 방식)</button>
            <button class="btn btn-info btn-sm" @click="changeByAction">userId 변경(Action을 이용한 비동기 방식)</button>
        </div>
    </div>
</template>

<script setup>
import { computed } from 'vue';
import {useStore} from 'vuex';
const store = useStore(); // 이렇게 Store객체를 얻을 수 있다!

function getUserId() {
    return store.state.userId;
}

const computedUserId = computed(() => {
    // return store.state.userId;
    return store.getters.getUserId;
});

/*
    Mutation 방식과 Actions 방식의 차이.. 어떤 것이 더 유리할까?
    Mutation 방식은 아래의 코드에서 처럼 쓸 것이 많다. 하지만 가독성 면에서 유리하다.

    우리가 프로젝트에서 비동기적인 방법으로 UI를 구성하고 싶다면
    Actions 방식으로(필요시) 할 수도 있지만, 필수는 아니다.
    Mutation 방식(세터)으로 값의 변화로만 사용할 수 있다.

    Actions 안에 세터(Mutation)를 이용하여 값을 변경하는 코드가 있다.
    어찌 저찌 됐든 값의 상태 변화를 나타내려면 Mutation(세터)은 필요하다.
*/


// 스프링 프로젝트에서 해당 jsp에서 AJAX를 작성하고 데이터를 전송했던 것처럼
// 내가 해당 컴포넌트에서 버튼을 눌렀을 때 데이터를 전송 받는 방식이 Mutation
function changeByMutation() {
    new Promise((resolve, reject) => {
        if(true){
          resolve ({result:"success", userId:"spring"});
        } else {
          resolve ({result:"fail", reason:"password is wrong"});
        }
      })
      .then((data) => { // data는? : 
        console.log("login 성공");
        // 컴포넌트에서 Mutations를 사용하려면 commit을 해야한다.
        store.commit("setUserId", data.userId); // mutations의 셋터함수와 userId
      })
      .catch((error) => { 
        console.log("login 실패");
      });
}


function changeByAction() {
    // 컴포넌트에서 Actions를 사용하려면 Dispatch를 해야한다. ppt 103p
    store.dispatch("loginAction", {userId: "fall"});  
    // {userId: "fall"} 은 payload
}

</script>


<style scoped>
div {
    border: 1px solid red;
}
</style>

 

 

 

컴포넌트에서 mutation으로 상태를 직접적으로 commit하여 변경하는 할 수 있다. (동기)

actions는 단순한 행위를 의미함. (비동기)

 

 

## 행위로 인한 상태값 변환...

 

# 정리

1. state 등록 (필수)

2. 게터와 셋터 작성 (필수)

3. 액션은 선택 사항.. 꼭 만들 필요는 없다. Actions를 이용하고 싶을 때 사용.. 

- Actions를 사용해야 할 때가 있을까??? --> 지연시간이 있는 경우!! 

- Actions안에서 데이터를 전달받아 Mutation을 이용하여 상태를 변화시킬 수 있다.

 

 

★ namespaced: true를 추가해주어야 한다.

최종적으로 root 상태의 값 개수가 합쳐진다.

여기서 실수가 발생할 수 있다. 상태 값이 합쳐짐에 있어서 똑같은 변수의 이름이거나 상태 값이 변경될 수 있다는 것.

 

# 실습 해보자.

 

1. store 폴더 아래에 counter.js 생성

☆ namespaced: true를 추가해주어야 한다.

export default {
  namespaced: true,

  state: {
    count:0
  },
  getters: {
    // state는 자신의 state를 참조. 위의 count
    // getters는 자신의 getters를 참조
    // rootState는 index.js의 State를 참조
    // rootGetters는 index.js의 Getters를 참조
    getCount(state, getters, rootState, rootGetters) {
      return state.count;
    }
  },
  mutations: {
    setCount(state, payload) {
      state.count = state.count + payload;
    }
  },
  actions: {

  }
};

 

2. Ch07VuexStateManagement 폴더 아래 Exam02CounterState.vue 생성

<template>
    <div class="card">
        <div class="card-header">Exam02CounterState</div>
        <div class="card-body">
            <h4><b>[counter 상태 읽기]</b></h4>
            <!-- $store.state 까지는 root상태. 
                counter는 store의 index.js module에서 정의한 하위 모듈의 이름 -->
            <p>count 단방향 바인딩 : {{ $store.state.counter.count }}</p>
            <p>count 단방향 바인딩 : {{ $store.getters["counter/getCount"] }}</p>
            <p>count 단방향 바인딩 : {{ getCounter() }}</p>
            <p>count 단방향 바인딩 : {{ computedCount }}</p>
            <p>count 양방향 바인딩 : <input type="number" v-model="$store.state.counter.count" /></p>

            <hr>

            <h4><b>[루트 하위 counter 상태 변경]</b></h4>
            <button class="btn btn-secondary btn-sm me-3" @click="changeByMutation">count 변경(Mutation을 이용한 동기 방식)</button>
            <button class="btn btn-info btn-sm" @click="changeByAction">count 변경(Action을 이용한 비동기 방식)</button>
        </div>
    </div>
</template>

<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';

const store = useStore();
// computed를 사용하면, 상태가 변할 때만! 값을 가져오고, 안변하면 캐시에서 가져온다.
const computedCount = computed(() => {
    return store.state.counter.count;
});

function getCounter() {
    return store.state.counter.count;   // 이렇게도 가져올 수 있는데 굳이 getter가 필요할까?
    // return store.getters["counter/getCount"];
}

function changeByMutation() {
    return store.commit("counter/setCount", 1); // 1값은 payload로 전달된다!
}

function changeByAction() {
    store.dispatch("counter/addAction", 1);
}

</script>


<style scoped>
div {
    border: 1px solid red;
}
</style>

 

+ Recent posts