241125 뷰 (CDN, 라이프사이클, 컴포넌트, defer, 디렉티브)

241125 에이콘 아카데미 수업에 기반하여 작성되었음을 알립니다.

뷰(Vue)

뷰는 비교적 쉬운 방법으로 수업 진행 예정
뷰는 2점대 버전과 3점대 버전이 있다. 2점대 버전은 볼 필요가 없다. 3점대에서 안돌아감
MVVM 패턴을 사용한다. 뷰와 모델 사이에 뷰 모델을 하나 더 걸어놓았다.
리액트와 마찬가자기로 버츄얼돔을 사용한다.
노드를 설치하여 사용, 비쥬얼 스튜디오로 개발, HTML에 CDN을 걸어놓고 사용한다.
보간법을 사용하고 있다. {{여기에 써주면 된다.}}
기본 디렉티브는 v-~ 써주면된다. 타임리프에서 th 걸어주듯이
뷰 인스턴스는 2점대와 3점대에서 많이 다르다!
리액트처럼 단일 컴포넌트로 개발하는 것도 좋다. 하지만 CDN만으로도 결과물이 잘 나올 수 있다.
자바스크립트 라이브러리는 모두 다 비슷한 구조를 가지고 있다.

Virtual Dom 활용으로 양방향 바인딩 처리 등 가능, 단방향도 가능!

Single File Component Vue는 단일 파일 컴포넌트 기반의 프레임워크다. 여러 컴포넌트를 조립하는 것이다.

HTML 기반 템플릿 구문 데이터 바인딩의 기본 형태는 "Mustache" 구문(이중 중괄호)을 사용하는 Text Interpolation(텍스트 보간)이다.

뷰도 리액트와 마찬가지로 커뮤니티가 활성화 되어있다. HomePage : https://v3-docs.vuejs-korea.org/

개발 환경 설정 및 실행 방법 두 가지
https://lifere.tistory.com/entry/Vuejs-Vue-3-Vuejs-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%84%A4%EC%B9%98-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85-CLI-CDN

MVVM 패턴과 vue proxy에 관해 정리해보자!

vue proxy

뷰 (CDN)

작업 폴더 생성

ex1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- jQuery cdn 사용 -->
    <script src="https://code.jquery.com/jquery-latest.js"></script>
    <script type="text/javascript">
        $(document).ready(function(){
            $("#msg").text("vue도 jQuery처럼 cdn으로 작업이 가능해요!");
        });
    </script>
</head>
<body>
    jQuery 출력 결과 : <span id="msg"></span>
</body>
</html>

이렇게 jQuery 출력하듯이 vue도 cdn으로 가능하다. 아래 예제를 통해 알아보자!

CDN

html 파일내에 vue CDN을 추가해준다.

<script src="https://unpkg.com/vue@3"></script>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- jQuery cdn 사용 -->
    <script src="https://code.jquery.com/jquery-latest.js"></script>
    <script type="text/javascript">
        $(document).ready(function(){
            $("#msg").text("vue도 jQuery처럼 cdn으로 작업이 가능해요!");
        });
    </script>

    <!-- vue cdn 사용 -->
     <script src="https://unpkg.com/vue@3"></script>
</head>
<body>
    jQuery 출력 결과 : <span id="msg"></span>
    <hr/>
    vue는 M(Model)V(View)VM(ViewModel)로 이어지는 소프트웨어 아키텍처 패턴을 사용한다.<br/>
    vue 출력 결과 : <span id="app1">{{msg}}</span> <!-- view : 출력 담당 -->
    <script>
        const hi = {msg:"뷰 세계에 오신 것을 환영해요!"}; // MVVM 패턴에서 Model 영역이다. 데이터만 가진다. 키밸류형식으로 객체 생성
       
        Vue.createApp({ // ViewModel : 상태와 연산을 담당
            // proxy 객체로써 반응성을 제공하는 기본요소!
            // Model 객체값을 변경하면 즉시 view(클라이언트 화면)을 갱신한다.
            // 그래서 객체를 반환하는 함수를 작성 (data())
            data() { 
                return hi;
            }
        }).mount("#app1"); // mount 메소드로 현재 앱(객체)을 id가 app1인 DOM 요소에 연결
    </script>
</body>
</html>

 

자체적인 문법으로 변경되었다.


뷰 (라이프사이클)

mounted el 속성에서 지정한 화면 요소에 인스턴스가 부착되고 난 후 호출되는 단계로 template 속성에서 정의한 화면 요소에 접근 가능. 화면 요소를 제어하는 코드를 구현하며 DOM에 인스턴스가 부착되자마자 호출되기 때문에 하위 컴포넌트나 외부 라이브러리에 의해 추가된 화면 요소들이 최종 html 코드로 변환되는 시점과 다를 수 있음

