공부방

Vue Component 본문

vue.js

Vue Component

코딩 화이팅 2023. 5. 4. 16:23

JavaScript Module

Module

  • 프로그램을 기능별로 여러 개의 파일로 나누는 형태
  • 브라우저의 지원 여부 확인

Module 시스템

  • CommonJS(NodeJS)-웹 브라우저 밖에서도 동작될 수 있는 모듈 규칙 설립
  • AMD(Asynchronous Module Definition)-비동기적으로 모듈을 로딩
  • ESM(ECMAScript Module, ECMA215, es6)-자바스크립트 자체 모듈

Module  정의 및 사용

  • 가져오기 : import
  • 내보내기 : export
module1.js
const title = "계산기 모듈";

function add(i, j) {
  return i + j;
}

function sub(i, j) {
  return i - j;
}

export { title, add, sub };
=======================================================================
test01.js
import { title, add, sub } from "./module1.js";

console.log(title);

const app = new Vue({
  el: "#app",
  data() {
    return {
      num1: 0,
      num2: 0,
      op: "-",
      result: 0,
    };
  },
  methods: {
    doCal() {
      if (this.op === "+") this.result = add(this.num1, this.num2);
      else this.result = sub(this.num1, this.num2);
    },
  },
  watch: {
    // watch : 감시자
    op(val) {
      if (val === "+") this.result = add(this.num1, this.num2);
      else this.result = sub(this.num1, this.num2);
    },
  },
});
=======================================================================
test01.html
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.13/dist/vue.js"></script>
  <title>Vue</title>
</head>
<body>
  <div id="app">
    <input type="text" v-model.number="num1">
    <select v-model="op">
      <option value="+">+</option>
      <option value="-">-</option>
    </select>
    <input type="text" v-model.number="num2">
    <button @click="doCal">계산</button>
    <div>{{result}}</div>
  </div>

  <script type="module" src="./test01.js"></script>
</body>
</html>
module2.js
export default {
  title: "계산기 모듈",
  add(i, j) {
    return i + j;
  },
  sub(i, j) {
    return i - j;
  },
};
==================================================================
test02.js
import cal from "./module2.js";

const app = new Vue({
  el: "#app",
  data() {
    return {
      num1: 0,
      num2: 0,
      op: "-",
      result: 0,
    };
  },
  methods: {
    doCal() {
      if (this.op === "+") this.result = cal.add(this.num1, this.num2);
      else this.result = cal.sub(this.num1, this.num2);
    },
  },
  watch: {
    op(val) {
      if (val === "+") this.result = cal.add(this.num1, this.num2);
      else this.result = cal.sub(this.num1, this.num2);
    },
  },
});
==================================================================
test02.html
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.13/dist/vue.js"></script>
  <title>Vue</title>
</head>
<body>
  <div id="app">
    <input type="text" v-model.number="num1">
    <select v-model="op">
      <option value="+">+</option>
      <option value="-">-</option>
    </select>
    <input type="text" v-model.number="num2">
    <button @click="doCal">계산</button>
    <div>{{result}}</div>
  </div>

  <script type="module" src="./test02.js"></script>
</body>
</html>

Vue Component

  • Vue의 가장 강력한 기능 중 하나
  • 기본 HTML 엘리먼트를 확장하여 재사용 가능한 코드를 캡슐화하는데 도움이 됨
  • VueComponent는 Vue Instance이기도함.
    (루트에서만 사용하는 옵션을 제외하고 모든 옵션 객체 사용 가능)
  • Life Cycle Hook 사용 가능
  • 전역 컴포넌트와 지역 컴포넌트 사용

Vue Component Template

  • DOM을 템플릿으로 사용할 때, Vue는 템플릿 콘텐츠만 가져올 수 있기 때문에 HTML이 작동하는 방식에 고유한 몇 가지 제한 사항이 적용됨
  • 가능하다면 문자열 템플릿을 사용하는 것이 좋다.

Vue Component(전역)

  • 전역 컴포넌트를 등록하려면 Vue.Component(tagName, options)를 사용
  • 권장하는 컴포넌트 이름 : 케밥 케이스
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <!-- 전역컴포넌트 -->
    <div id="app">
      <my-global></my-global>
      <my-global></my-global>
      <my-global></my-global>
    </div>

    <div id="app2">
      <my-global></my-global>
    </div>

    <script>
      // Vue.component('comp-name', {옵션})
      Vue.component("my-global", {
        template: "<h2>전역 컴포넌트</h2>",
        // template : 안에 있는 내용을 실제로 붙여주는 옵션
      });
      // my-global이라는 컴포넌트를 하나 만들어놈

      const app = new Vue({
        el: "#app",
      });
      const app2 = new Vue({
        el: "#app2",
      });
    </script>
  </body>
</html>

