Vue.js - props 알아보기

jomminii_before·2020년 5월 17일
3

vue-js

목록 보기
2/5

본 글은 Vue.js 문서에서 컴포넌트 - Props 부분을 정리하고 예시를 추가한 글 입니다.
Vue.js - 컴포넌트(Props)

1. 공식문서 내용 정리

# Props로 데이터 전달하기

모든 컴포넌트 인스턴스에는 각자의 자체 격리된 범위(scope)가 있습니다. 이 때문에 하위 컴포넌트의 템플릿에서는 상위 데이터를 직접 참조할 수 없는데, props 를 사용하면 하위 컴포넌트로 데이터를 전달할 수 있습니다.

props는 상위 컴포넌트의 데이터를 전달하기 위한 사용자 지정 특성 입니다. 하위 컴포넌트에서는 props 옵션을 사용해 수신할 것으로 기대되는 props를 명시적으로 선언해야 합니다.

# 하위 컴포넌트 js

Vue.component('child', {
  // props 정의
  props: ['message'],
  // 데이터와 마찬가지로 prop은 템플릿 내부에서 사용할 수 있으며
  // vm의 this.message로 사용할 수 있습니다.
  template: '<span>{{ message }}</span>'
})

그런 다음 일반 문자열을 다음과 같이 전달할 수 있습니다. (리터럴 방식)

# 상위 컴포넌트 html template

<child message="안녕하세요!"></child>

이렇게 하면 상위 컴포넌트에서 message에 담은 안녕하세요!라는 문자열을 하위 컴포넌트의 message로 보내줄 수 있습니다.

하지만 이 방식은 동적 바인딩은 아니라서, 상위 컴포넌트의 데이터가 변해도 하위 컴포넌트에는 반영이 되지 않습니다.


# 동적 Props

v-bind를 사용하면 부모(상위 컴포넌트)의 데이터를 동적으로 바인딩해 자식(하위 컴포넌트)에게 전달할 수 있습니다. 데이터가 상위에서 업데이트 될 때마다 하위 컴포넌트로도 전달됩니다.

# 상위 컴포넌트 html template
<div>
  <input v-model="parentMsg">
  <br>
  <child v-bind:my-message="parentMsg"></child> # 동적 바인딩
</div>

v-bind는 아래와 같이 :을 활용해 단축 구문으로 사용하는 경우가 더 많습니다.

<child :my-message="parentMsg"></child>

전달하고 싶은 데이터가 객체라면, 인자 없이 v-bind를 써도 전달 가능합니다. (v-bind:prop-name 대신 v-bind:객체명)

예를 들어 todo라는 객체가 있다면,

# 상위 컴포넌트 js
todo: {
  text: 'Learn Vue',
  isComplete: false
}

아래의 두 가지 작성 방식은 같은 동작을 합니다. 각자의 props를 바인딩 하는 것과 객체로 바인딩하는 방식이 가져오는 효과는 같습니다.

# 상위 컴포넌트 html template
1)
<todo-item v-bind="todo"></todo-item>

2)
<todo-item
  v-bind:text="todo.text"
  v-bind:is-complete="todo.isComplete"
></todo-item>

# 리터럴 vs 동적

초보자가 흔히 범하는 실수는 리터럴 구문을 사용하여 숫자를 전달하려고 하는 것 입니다.

# 상위 컴포넌트 html template
<!-- 이것은 일반 문자열 "1"을 전달합니다. -->
<comp some-prop="1"></comp>

하지만 위의 리터럴 방식은 실제 숫자가 아닌 일반 문자열 "1"로 데이터를 전달합니다. 실제 숫자를 전달하려면 값이 Javascript 표현식으로 평가 되도록 v-bind를 사용해야합니다.

# 상위 컴포넌트 html template
<!-- 이것은 실제 숫자로 전달합니다. -->
<comp v-bind:some-prop="1"></comp>

# Prop 검증

하위 컴포넌트가 상위 컴포넌트로부터 받는 Props에 대한 요구사항을 지정할 수도 있습니다. 요구사항이 충족되지 않으면 Vue에서 경고를 내보냅니다. 이 기능은 다른 사용자가 사용할 컴포넌트를 제작할 때 특히 유용합니다.

Props를 문자열 배열로 정의하는 대신, 유효성 검사 요구사항을 담은 객체를 사용할 수 있습니다.

# 하위 컴포넌트 js