beforeUpdate el 속성에서 지정한 화면 요소에 인스턴스가 부착되고 난 후에 인스턴스 속성들이 화면에 치환됨 치환된 값을 $watch 속성으로 감시 (관찰) 관찰하고 있는 데이터가 변경되면 가상 돔을 이용해 화면을 다시 그려야 함. 이때, 그리기 직전 호출되는 단계가 beforeUpdate 변경 예정인 데이터 값을 이용해 작업을 해야할 때 이 단계에서 로직 구현. 변경하는 로직 구현해도 다시 화면에 그려지지는 않음

updated beforeUpdate가 끝나고 화면에 다시 그리고 나면 실행되는 단계로 데이터가 변경되고 화면 요소를 제어하고 싶을 때 이 단계에서 로직이 구현됨. 그러나 beforeUpdate -> updated -> beforeUpdate -> .. 무한루프될 가능성 있음 데이터 값 갱신하는 로직은 beforeUpdate에서 처리하는게 좋음

ex1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- jQuery cdn 사용 -->
    <script src="https://code.jquery.com/jquery-latest.js"></script>
    <script type="text/javascript">
        $(document).ready(function(){
            $("#msg").text("vue도 jQuery처럼 cdn으로 작업이 가능해요!");
        });
    </script>

    <!-- vue cdn 사용 -->
     <script src="https://unpkg.com/vue@3"></script>
</head>
<body>
	... 생략
    <hr/>
    vue 출력 결과 2 : 
    <div id="app2">
        <span>
            {{message1}}&nbsp;&nbsp;
            {{message2}}&nbsp;&nbsp;
            {{message3}}&nbsp;&nbsp;
        </span>
    </div>
    <script>
        const {createApp} = Vue; // 뷰 생성자를 사용하지않고 만들수도있다.
        createApp({
            data() {
                return {
                    message1 : "안녕 세상아!",
                    message2 : "안녕" + "반갑다",
                    message3 : Math.random() * 9
                }
            },

            // lifecycle(생명주기)은 컴포넌트가 생성된 후 제거될 때 까지의 흐름을 말함
            // 각 생명주기 메소드마다 실행할 수 있는 이벤트훅을 등록할 수 있다. 
            beforeCreate(){ 
              // 인스턴스가 생성되고 나서 가장 처음으로 실행
              console.log("beforeCreate");
            },
            created(){
                // 아직 화면 요소에 인스턴스가 부착되기 전 실행
                console.log("created");
            },
            mounted(){
                // 화면 요소를 제어하는 코드를 구현하며 DOM에 인스턴스가 부착되자마자 호출
                console.log("mounted");
            },
            beforeUpdate(){
                // 관찰하고 있는 데이터가 변경되면 가상 돔을 이용해 화면을 다시 그려야 하는데 그리기 직전 호출되는 단계
                console.log("beforeUpdate");
            },
            updated(){
                // beforeUpdate가 끝나고 화면에 다시 그리고 나면 실행되는 단계
                console.log("updated");
            }
        }).mount("#app2");
    </script>
</body>
</html>

beforeCreate, created, mounted까지만 확인이 가능하다.

updated, beforeUpdate를 확인하려 코드를 추가해주자.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- jQuery cdn 사용 -->
    <script src="https://code.jquery.com/jquery-latest.js"></script>
    <script type="text/javascript">
        $(document).ready(function(){
            $("#msg").text("vue도 jQuery처럼 cdn으로 작업이 가능해요!");
        });
    </script>

    <!-- vue cdn 사용 -->
     <script src="https://unpkg.com/vue@3"></script>
</head>
<body>
    ... 생략
    vue 출력 결과 2 : 
    <div id="app2">
        <span>
            {{message1}}&nbsp;&nbsp;
            {{message2}}&nbsp;&nbsp;
            {{message3}}&nbsp;&nbsp;
        </span>
        <br/>
        <button @click="updateMessage">메세지 변경</button>
    </div>
    <script>
        const {createApp} = Vue; // 뷰 생성자를 사용하지않고 만들수도있다.
        createApp({
            data() {
                return {
                    message1 : "안녕 세상아!",
                    message2 : "안녕" + "반갑다",
                    message3 : Math.random() * 9
                }
            },
            methods:{
                updateMessage(){
                    this.message1 = "update hello";
                    this.message2 = "this is vue";
                    this.message3 = Math.random() * 9
                }
            },

            // lifecycle(생명주기)은 컴포넌트가 생성된 후 제거될 때 까지의 흐름을 말함
            // 각 생명주기 메소드마다 실행할 수 있는 이벤트훅을 등록할 수 있다. 
            beforeCreate(){ 
              // 인스턴스가 생성되고 나서 가장 처음으로 실행
              console.log("beforeCreate");
            },
            created(){
                // 아직 화면 요소에 인스턴스가 부착되기 전 실행
                console.log("created");
            },
            mounted(){
                // 화면 요소를 제어하는 코드를 구현하며 DOM에 인스턴스가 부착되자마자 호출
                console.log("mounted");
            },
            beforeUpdate(){
                // 관찰하고 있는 데이터가 변경되면 가상 돔을 이용해 화면을 다시 그려야 하는데 그리기 직전 호출되는 단계
                console.log("beforeUpdate");
            },
            updated(){
                // beforeUpdate가 끝나고 화면에 다시 그리고 나면 실행되는 단계
                console.log("updated");
            }
        }).mount("#app2");
    </script>