Vue Component(지역)

  • 모든 컴포넌트를 전역으로 등록할 필요X
  • 컴포넌트를 components 인스턴스 옵션으로 등록함으로써 다른 인스턴스/컴포넌트의 범위에서만 사용할 수 있는 컴포넌트 생성 가능
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <!-- 지역컴포넌트 -->
    <div id="app">
      <my-local></my-local>
      <my-local></my-local>
    </div>

    <!-- 이건 인식할수 없어~~ -->
    <div id="app2">
      <my-local></my-local>
    </div>

    <script>
      const app = new Vue({
        el: '#app',
        components: {
          'my-local': {
            template: '<h2>지역컴포넌트</h2>',
          },
        },
      });

      const app2 = new Vue({
        el: '#app2',
      });
    </script>
  </body>
</html>

Vue Component data

  • data는 반드시 함수여야 한다.
  • data 객체를 공유하는 문제를 막고 새로운 데이터 객체를 반환해서 해결한다.
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <my-comp></my-comp>
    </div>

    <template id="mycomponent">
      <!-- <div>얍</div> 이거 안돼 최상위 태그가 하나 있어야한다. -->
      <div>
        <h2>{{msg}}</h2>
      </div>
    </template>

    <script>
      //   컴포넌트에서 객체형태로는 data를 사용하면 안된다.
      //   Vue.component('my-comp', {
      //     template: '#mycomponent',
      //     data: {
      //       msg: 'hello',
      //     },
      //   });
      Vue.component("my-comp", {
        template: "#mycomponent",
        data() {
          return {
            msg: "hello",
          };
        },
      });

      const app = new Vue({
        el: "#app",
      });
    </script>
  </body>
</html>

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <count-view></count-view>     
    </div>

    <template id="count-view">
      <div>
        <h2>{{count}}</h2>
        <button @click="count++">클릭</button>
      </div>
    </template>

    <script>
      let data = {
        count: 0,
      };

      Vue.component("count-view", {
        template: "#count-view",
        data() {
          return data;
        },
      });

      const app = new Vue({
        el: "#app",
      });
    </script>
  </body>
</html>

클릭하면 숫자가 오른다.

버튼을 두 개 만들어 클릭을 눌러준다면?

나머지 코드는 위와 동일
<body>
    <div id="app">
      <count-view></count-view>
      <count-view></count-view>
    </div>

한 클릭만 눌러도 두 개가 같이 올라간다.

let data = {
        count: 0,
      };

여기서 한 개의 데이터를 공유를 하고 있기 때문에 두 개는 하나의 데이터를 공유하고 있다.

이것을 방지하기 위해

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <count-view></count-view>
      <count-view></count-view>
    </div>

    <template id="count-view">
      <div>
        <h2>{{count}}</h2>
        <button @click="count++">클릭</button>
      </div>
    </template>

    <script>
      // let data = {
      //   count: 0,
      // };
      // 원래 이렇게 있던 걸 밑에 return값에 넣어줘 새로운 객체가 만들어지게 해준다.
      Vue.component('count-view', {
        template: '#count-view',
        data() {
          return {
            count: 0,
          };
        },
      });

      const app = new Vue({
        el: '#app',
      });
    </script>
  </body>
</html>

원래 let으로 만들었던 객체 하나를 return 값에 넣어주어 새로운 객체를 만들어 다른 객체를 사용할 수 있게끔 해준다.

Vue Component  통신

  • 컴포넌트는 부모-자식 관계에서 가장 일반적으로 함께 사용하기 위한 것(트리 구조)
  • 부모는 자식에게 데이터를 전달(Pass Props)
  • 자식은 부모에게 일어난 일을 알림(Emit Event)
  • 부모와 자식이 명확하게 정의된 인터페이스를 통해 격리된 상태 유지
  • props는 아래로, events는 위로

부모 컴포넌트-> 자식 컴포넌트

  • 상위 컴포넌트에서 하위 컴포넌트로 데이터 전달
  • 하위 컴포넌트는 상위 컴포넌트의 값을 직접 참조 불가능
  • data와 마찬가지로 props 속성의 값을 template에서 사용 가능
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>

    <!-- 부모 -->
    <div id="app">
      <h2>부모컴포넌트</h2>
      <!-- 자식 컴포넌트를 넣는다. -->
      <!-- 정적 props -->
      <child-view propsdata="정적인메시지"></child-view>
    </div>

    <!-- 자식 -->
    <template id="child-view">
      <div>
        <h3>자식컴포넌트</h3>
        <div>{{propsdata}}</div>
      </div>
    </template>

    <script>
      Vue.component("child-view", {
        template: "#child-view",
        props: ["propsdata"],
      });

      const app = new Vue({
        el: "#app",
        data() {
          return {
            msg: "",
          };
        },
      });
    </script>
  </body>
</html>