Vue.component('example', {
  props: {
    // 기본 타입 확인 (`null` 은 어떤 타입이든 가능하다는 뜻입니다)
    // propA 는 숫자 타입이어야 함
    propA: Number,
    
    // 여러개의 가능한 타입
    // propB는 문자열이나 숫자 타입이어야 함
    propB: [String, Number],
    
    // propC는 문자열이며, 필수 값임
    propC: {
      type: String,
      required: true
    },
    
    //propD는 숫자 타입이며, 100의 기본 값을 가짐
    propD: {
      type: Number,
      default: 100
    },
    
    // propE의 타입은 객체/배열이며 기본값은 팩토리 함수에서 반환 되어야 함
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    
    // 사용자 정의 유효성 검사 가능
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})

type은 다음 네이티브 생성자 중 하나를 사용할 수 있습니다.

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array
  • Symbol

props 검증이 실패하면 Vue는 콘솔에서 경고를 출력합니다.(개발 빌드를 사용하는 경우) Props는 컴포넌트 인스턴스가 생성되기 전에 검증되기 때문에 default 또는 validator 함수 내에서 data, computed 또는 methods와 같은 인스턴스 속성을 사용할 수 없습니다.

팩토리 함수란 클래스나 생성자를 반환하지 않는 모든 함수를 칭함
A factory function is any function which is not a class or constructor that returns a (presumably new) object. In JavaScript, any function can return an object. When it does so without the new keyword, it’s a factory function.
JavaScript Factory Functions with ES6+ 내용 발췌


2. To do list 사례로 보는 props 예시

Vue JS Crash Course 유튜브를 통해 공부한 내용을 사례로 제시 합니다. 설명에 사용되지 않는 코드는 삭제했습니다.

살펴 볼 투두리스트의 컴포넌트 구조는 아래와 같습니다. Home이라는 컴포넌트 안에 Todos가 있고, 그 아래에 각각의 TodoItem이 배치되어 있습니다.

# Home.vue

<template>
  <div id="app">
  
  	// 3) api의 데이터가 저장된 todos를 v-bind를 통해 todos라는 이름으로 'Todos' 하위 컴포넌트로 전달해줌
    <Todos v-bind:todos="todos" v-on:del-todo="deleteTodo"/> 
  </div>
</template>

<script>
import Todos from '../components/Todos';
import axios from 'axios';

export default {
  name: 'Home',
  components: {
    Todos
  },
  
  // 1) todos를 빈배열로 선언
  data() {
    return {
      todos: []
    }
  },
  
  // 2) api를 통해 todo 리스트 데이터를 받아오고, 이 데이터를 1)에서 선언한 todos에 넣어줌 
  created() {
    axios.get('https://jsonplaceholder.typicode.com/todos?_limit=5  ')
    .then(res => this.todos = res.data)
    .catch(err => console.log(err));
  }
}
</script>

<style>
	...
</style>

# Todos.vue

<template>
    <div>
    
    	// 2) 상위 컴포넌트에서 받아 온 todos를 v-for 문을 돌려 TodoItem 컴포넌트를 적용시킵니다. 
        <div v-bind:key="todo.id" v-for="todo in todos">
            // 3) for 문을 통해 생긴 todo 데이터를 todo로 v-bind 하여 하위 컴포넌트로 전달합니다.
            <TodoItem v-bind:todo="todo" v-on:del-todo="$emit('del-todo', todo.id)" />
        </div>
    </div>

</template>

<script>
import TodoItem from './TodoItem.vue';

export default {
    name: "Todos",
    components: {
        TodoItem
    },
    
    // 1) 상위 컴포넌트인 Home.vue에서 받은 'todos' 데이터를 props를 정의해 받아옵니다.
    props: ["todos"]
}
</script>

<style scoped>

</style>
# TodoItem.vue

<template>
    <div class="todo-item" v-bind:class="{'is-complete':todo.completed}">
        <p>
            <input type="checkbox" v-on:change="markComplete">
            
            // 2) todo 데이터를 이용해 데이터를 렌더링합니다.
            {{todo.title}}
            <button @click="$emit('del-todo', todo.id)" class="del">x</button>
            </p>    
    </div>    
</template>

<script>
export default {
    name: "TodoItem",
    
    // 1) Todos.vue 에서 전달 받은 todo를 props로 정의합니다.
    props: ["todo"],
    methods: {
        markComplete() {
            this.todo.completed = !this.todo.completed;
        }
    }
}
</script>

<style scoped>
	...
</style>

profile
https://velog.io/@jomminii 로 이동했습니다.

0개의 댓글