</body>
</html>

update확인


뷰 컴포넌트 (글로벌, 로컬)

ex2globallocal.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3"></script>
  </head>
  <body>
    <!-- vue 컴포넌트는 global((여러 파일에서 공유), local(등록된 파일에서만 사용)이 있다. -->
    <div id="abasic">
        <hello></hello> <!-- hello 컴포넌트 사용 -->
    </div>
    <div id="abasic2">
        <world></world> <!-- world 컴포넌트 사용 -->
        <world></world>
    </div>

    <script>
        const {createApp} = Vue;
        const abasic = createApp({});

        abasic.component("hello", { // hello 컴포넌트를 전역으로 등록, 첫번째 인자에 태그명(컴포넌트명?)을 적어줌
            template:"<h1>{{title}}</h1>", // template : 출력 담당, HTML, CSS 마크업 요소를 정하는 곳, 
            data() {
                return {
                    title:"헬로", // 데이터
                }
            }
        });
        abasic.mount("#abasic");
    </script>
  </body>
</html>

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3"></script>
  </head>
  <body>
  	... 생략
        // 위와 같은 방법으로 만들어주면 된다.ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
        const abasic2 = createApp({});
        abasic2.component("world", { // world 컴포넌트 생성
            template:"<h3>{{title}}</h3>",
            data() {
                return {
                    title:"바이",
                }
            }
        });
        abasic2.mount("#abasic2");
    </script>
  </body>
</html>

 

 

지역변수값을 각각 수정해보자!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3"></script>
  </head>
  <body>
    <!-- vue 컴포넌트는 global((여러 파일에서 공유), local(등록된 파일에서만 사용)이 있다. -->
    <div id="abasic">
        <hello></hello> <!-- hello 컴포넌트 사용 -->
    </div>
    <div id="abasic2">
        <world></world> <!-- world 컴포넌트 사용 -->
        <world></world>
    </div>

    <script>
		... 생략
        const abasic2 = createApp({});
        abasic2.component("world", { // world 컴포넌트 생성
            template:"<h3>{{title}}<button @click='changeTitle'>타이틀 변경</button></h3>",
            data() {
                return {
                    title:"바이",
                }
            },
            methods:{
                changeTitle(){
                    this.title = "오늘은 뷰 구경하는 날";
                }
            }
        });
        abasic2.mount("#abasic2");
    </script>
  </body>
</html>

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3"></script>
  </head>
  <body>
	... 생략
    <!------------------------------>
    <hr color="red" size="5px"/>
    <div id="app">
        <div>컴포넌트 등록1</div>
        <my-global-component></my-global-component>
        <my-local-component></my-local-component>
    </div>

    <script>
        // 전역 컴포넌트를 등록하는 공용 컴포넌트 정의
        const myGlobalComponent = {
            template:"<b>전역 컴포넌트가 등록</b>",

        }
        const appli = Vue.createApp({
            components:{
                "my-global-component":myGlobalComponent, // 전역 컴포넌트 등록
                "my-local-component":{ // 지역 컴포넌트 등록
                    template:"<br>지역 컴포넌트가 등록 1</br>",
                },
            },
        });
        appli.mount("#app");
    </script>
  </body>
</html>

하나 더 만들어보자!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3"></script>
  </head>
  <body>
	.. 생략
    <!------------------------------>
    <hr color="red" size="5px"/>
    <div id="app">
        <div>컴포넌트 등록1</div>
        <my-global-component></my-global-component>
        <my-local-component></my-local-component>
    </div>
    <hr/>
    <div id="app2">
        <div>컴포넌트 등록2</div>
        <my-global-component></my-global-component>
        <my-local-component></my-local-component>
    </div>

    <script>
        // 전역 컴포넌트를 등록하는 공용 컴포넌트 정의
        const myGlobalComponent = {
            template:"<b>전역 컴포넌트가 등록되었습니다.</b>",

        }
        const appli = Vue.createApp({
            components:{
                "my-global-component":myGlobalComponent, // 전역 컴포넌트 등록
                "my-local-component":{ // 지역 컴포넌트 등록
                    template:"<br>지역 컴포넌트가 등록 1</br>",
                },
            },
        });
        appli.mount("#app");

        const appli2 = Vue.createApp({
            components:{
                "my-global-component":myGlobalComponent, // 전역 컴포넌트 등록
                "my-local-component":{ // 지역 컴포넌트 등록
                    template:"<br>지역 컴포넌트가 등록 2</br>",
                },
            },
        });
        appli2.mount("#app2");
    </script>
  </body>
</html>

전역 컴포넌트, 지역컴포넌트의 출력 내용이 다른 것을 확인 할 수 있다.

하나 더 만들어 보자!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://unpkg.com/vue@3"></script>
  </head>
  <body>
		.. 생략
    <!------------------------------>
    <hr color="red" size="5px"/>
    <div id="app">
        <div>컴포넌트 등록1</div>
        <my-global-component></my-global-component>
        <my-local-component></my-local-component>
    </div>
    <hr/>
    <div id="app2">
        <div>컴포넌트 등록2</div>
        <my-global-component></my-global-component>
        <my-local-component></my-local-component>
    </div>
    <hr/>
    <div id="app3">
        <div>컴포넌트 등록3</div>
        <my-global-component></my-global-component>
        <my-local-component></my-local-component>
    </div>
    <script>
        // 전역 컴포넌트를 등록하는 공용 컴포넌트 정의
        const myGlobalComponent = {
            template:"<b>전역 컴포넌트가 등록되었습니다.</b>",

        }
        const appli = Vue.createApp({
            components:{
                "my-global-component":myGlobalComponent, // 전역 컴포넌트 등록
                "my-local-component":{ // 지역 컴포넌트 등록
                    template:"<br>지역 컴포넌트가 등록 1</br>",
                },
            },
        });
        appli.mount("#app");

        const appli2 = Vue.createApp({
            components:{
                "my-global-component":myGlobalComponent, // 전역 컴포넌트 등록
                "my-local-component":{ // 지역 컴포넌트 등록
                    template:"<br>지역 컴포넌트가 등록 2</br>",
                },
            },
        });
        appli2.mount("#app2");

        const appli3 = Vue.createApp({
            components:{
                "my-global-component":myGlobalComponent, // 전역 컴포넌트 등록
                "my-local-component":{ // 지역 컴포넌트 등록
                    template:"<br><i>지역 컴포넌트가 등록 3</i></br>",
                },
            },
        });
        appli3.mount("#app3");
    </script>
  </body>
</html>

전역 컴포넌트는 모두 동일, 지역 컴포넌트는 다르게 출력되는 것을 확인할 수 있다.

컴포넌트 운영에 관해 이해가 필요하다!


defer 속성

ex1.html으로 돌아가보자.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- jQuery cdn 사용 -->
    <script src="https://code.jquery.com/jquery-latest.js"></script>
    <script type="text/javascript">
      $(document).ready(function () {
        $("#msg").text("vue도 jQuery처럼 cdn으로 작업이 가능해요!");
      });
    </script>

    <!-- vue cdn 사용 -->
    <script src="https://unpkg.com/vue@3"></script>
    <script>
      const hi = { msg: "뷰 세계에 오신 것을 환영해요!" }; // MVVM 패턴에서 Model 영역이다. 데이터만 가진다. 키밸류형식으로 객체 생성

      Vue.createApp({
        // ViewModel : 상태와 연산을 담당
        // proxy 객체로써 반응성을 제공하는 기본요소!
        // Model 객체값을 변경하면 즉시 view(클라이언트 화면)을 갱신한다.
        // 그래서 객체를 반환하는 함수를 작성 (data())
        data() {
          return hi;
        },
      }).mount("#app1"); // mount 메소드로 현재 앱(객체)을 id가 app1인 DOM 요소에 연결
    </script>

    <script>
      const { createApp } = Vue; // 뷰 생성자를 사용하지않고 만들수도있다.
      createApp({
        data() {
          return {
            message1: "안녕 세상아!",
            message2: "안녕" + "반갑다",
            message3: Math.random() * 9,
          };
        },

        methods: {
          updateMessage() {
            this.message1 = "update hello";
            this.message2 = "this is vue";
            this.message3 = Math.random() * 9;
          },
        },
        // lifecycle(생명주기)은 컴포넌트가 생성된 후 제거될 때 까지의 흐름을 말함
        // 각 생명주기 메소드마다 실행할 수 있는 이벤트훅을 등록할 수 있다.
        beforeCreate() {
          // 인스턴스가 생성되고 나서 가장 처음으로 실행
          console.log("beforeCreate");
        },
        created() {
          // 아직 화면 요소에 인스턴스가 부착되기 전 실행
          console.log("created");
        },
        mounted() {
          // 화면 요소를 제어하는 코드를 구현하며 DOM에 인스턴스가 부착되자마자 호출
          console.log("mounted");
        },
        beforeUpdate() {
          // 관찰하고 있는 데이터가 변경되면 가상 돔을 이용해 화면을 다시 그려야 하는데 그리기 직전 호출되는 단계
          console.log("beforeUpdate");
        },
        updated() {
          // beforeUpdate가 끝나고 화면에 다시 그리고 나면 실행되는 단계
          console.log("updated");
        },
      }).mount("#app2");
    </script>
  </head>
  <body>
    jQuery 출력 결과 : <span id="msg"></span>
    <hr />
    vue는 M(Model)V(View)VM(ViewModel)로 이어지는 소프트웨어 아키텍처 패턴을
    사용한다.<br />
    vue 출력 결과 :
    <span id="app1">{{msg}}</span> <!-- view : 출력 담당 -->
    <hr />
    vue 출력 결과 2 :
    <div id="app2">
      <span>
        {{message1}}&nbsp;&nbsp; {{message2}}&nbsp;&nbsp;
        {{message3}}&nbsp;&nbsp;
      </span>
      <br />
      <button @click="updateMessage">메세지 변경</button>
    </div>
  </body>
</html>

바디 태그내에 자바스크립트를 헤더로 옮겼더니 적용이 안되는 모습을 볼 수 있다.

돔이 바디를 완전히 파싱을 끝내줘야 적용이 가능하다 파싱이 덜 끝나서 저렇게 됨

자바스크립트를 비동기적으로 다운로드 시켜줘야겠다.

바디태그 파싱이 끝난 후 스크립트가 적용되도록 defer 속성을 써주면 됨
<script> 태그내에 defer를 걸어주고,
document.addEventListener("DOMContentLoaded", function () {
               내부에 코드를 적어준다.
});
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- jQuery cdn 사용 -->
    <script src="https://code.jquery.com/jquery-latest.js"></script>
    <script type="text/javascript">
      $(document).ready(function () {
        $("#msg").text("vue도 jQuery처럼 cdn으로 작업이 가능해요!");
      });
    </script>

    <!-- vue cdn 사용 -->
    <script src="https://unpkg.com/vue@3"></script>
    <script defer> // <script> 태그의 defer 속성은 페이지가 모두 로드된 후에 해당 외부 스크립트가 실행됨을 명시
      document.addEventListener("DOMContentLoaded", function(){
        const hi = { msg: "뷰 세계에 오신 것을 환영해요!" }; // MVVM 패턴에서 Model 영역이다. 데이터만 가진다. 키밸류형식으로 객체 생성

      Vue.createApp({
        // ViewModel : 상태와 연산을 담당
        // proxy 객체로써 반응성을 제공하는 기본요소!
        // Model 객체값을 변경하면 즉시 view(클라이언트 화면)을 갱신한다.
        // 그래서 객체를 반환하는 함수를 작성 (data())
        data() {
          return hi;
        },
      }).mount("#app1"); // mount 메소드로 현재 앱(객체)을 id가 app1인 DOM 요소에 연결
      });
    </script>

    <script defer>
      document.addEventListener("DOMContentLoaded", function(){
        const { createApp } = Vue; // 뷰 생성자를 사용하지않고 만들수도있다.
      createApp({
        data() {
          return {
            message1: "안녕 세상아!",
            message2: "안녕" + "반갑다",
            message3: Math.random() * 9,
          };
        },

        methods: {
          updateMessage() {
            this.message1 = "update hello";
            this.message2 = "this is vue";
            this.message3 = Math.random() * 9;
          },
        },
        // lifecycle(생명주기)은 컴포넌트가 생성된 후 제거될 때 까지의 흐름을 말함
        // 각 생명주기 메소드마다 실행할 수 있는 이벤트훅을 등록할 수 있다.
        beforeCreate() {
          // 인스턴스가 생성되고 나서 가장 처음으로 실행
          console.log("beforeCreate");
        },
        created() {
          // 아직 화면 요소에 인스턴스가 부착되기 전 실행
          console.log("created");
        },
        mounted() {
          // 화면 요소를 제어하는 코드를 구현하며 DOM에 인스턴스가 부착되자마자 호출
          console.log("mounted");
        },
        beforeUpdate() {
          // 관찰하고 있는 데이터가 변경되면 가상 돔을 이용해 화면을 다시 그려야 하는데 그리기 직전 호출되는 단계
          console.log("beforeUpdate");
        },
        updated() {
          // beforeUpdate가 끝나고 화면에 다시 그리고 나면 실행되는 단계
          console.log("updated");
        },
      }).mount("#app2");
      });   
    </script>
  </head>
  <body>
	... 생략
  </body>
</html>

별도 자바스크립트 파일로 빼기!

ex1js.js

document.addEventListener("DOMContentLoaded", function () {
  const { createApp } = Vue; // 뷰 생성자를 사용하지않고 만들수도있다.
  createApp({
    data() {
      return {
        message1: "안녕 세상아!",
        message2: "안녕" + "반갑다",
        message3: Math.random() * 9,
      };
    },

    methods: {
      updateMessage() {
        this.message1 = "update hello";
        this.message2 = "this is vue";
        this.message3 = Math.random() * 9;
      },
    },
    // lifecycle(생명주기)은 컴포넌트가 생성된 후 제거될 때 까지의 흐름을 말함
    // 각 생명주기 메소드마다 실행할 수 있는 이벤트훅을 등록할 수 있다.
    beforeCreate() {
      // 인스턴스가 생성되고 나서 가장 처음으로 실행
      console.log("beforeCreate");
    },
    created() {
      // 아직 화면 요소에 인스턴스가 부착되기 전 실행
      console.log("created");
    },
    mounted() {
      // 화면 요소를 제어하는 코드를 구현하며 DOM에 인스턴스가 부착되자마자 호출
      console.log("mounted");
    },
    beforeUpdate() {
      // 관찰하고 있는 데이터가 변경되면 가상 돔을 이용해 화면을 다시 그려야 하는데 그리기 직전 호출되는 단계
      console.log("beforeUpdate");
    },
    updated() {
      // beforeUpdate가 끝나고 화면에 다시 그리고 나면 실행되는 단계
      console.log("updated");
    },
  }).mount("#app2");
});

ex1.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- jQuery cdn 사용 -->
    <script src="https://code.jquery.com/jquery-latest.js"></script>
    <script type="text/javascript">
      $(document).ready(function () {
        $("#msg").text("vue도 jQuery처럼 cdn으로 작업이 가능해요!");
      });
    </script>

    <!-- vue cdn 사용 -->
    <script src="https://unpkg.com/vue@3"></script>
	... 생략
    
    <!-- 별도 작성 js 문서 호출 -->
    <script src="ex1js.js" defer></script>
  </head>
  <body>
		... 생략
  </body>
</html>

정상 작동하는 것을 확인


뷰 디렉티브

더보기

ex3directive.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@3"></script>
</head>
<body>
    directive : DOM 엘리먼트가 뭔가를 수행하도록 지시하는 특수한 토큰이다. 접두어가 v- 이다.
    <br/>
    <h2>directive 속성 연습</h2>
    <div id="app">
        <a href={{link}}>다음 (html 속성에서는 {{}} 사용 X)</a>
        <br/>
        <!-- 일방적으로 데이터를 받고 있다. -->
        <a v-bind:href="link">다음 - 단방향으로 데이터에 접근</a>
        <br/>
        <a :href="link">다음 - v-bind 생략 가능</a>
        <br/>
        <img v-bind:src="imageSrc" alt="이미지" />
        <br/>
        <h2 v-text="say"></h2> <!-- html의 innerText, 타임리프의 th:text라고 생각하자 -->
        <h2>{{say}}</h2> <!-- 보간법 사용해도 괜찮다. -->
        <h2 v-once>data 속성 값 : {{say}}</h2> <!-- 처음 1회만 바인딩된 후 1회만 렌더링된다. 업데이트 안됨 -->
        <h2>data 속성 값 : {{sayFunc()}}</h2>
        <br/>
        <!-- 텍스트 형태로 출력 또는 파싱 -->
        <span v-text="aLinkToNaver"></span><br/> <!-- innerText -->
        <span v-html="aLinkToNaver"></span> <!-- innerHTML -->
        <hr/>
        <!-- 양방향 데이터 바인딩, 엘리먼트를 보고 양방향이 성립될 지 안될 지 판단하여 걸어준다. -->
        이름은 <input type="text" v-model="name">{{name}}
        <br/><!-- 위 코드를 풀어쓴다면 아래이다. -->
        이름은 <input type="text" v-bind:value="name" @input="changeName">{{name}}
        <hr/>
        <!-- 계산 관련 -->
        a = {{a}}, b = {{b}} <!-- b는 문자열이 더해진것이다! 11이 아닌 각각의 1이 출력됨 -->
        <br/>
        a 값 입력 : <input type="text" v-model="a"><br/>
        입력된 a : {{a}} (타입은 {{typeof a}})<br/>
        a 값 입력 : <input type="text" v-model.number="a"><br/> <!-- number type으로 바뀜 -->
        <hr/>
        <!-- 조건문 if -->
         if test : <br/>
        <h2 v-if="nai >= 20">성인</h2>
        <h2 v-else-if="nai === 15">15살</h2>
        <h2 v-else>애기</h2>
        <hr/>
        <!-- 반복문 for -->
        for test : <br/>
        <h2>오늘 할 일!</h2>
        <ul>
            <li v-for="todo in todos">{{todo.msg}}</li>
        </ul>
        <ul>
            <li v-for="(todo, index) in todos" :key='index'>{{index+1}}) {{todo.msg}}</li>
        </ul>
    </div>

    <script>
        const {createApp} = Vue;
        
        createApp({
            data() { // 반응형 객체를 반환
                return {
                    link:"https://daum.net",
                    imageSrc:"02.png",
                    say:"이해가 잘 되고 있을까?",
                    aLinkToNaver:"<a href='https://naver.com'>네이버</a>",
                    name:"차나핑",
                    a:"1",
                    nai:1,
                    todos:[
                        {msg:'아침을 먹고'},
                        {msg:'점심을 먹고'},
                        {msg:'집에 가자'}
                    ],
                };
            },
            methods:{
                sayFunc(){
                    this.say = "vue의 지시어에 대한 이해를 묻는거다"
                    return this.say;
                },
                changeName(e){
                    this.name = e.target.value;
                }
            },
            computed:{
                // 계산된 프로퍼티로 사용할 수 있는 함수처럼 작동함
                // 함수와의 차이는 계산된 프로퍼티가 의존하는 요소가 변경되면 값이 다시 평가됨
                b(){
                    return this.a + 1;
                }
            }
        }).mount("#app");
    </script>
</body>
</html>

v-bind

<a href={{link}}>다음 (html 속성에서는 {{}} 사용 X)</a>
<br/>
<!-- 일방적으로 데이터를 받고 있다. -->
<a v-bind:href="link">다음 - 단방향으로 데이터에 접근</a>
<br/>
<a :href="link">다음 - v-bind 생략 가능</a>

<script>
        const {createApp} = Vue;
        
        createApp({
            data() { // 반응형 객체를 반환
                return {
                    link:"https://daum.net",
                };
            },
        }).mount("#app");
    </script>

 

마우스를 올려보았을 때 링크가 제대로 걸린 것을 확인

v-bind (이미지)

<img v-bind:src="imageSrc" alt="이미지" />

<script>
        const {createApp} = Vue;

        createApp({
            data() { // 반응형 객체를 반환
                return {
                    link:"https://daum.net",
                    imageSrc:"02.png",
                };
            },
        }).mount("#app");
    </script>

이미지 출력

v-text, v-once

<h2 v-text="say"></h2> <!-- html의 innerText, 타임리프의 th:text라고 생각하자 -->
<h2>{{say}}</h2> <!-- 보간법 사용해도 괜찮다. -->
<h2 v-once>data 속성 값 : {{say}}</h2> <!-- 처음 1회만 바인딩된 후 1회만 렌더링된다. 업데이트 안됨 -->
<h2>data 속성 값 : {{sayFunc()}}</h2>

<script>
        const {createApp} = Vue;
        
        createApp({
            data() { // 반응형 객체를 반환
                return {
                    link:"https://daum.net",
                    imageSrc:"02.png",
                    say:"이해가 잘 되고 있을까?",
                };
            },
            methods:{
                sayFunc(){
                    this.say = "vue의 지시어에 대한 이해를 묻는거다"
                    return this.say;
                },
            },
        }).mount("#app");
    </script>

v-text, v-html

<!-- 텍스트 형태로 출력 또는 파싱 -->
<span v-text="aLinkToNaver"></span><br/> <!-- innerText -->
<span v-html="aLinkToNaver"></span> <!-- innerHTML -->

<script>
        const {createApp} = Vue;
        
        createApp({
            data() { // 반응형 객체를 반환
                return {
                    link:"https://daum.net",
                    imageSrc:"02.png",
                    say:"이해가 잘 되고 있을까?",
                    aLinkToNaver:"<a href='https://naver.com'>네이버</a>",
                };
            },
            methods:{
                sayFunc(){
                    this.say = "vue의 지시어에 대한 이해를 묻는거다"
                    return this.say;
                },
            },
        }).mount("#app");
    </script>

v-model 1

<!-- 양방향 데이터 바인딩, 엘리먼트를 보고 양방향이 성립될 지 안될 지 판단하여 걸어준다. -->
이름은 <input type="text" v-model="name">{{name}}
<br/><!-- 위 코드를 풀어쓴다면 아래이다. -->
이름은 <input type="text" v-bind:value="name" @input="changeName">{{name}}

<script>
        const {createApp} = Vue;
        
        createApp({
            data() { // 반응형 객체를 반환
                return {
                    link:"https://daum.net",
                    imageSrc:"02.png",
                    say:"이해가 잘 되고 있을까?",
                    aLinkToNaver:"<a href='https://naver.com'>네이버</a>",
                    name:"차나핑",
                };
            },
            methods:{
                sayFunc(){
                    this.say = "vue의 지시어에 대한 이해를 묻는거다"
                    return this.say;
                },
                changeName(e){
                    this.name = e.target.value;
                }
            },
        }).mount("#app");
    </script>

위에서 수정해도 아래의 값도 변화한다. v-model은 값을 참조하기도하고 변화하기도 한다.

v-model 2

<!-- 계산 관련 -->
a = {{a}}, b = {{b}} <!-- b는 문자열이 더해진것이다! 11이 아닌 각각의 1이 출력됨 -->
<br/>
a 값 입력 : <input type="text" v-model="a"><br/>
입력된 a : {{a}} (타입은 {{typeof a}})<br/>
a 값 입력 : <input type="text" v-model.number="a"><br/> <!-- 여기에 입력하면 숫자 type으로 바뀜 -->

    <script>
        const {createApp} = Vue;
        
        createApp({
            data() { // 반응형 객체를 반환
                return {
                    link:"https://daum.net",
                    imageSrc:"02.png",
                    say:"이해가 잘 되고 있을까?",
                    aLinkToNaver:"<a href='https://naver.com'>네이버</a>",
                    name:"차나핑",
                    a:"1",
                };
            },
            methods:{
                sayFunc(){
                    this.say = "vue의 지시어에 대한 이해를 묻는거다"
                    return this.say;
                },
                changeName(e){
                    this.name = e.target.value;
                }
            },
            computed:{
                // 계산된 프로퍼티로 사용할 수 있는 함수처럼 작동함
                // 함수와의 차이는 계산된 프로퍼티가 의존하는 요소가 변경되면 값이 다시 평가됨
                b(){
                    return this.a + 1;
                }
            }
        }).mount("#app");
    </script>

양방향 바인딩, 타입 변경

v-if, v-else-if, v-else

<!-- 조건문 if -->
if test : <br/>
<h2 v-if="nai >= 20">성인</h2>
<h2 v-else-if="nai === 15">15살</h2>
<h2 v-else>애기</h2>

<script>
        const {createApp} = Vue;
        
        createApp({
            data() { // 반응형 객체를 반환
                return {
                    link:"https://daum.net",
                    imageSrc:"02.png",
                    say:"이해가 잘 되고 있을까?",
                    aLinkToNaver:"<a href='https://naver.com'>네이버</a>",
                    name:"차나핑",
                    a:"1",
                    nai:30,
                };
            },
            methods:{
                sayFunc(){
                    this.say = "vue의 지시어에 대한 이해를 묻는거다"
                    return this.say;
                },
                changeName(e){
                    this.name = e.target.value;
                }
            },
            computed:{
                // 계산된 프로퍼티로 사용할 수 있는 함수처럼 작동함
                // 함수와의 차이는 계산된 프로퍼티가 의존하는 요소가 변경되면 값이 다시 평가됨
                b(){
                    return this.a + 1;
                }
            }
        }).mount("#app");
    </script>

나이를 변경할 때마다 변경된다.

v-for

<!-- 반복문 for -->
for test : <br/>
<h2>오늘 할 일!</h2>
<ul>
	<li v-for="todo in todos">{{todo.msg}}</li>
</ul>
<ul>
	<li v-for="(todo, index) in todos" :key='index'>{{index+1}}) {{todo.msg}}</li>