Dynamic Props

  • v-bind를 사용하여 부모의 데이터에 props를 동적으로 바인딩할 수 있음
  • 데이터가 상위에서 업데이트 되루 때마다 하위 데이터로도 전달
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <!-- 부모 -->
    <div id="app">
      <h2>부모컴포넌트</h2>
      <!-- 자식 컴포넌트를 넣는다. -->
      <!-- 정적 props -->
      <child-view propsdata="정적인메시지"></child-view>
      
      <!-- 동적 props -->
      <input type="text" v-model="msg" />
      <child-view :propsdata="msg"></child-view>
    </div>

    <!-- 자식 -->
    <template id="child-view">
      <div>
        <h3>자식컴포넌트</h3>
        <div>{{propsdata}}</div>
      </div>
    </template>

    <script>
      Vue.component("child-view", {
        template: "#child-view",
        props: ["propsdata"],
      });

      const app = new Vue({
        el: "#app",
        data() {
          return {
            msg: "",
          };
        },
      });
    </script>
  </body>
</html>

Dynamic Props 객체의 속성 전달

  • 객체의 모든 속성을 props로 전달할 경우 인자없이 v-bind를 사용

  • 위의 코드는 아래와 같이 동작

단방향 데이터 흐름

  • 모든 props는 하위 속성과 상위 속성 사이의 단방향 바인딩을 형성
  • 상위 속성이 업데이트되면 하위로 흐르게 되지만 반대로는 안됨
  • 하위 컴포넌트가 실수로 부모의 상태를 변경하여 앱의 데이터 흐름을 이해하기 어렵게 만드는 일 방지
  • 상위 컴포넌트가 업데이트 될 때마다 하위 컴포넌트의 모든 prop들이 최신 값으로 갱신
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <h2>부모컴포</h2>
      <child-view
        area="속초"
        :msg="msg[parseInt(Math.random()*5)]"
      ></child-view>
      <child-view
        area="일본"
        :msg="msg[parseInt(Math.random()*5)]"
      ></child-view>
      <child-view
        area="수유"
        :msg="msg[parseInt(Math.random()*5)]"
      ></child-view>
      <child-view
        area="부산"
        :msg="msg[parseInt(Math.random()*5)]"
      ></child-view>
      <child-view
        area="통영"
        :msg="msg[parseInt(Math.random()*5)]"
      ></child-view>
    </div>

    <template id="child-view">
      <div>
        <h3>{{area}}  지역</h3>
        <div>{{msg}}</div>
      </div>
    </template>

    <script>
      Vue.component('child-view', {
        template: '#child-view',
        props: ['area', 'msg'],
      });

      const app = new Vue({
        el: '#app',
        data() {
          return {
            msg: [
              '오향족발먹으로',
              '고양이를 보러',
              '경섭이가 놀러가서',
              '라면먹으로',
              '예비군하러',
            ],
          };
        },
      });
    </script>
  </body>
</html>

위의 코드를 줄일 수 있다.

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <h2>부모컴포</h2>
      <child-view
        v-for="area in areas"
        :area2="area"
        :msg="msg[parseInt(Math.random()*5)]"
      ></child-view>
    </div>

    <template id="child-view">
      <div>
        <h3>{{area2}} 지역</h3>
        <div>{{msg}}</div>
      </div>
    </template>

    <script>
      Vue.component('child-view', {
        template: '#child-view',
        props: ['area2', 'msg'],
      });

      const app = new Vue({
        el: '#app',
        data() {
          return {
            areas: ['속초', '일본', '수유', '부산', '통영'],
            msg: [
              '오향족발먹으로',
              '고양이를 보러',
              '경섭이가 놀러가서',
              '라면먹으로',
              '예비군하러',
            ],
          };
        },
      });
    </script>
  </body>
</html>

결과는 같다.

객체를 내려보내기

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <h2>부모컴포</h2>
      <child-view :user="person"></child-view>
    </div>

    <template id="child-view">
      <div>
        <h3>{{user.name}} 님의 신상명세서입니다. 킬러님</h3>
        <div>나이 : {{user.age}}세</div>
        <div>이멜 : {{user.email}}</div>
      </div>
    </template>

    <script>
      Vue.component("child-view", {
        template: "#child-view",
        props: ["user"],
      });

      const app = new Vue({
        el: "#app",
        data() {
          return {
            person: {
              name: "유지나",
              age: 20,
              email: "ssafy.com",
            },
          };
        },
      });
    </script>
  </body>
</html>

