Vue.js의 컴포넌트를 사용한 UI를 만들고 있었다. 그런데 컴포넌트의 props가 이상하게 작동했다. 정확하게는 props로 넘겨준 인자를 사용할 때 문제가 생겼다.

여기까진 문제 없었다.

<!DOCTYPE html>
<html>
<head>
  <title>Component test</title>
  <script type="application/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
  <script type="application/javascript" src="https://unpkg.com/vue"></script>
  <script type="application/javascript">
    Vue.component("component-item", {
      template: "#tpl-component-item",
      props: ["comp", "msg"]
    });

    $(document).ready(function(){
      var data = {
        componentItems: [{
          key: 0,
          id: 1,
          text: "test component item #1"
        }]
      };
      var app = new Vue({
        el: "#app",
        data: data
      });
    });
  </script>
</head>
<body>
  <h1>Component test</h1>
  <div id="app">
    <ol>
      <li is="component-item"
       v-for="item in componentItems" v-bind:key="item.key" v-bind:comp="item" v-bind:msg="'static message'"></li>
    </ol>
  </div>
  <script type="text/x-template" id="tpl-component-item">
    <li :id="comp.id">
      <p>{{msg}}</p>
    </li>
  </script>
</body>
</html>

이 HTML은 다음과 같이 문제없이 리스트를 렌더링한다.

문제없는 Vue.js의 렌더링

에러 나기 시작

뭔가가 잘못되기 시작

그런데 뭔가 이상하다. 메시지가 나오긴 나오는데 에러가 난다. 그래도 화면은 문제없이 나오니까 넘어가고, 테스트하느라 사용한 msg는 삭제.

<!DOCTYPE html>
<html>
<head>
  <title>Component test</title>
  <script type="application/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
  <script type="application/javascript" src="https://unpkg.com/vue"></script>
  <script type="application/javascript">
    Vue.component("component-item", {
      template: "#tpl-component-item",
      props: ["comp"]
    });

    $(document).ready(function(){
      var data = {
        componentItems: [{
          key: 0,
          id: 1,
          text: "test component item #1"
        }]
      };
      var app = new Vue({
        el: "#app",
        data: data
      });
    });
  </script>
</head>
<body>
  <h1>Component test</h1>
  <div id="app">
    <ol>
      <li is="component-item"
       v-for="item in componentItems" v-bind:key="item.key" v-bind:comp="item" v-bind:msg="'static message'"></li>
    </ol>
    <script type="text/x-template" id="tpl-component-item">
      <li :id="comp.id">
        <p>{{msg}}</p>
      </li>
    </script>
  </div>
</body>
</html>

망했다

시험용 변수 대신 데이터를 출력하도록 수정.

<script type="text/x-template" id="tpl-component-item">
  <li :id="comp.id">
    <p>{{comp.text}}</p>
  </li>
</script>

아무 것도 안나온다... 아무 것도 안나온다...(2)

그런데 나오라는 데이터는 안나오고 에러만 나온다.

이상해!!!

그래서 시험을 해보는데…

<script type="text/x-template" id="tpl-component-item">
  <li :id="comp.id">
    <p>{{comp}}</p>
  </li>
</script>

너는 왜 나와...

에러가 나기는 하지만, 이건 왜 나오는지 이유가 뭔지 모르겠다.

결론

[Vue warn]: Property or method "comp" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.

(found in <Root>)

여기에 사용한 코드보다는 좀 더 복잡한 코드를 작성하던 도중에 이 문제를 겪었다. 그리고 약 하루 반 정도를 썼는데, 원인은 아주 간단했다. 템플릿 엘리먼트의 위치가 문제였다.

<!-- 문제의 템플릿 엘리먼트 -->
<script type="text/x-template" id="tpl-component-item">
  <li :id="comp.id">
    <p>{{comp}}</p>
  </li>
</script>

처음에는 div#appscript#tpl-component-item 엘리먼트가 형제 관계 였다.

<body>
  - <div id="app">
  - <script id="tpl-component-item">

그런데 이걸 묶어서 관리하겠다고 템플릿(script#tpl-component-item) 위치를 옮겼다.

<body>
  - <div id="app">
    - <script id="tpl-component-item">

Vue 오브젝트를 만들면

var app = new Vue({
  el: "#app",
  data: data
});

div#app 밑에 있는 템플릿 엘리먼트를 스캔, HTML 엘리먼트에 뭔가 처리를 하고,

Vue.component("component-item", {
  template: "#tpl-component-item",
  props: ["comp", "msg"]
});

컴포넌트(component-item)가 템플릿에 접근하면서 문제가 생긴 것이다.

아니면 반대로 컴포넌트를 등록하면서 템플릿 엘리먼트에 어떤 처리를 해두는데 이걸 Vue 오브젝트 초기화 과정에 잘못 건드는 것일 수도 있다.

어느 쪽이든, Vue 애플리케이션이 되는 엘리먼트 밑에 템플릿 코드를 두지 말자.

참고