</ul>

<script>
        const {createApp} = Vue;
        
        createApp({
            data() { // 반응형 객체를 반환
                return {
                    link:"https://daum.net",
                    imageSrc:"02.png",
                    say:"이해가 잘 되고 있을까?",
                    aLinkToNaver:"<a href='https://naver.com'>네이버</a>",
                    name:"차나핑",
                    a:"1",
                    nai:30,
                    todos:[
                        {msg:'아침을 먹고'},
                        {msg:'점심을 먹고'},
                        {msg:'집에 가자'}
                    ],
                };
            },
            methods:{
                sayFunc(){
                    this.say = "vue의 지시어에 대한 이해를 묻는거다"
                    return this.say;
                },
                changeName(e){
                    this.name = e.target.value;
                }
            },
            computed:{
                // 계산된 프로퍼티로 사용할 수 있는 함수처럼 작동함
                // 함수와의 차이는 계산된 프로퍼티가 의존하는 요소가 변경되면 값이 다시 평가됨
                b(){
                    return this.a + 1;
                }
            }
        }).mount("#app");
    </script>

반복문 출력


뷰 반복문 예제

ex4fortest.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/vue@3"></script>
    <script defer>
        document.addEventListener("DOMContentLoaded", function () {
            const {createApp, ref} = Vue; // ref : 반응형 데이터 생성을 위한 메소드
            
            createApp({
                setup() { // 컴포넌트의 초기 상태와 로직을 설정
                    const bookList = ref([
                        // ref()로 감싸준 데이터들은 반응형 데이터로 동작한다.
                        // 데이터 변경 시 리렌더링이 된다.
                        {name:"자바의 이해", price:25000},
                        {name:"파이썬 프로그래밍", price:35000},
                        {name:"마리아 디비", price:45000},
                        {name:"리액트 완전 정복", price:55000},
                        {name:"뷰가 짱이야", price:65000},
                    ]);
                    const selectedBooks = ref([]); // 사용자가 선택한 책이 저장되기 위한 배열
                    return {bookList, selectedBooks}; // 셋업 함수는 두 개의 변수를 리턴한다.
                },
            }).mount("#app");
        });
    </script>
</head>
<body>
    <div id="app">
        <div>
            <h2>도서 정보</h2>
            <div v-for="(book, index) in bookList" :key="index">
                <input type="checkbox" :id="'book' + index" v-bind:value="book" v-model="selectedBooks">
                <label :for="'book' + index">{{book.name}} {{book.price}}</label>
            </div>
            <div>
                <h3>선택된 도서 : </h3>
                <ul>
                    <li v-for="(book, index) in selectedBooks" :key="index">
                        {{index + 1}}: {{book.name}}
                    </li>
                </ul>
            </div>
        </div>
    </div>
</body>
</html>

결과

반복문 출력 확인

선택된 도서 출력

'Study > Acorn' 카테고리의 다른 글

241127 뷰 (이벤트, 구글 차트, route, ajax)  (0) 2024.11.27
241126 뷰 (이벤트)  (1) 2024.11.26
241122 리액트 (총정리 문제)  (0) 2024.11.22
241121 리액트 (리덕스 DB연동, RESTful)  (0) 2024.11.21
241120 리액트 (Redux)  (1) 2024.11.20