사용자 정의 이벤트

  • 컴포넌트 및 props와는 달리, 이벤트는 자동 대소문자 변환 제공X
  • 대소문자를 혼용하는 대신 emit할 정확한 이벤트 이름을 작성하는 것을 권장
  • DOM 템플릿의 v-on이벤트 리스너는 항상 자동으로 소문자 변환(v-on:myEvent->v-on:myevent)
  • $on(eventName): 이벤트 수신 
  • $emit(eventName): 이벤트 발생, 추가 인자는 리스너의 콜백 함수로 전달
  • 부모 컴포넌트는 자식 컴포넌트가 사용되는 템플릿에서 v-on을 사용하여 자식 컴포넌트가 보낸 이벤트를 청취
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <button @click="doAction">메시지전송</button>
    </div>

    <script>
      const app = new Vue({
        el: '#app',
        data() {
          return {};
        },
        methods: {
          doAction() {
            this.$emit('sendMsg', '안녕하세요...');
          },
        },
        created() {
          this.$on('sendMsg', (msg) => {
            alert(msg);
          });
        },
      });
    </script>
  </body>
</html>

자식 컴포넌트->부모 컴포넌트

  • 이벤트 발생과 수신을 이용
  • 자식 컴포넌트에서 부모 컴포넌트가 지정한 이벤트를 발생($emit)
  • 부모 컴포넌트는 자식 컴포넌트가 발생한 이벤트를 수신(on)하여 데이터 처리

총 투표 수

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <!-- 총투표수  -->
    <!-- 코딩 / 알고리즘 -->
    <div id="app">
      <h2>여러분의 선호도는~~</h2>
      <h3>총 투표수는 : {{total}}</h3>
      <child-view @add-count="addTotalCount" title="코딩"></child-view>
      <child-view @add-count="addTotalCount" title="알고리즘"></child-view>
    </div>

    <!-- 자식컴포넌트를 정의 해보자 -->
    <template id="child-view">
      <div>
        <h2>{{title}} 의 득표수는 {{count}}</h2>
        <button @click="addCount">투표</button>
      </div>
    </template>
 
    <script>
      Vue.component('child-view', {
        template: '#child-view',
        data() {
          return {
            count: 0,
          };
        },
        props: ['title'],
        methods: {
          addCount() {
            this.count += 1;
            //부모에게 알려줘야된다.
            this.$emit('add-count');
          },
        },
      });

      const app = new Vue({
        el: '#app',
        data() {
          return {
            total: 0,
          };
        },
        methods: {
          addTotalCount() {
            this.total += 1;
          },
        },
      });
    </script>
  </body>
</html>

컴포넌트에 js 삽입

FooterBar.js
export default {
  template: `
    <footer class="footer">
    <ul class="list">
      <li>
        <a href="Personal information processing policy"
          >개인정보처리방침</a
        >
      </li>
      <li>
        <a href="Terms of Service">이용약관</a>
      </li>
      <li>
        <a href="Directions">오시는길</a>
      </li>
      <li>Created by the SSAFY team &middot; &copy; 2022</li>
    </ul>
  </footer>
    `,
};
================================================================
HeaderNav.js
export default {
  template: `
    <header>
      <nav class="container">
        <div>
          <a class="header_nav_home" href="index.html">홈으로</a>
        </div>
        <div class="header_nav_search">
          <form action="#">
            <input type="text" placeholder="검색어를 입력해주세요" />
            <button class="button">검색</button>
          </form>
        </div>
        <div class="header_nav_menuitem">
          <a href="login.html" v-if="getUser">로그인</a>
          <a href="#" @click="logout" v-else>로그아웃</a>
          <a href="#">마이페이지</a>
        </div>
      </nav>
  </header>
    `,
  methods: {
    logout() {
      localStorage.removeItem("loginUser");
      alert("로그아웃 했습니다.");
    },
  },
  computed: {
    getUser() {
      let user = JSON.parse(localStorage.getItem("loginUser"));
      if (!user) {
        return true;
      } else {
        return false;
      }
    },
  },
};
================================================================
MainContent.js
export default {
  template: `
    <main class="index-main">
    <div class="main-menu">
      <ul>
        <li><a href="./create.html">등록</a></li>
        <li><a href="./list.html">목록</a></li>
      </ul>
    </div>
  </main>
    `,
};
================================================================
test11.html
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <title>Vue</title>
  </head>
  <body>
    <div id="app">
      <header-nav></header-nav>
      <main-content></main-content>
      <footer-bar></footer-bar>
    </div>

    <script type="module">
      import HeaderNav from './component/HeaderNav.js';
      import MainContent from './component/MainContent.js';
      import FooterBar from './component/FooterBar.js';

      const app = new Vue({
        el: '#app',
        components: {
          HeaderNav,
          MainContent,
          FooterBar,
        },
      });
    </script>
  </body>
</html>

 

'vue.js' 카테고리의 다른 글

Vue Router  (0) 2023.05.09
Vue CLI  (0) 2023.05.08
Vue Event  (0) 2023.05.03
Vue Directive  (0) 2023.05.02
vue기본  (0) 2023.05.01