[Vue.js] 뷰 기초
Vue.js 기본 구조
- Vue 컴포넌트의 경우 세개의 구역으로 나누어져 있다.
- template: html부분이다. 이 안에 style태그와 script태그를 사용할 수 없다.
- script: 이 템플릿에서 사용할 스크립트 코드이다. import로 외부라이브러리를 가져올 수 있다.
- style: 이 템플릿에서 사용할 css이다. scoped속성을 부여하면 딱 이 스타일에서만 사용할 수 있다. 아무것도 없으면 자식 컴포넌트도 일괄 적용된다.
-
Vue.js는 (Value, Key) 순서이다.
-
this.$set(person1, 'name', 'John')
메소드로 vue가 객채 내 변화를 알아채고 재렌더링 시킴 -
vue는 객체 자체의 변화는 감지하지만 객체 내 속성, 배열 값 변화 등 객채 내 변화는 감지하지 못한다.
-
배열의 크기 변경을 재렌더링 할 때, splice 메소드를 사용 ex)
this.arr.splice(4)
: arr배열의 크기를 4로 재렌더링 -
mousedown/mousedown 이벤트(모든 이벤트는 '@'로 대체 가능하다)에서
method:function(e){
if(e.button==0 또는 1 또는 2) // 0: 왼쪽마우스, 2: 오른쪽마우스
}
<a>
: anchor 앵커 태그 (리다이렉트 시키는 태그)
<a @click.prevent href="http://www.naver.com">naver링크</a> // click이벤트에 prevent를 쓰면 리다이렉트를 막는다.
이벤트
- self : 자신을 직접 눌렀을때만 이벤트 발생
- stop : 자신 이벤트까지만 실행 시키고 하위 이벤트는 중지
- prevent
- capture : 우선순위애 따라 이벤트 발생
- once : 이벤트 한 번만 발생
- props : 컴포넌트의 속성, 부모 컴포넌트가 자식 컴포넌트에게 전달할 수 있다.
- template : 외부에서 이 컴포넌트를 호출할때 렌더링될 템플릿이다.
- data : 컴포넌트 내부에서 쓰일 변수
- methods : 컴포넌트 내부에서 쓰일 메소드
<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<simple-component v-bind:initial-counter="counter"></simple-component> <!-- HTML 영역 -->
</div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<script> // Vue.js 영역
// 글로벌 컴포넌트
Vue.component('simple-component', { // Vue.component([컴포넌트명],[컴포넌트정보]) / simple-component는 해당 Vue 컴포넌트의 이름
props: ['initialCounter'], // props만 컴포넌트끼리의 데이터 전달이 가능
template: '<button @click="addCounter">{{counter}}</button>',
data: function () {
return {
counter: this.initialCounter
};
},
methods: {
addCounter: function () {
this.counter += 1;
}
}
});
new Vue({
el: '#app',
data: {
counter: 0
},
components: {
'simple-component': simpleComponent, // 로컬 컴포넌트
}
});
</script>
</html>
네이밍 케이스
-
케밥-케이스: kebab-case, spinal-case, Train-Case, Lisp-case
-하이픈으로 단어를 연결하는 표기법
-HTML 태그의 id, class 속성으로 흔히 사용됨. -
파스칼 표기법: PascalCase, BackgroundColor, TypeName, PowerPoint
-첫 단어를 대문자로 시작하는 표기법 -
스네이크 케이스(뱀 표기법):
snake_case, background_color, type_name
-단어를 밑줄 문자로 구분하는 표기법
-perl, php, python, ruby, rust.... -
헝가리언 표기법:
strName, bBusy, szName
-접두어를 사용하는 표기법
-str -> string, b -> boolean, sz -> null로 끝나는 문자열(string+zero) -
카멜케이스:
ex) userName
-
자바스크립트 내에서 카멜케이싱으로 선언한 변수는 HTML내에서 케밥케이싱으로 자동변환된다.
-
userName(JS) -(자동변환)-> user-name(HTML)
-
'{{ }}'
: 머스태치
라우팅
-
https://www.naver.com/user/ 에서 컨텍스트 루트는 https://www.naver.com
-
만약 http://www.naver.com/user/#/kim 이라면 '#' 이하부터는 프론트엔드에서 라우팅 된거임
-
프론트엔드에서 Vue로 라우팅하기 위해서는 단순히 Vue 뿐만아니라 Vue-router가 필요
동적변경
- v-model을 이용하여 동적변경을 간단하게 작성할 수 있음
<body>
<div id="app">
<input v-model="name">
<br/>
{{name}}
</div>
</body>
<script>
new Vue({
el: '#app',
data: {
name: 'kimdongmin',
}
});
</script>
Vue Import
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
HelloWorld
},
}
</script>
- Vue파일을 import시킨다.
- import구문의 결과로 vue컴포넌트가 반환된다.
- 단, 불러 오는 시점에서 js객체로 바뀌게 된다. (컴파일 되는거임)
- components에 포함시켜준다.
- 그래야 template 구문에 사용할 수 있다.
Vue.js 라이프사이클
-
beforeCreate: 돔에 접근 불가능
-
created: this.data 또는 this.fetchData() 를 이용하여 data, methods에 정의된 값에 접근 가능. 단, template 속성에 정의된 돔 요소로 접근 불가
-
beforeMount: render() 함수가 호출되기 직전의 로직을 추가하기 좋음
-
mounted: 화면 요소(돔)에 접근할 수 있어 화면 요소를 제어하는 로직을 수행하기 좋은 단계
-
beforeUpdate: 데이터 값을 갱신하는 로직은 가급적 beforeUpdate에 추가하는 것이 좋음
-
updated: 변경 데이터의 화면 요소(돔)와 관련된 로직을 추가하는 것이 좋음
-
beforeDestroy: 뷰 인스턴스가 파괴되기 직전에 호출. 뷰 인스턴스의 데이터를 삭제하기 좋은 단계
-
destroyed
new Vue({ el: '#app', data: { name: 'kimdongmin', }, beforeCreate: function(){ console.log("beforeCreate") }, created: function(){ console.log("created") }, mounted: function(){ console.log("mounted") this.name = 'dongminkim' // beforeUpdate & updated 라이프사이클로 넘어가기 위해 name을 update 함 }, beforeUpdate: function(){ console.log("beforeUpdate") }, updated: function(){ console.log("updated") } });
- 이러한 라이프사이클 메소드 내에 각 라이프사이클 시점에 맞는 로직을 작성할 수 있다.
Vue 컴포넌트
- 전역 컴포넌트
Vue.component('컴포넌트 이름', {
// 컴포넌트 내용
});
예시
<body>
<div id="app">
<button>컴포넌트 등록</button>
<test-component></test-component> <!-- JS의 카멜케이스는 HTML에서 케밥케이스로 바뀜 -->
</div>
</body>
<script>
// 글로벌 컴포넌트
Vue.component('testComponent', { // JS의 카멜케이스는 HTML에서 케밥케이스로 바뀜 (testComponent -> test-component)
template: '<div>전역 컴포넌트가 등록되었습니다.</div>',
});
new Vue({
el: '#app',
data: {
name: 'kimdongmin',
},
});
</script>
- 지역 컴포넌트
<script>
var cmp = {
template: '<div>지역 컴포넌트가 등록되었습니다!</div>'
};
new Vue({
el: '#app',
components: {
'my-local-component': cmp
}
});
</script>
- 지역 컴포넌트 & 전역 컴포넌트
<body>
<div id="app">
<b>컴포넌트 등록</b>
<test-local-component></test-local-component> <!-- 지역 컴포넌트 표시 -->
<test-global-component></test-global-component> <!-- 전역 컴포넌트 표시 -->
</div>
</body>
<script>
// 전역 컴포넌트 등록
Vue.component('test-global-component', {
template: "<div>전역 컴포넌트입니다.</div>" // 전역 컴포넌트 내용
});
// 지역 컴포넌트 내용
var localcmp = {
template: "<div>지역 컴포넌트입니다.</div>"
}
new Vue({
el: '#app',
data: {
},
components:{ // 지역 컴포넌트 등록
'test-local-component' : localcmp,
}
});
</script>
- 전역 컴포넌트는 app, app1 ... 등 재사용이 가능하지만 지역 컴포넌트는 그렇지 못하다.
<body>
<div id="app">
<simple-component></simple-component> <!-- 전역 컴포넌트 재사용 O -->
<local-component></local-component> <!-- 로컬 컴포넌트 -->
</div>
<div id="app1">
<simple-component></simple-component> <!-- 전역 컴포넌트 재사용 O -->
<local-component></local-component> <!-- Error 로컬 컴포넌트 재사용 X -->
</div>
</body>
<script>
Vue.component('simpleComponent', {
template: '<b>Hello, world!</b>',
});
var cmp = {
template: '<h1>Hello world local compoenents</h1>'
}
new Vue({
el: "#app",
data: {
},
components: {
'local-component': cmp,
},
});
new Vue({ // 생성자 추가
el: "#app1",
data: {
},
});
</script>
상하위 컴포넌트 관계
props
를 통해 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달한다.- 하위 컴포넌트에서 상위 컴포넌트로는 이벤트만 전달할 수 있음. (이벤트 버스를 이용하여 데이터 전달 가능?)
Vue.component('child-component', {
props:['props 속성 이름'],
});
예시
<body>
<div id="app">
<child-component :propstest="message"></child-component>
</div>
</body>
<script>
Vue.component('child-component', {
props:['propstest'],
template: "<p>{{propstest}}</p>"
});
new Vue({
el: '#app',
data: {
message: "hello, world",
},
});
</script>
상하위 컴포넌트 간 이벤트
- 이벤트 발생
this.$emit('이벤트명')
- 이벤트 수신
<child-component v-on:이벤트명="상위 컴포넌트의 메서드명"></child-component>
예시
<body>
<div id="app">
<child-component v-on:show-log="printText"></child-component>
<!-- show-log: 하위 컴포넌트의 이벤트 명, printText: 상위 컴포넌트의 메서드 명 -->
</div>
</body>
<script>
Vue.component('child-component', {
template: "<button v-on:click='showLog'>show</button>" // 버튼 요소 추가
,methods:{
showLog:function(){
this.$emit('show-log'); // 이벤트 발생 로직 (케밥 케이싱)
}
}
});
new Vue({ // 최상위 컴포넌트
el: '#app',
data: {
message: "hello, world",
},
methods:{
printText:function(){
console.log(this.message);
}
}
});
</script>
이벤트 버스
- 같은 레벨의 컴포넌트의 경우, 데이터 전달은 상위 컴포넌트(공통)로 이벤트를 전달 후 props를 이용하여 하위 컴포넌트들(이벤트를 전달한 자기자신 포함, 대상 같은레벨 컴포넌트)에게 데이터를 전달함
- 이와 같은 데이터 전달 구조는 비효율적이므로 '이벤트 버스'를 활용
- 이벤트 버스는 상하위 관계를 유지하고 있지 않아도 컴포넌트 간 데이터 전달 가능. (Vuex 주로 사용)
<body>
<div id="app">
<child-component></child-component>
</div>
</body>
<script>
var eventBus = new Vue(); // 이벤트 버스 인스턴스 생성
// 하위 컴포넌트
Vue.component('child-component',{
template: "<button v-on:click='showLog'>show</button>",
methods:{
showLog:function(){
eventBus.$emit('triggerEventBus', 100, 150, "hello"); // 이벤트 발신, $emit
}
}
});
// 최상위 컴포넌트
new Vue({
el: '#app',
data: {
message: "hello, world! Vue.js"
},
methods:{
},
created:function(){
eventBus.$on('triggerEventBus', function(value1, value2, value3){ // 이벤트 수신, $on
console.log(value1, value2, value3);
});
}
});
</script>
- 하지만, 컴포넌트가 많아지면 데이터를 어디서 어디로 보냈는지 관리가 어려워져 Vuex(뷰엑스)라는 상태 관리 도구가 필요하다.
라우팅
-
웹 페이지 간의 이동 방법으로 싱글 페이지 애플리케이션에서 주로 사용
-
싱글 페이지 애플리케이션: 페이지를 이동할 때마다 서버에 웹 페이지를 요청하여 새로 갱신하는 것이 아니라 미리 해당 페이지들을 받아 놓고 페이지 이동 시에 클라이언트의 라우팅을 이용하여 화면을 갱신하는 패턴
-
CDN: 콘텐츠 전송 네트워크(Content delivery network 또는 content distribution network)
<body>
<div id="app">
<h1>뷰 라우터 예제</h1>
<p>
<router-link to="/main">Main 컴포넌트로 이동</router-link> <!-- to=""에 정의된 텍스트 값이 브라우저 URL 끝에 추가됨 -->
<router-link to="/login">Login 컴포넌트로 이동</router-link>
<router-link to="/sub">Sub 컴포넌트로 이동</router-link>
</p>
<router-view></router-view> <!-- URL 값에 따라 갱신되는 화면 영역 -->
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue-router.js"></script> <!-- 라우터 CDN -->
<script>
// 3. Main. Login 컴포넌트 내용 정의
var Main = { template: '<div>main</div>' }; // Main 컴포넌트 정의
var Login = { template: '<div>login</div>' };
var Sub = { template: '<div>sub</div>'}
// 4. 각 url에 해당하는 컴포넌트 등록
var routes = [
{ path: '/main', component: Main }, // routes 변수에는 URL 값이 /main 일 때 Main 컴포넌트를 표시하도록 정의
{ path: '/login', component: Login },
{ path: '/sub', component: Sub },
];
// 5. 뷰 라우터 정의
var router = new VueRouter({ // 뷰 라우터를 하나 생성하고, routes를 삽입해 URL에 따라 화면이 전환될 수 있게 정의
routes
});
// 6. 뷰 라우터를 인스턴스에 등록
var app = new Vue({
router // 라우터 추가
}).$mount('#app'); // $mount: 인스턴스를 화면에 붙여줌 (el 속성과 동일)
// 인스턴스를 생성할 때 el 속성을 넣지 않았더라도 생성하고 나서 $mount()를 이용하면 강제로 인스턴스를 화면에 붙일 수 있음
</script>
</body>
라우터 URL의 해시 값(#)을 없애려면
- 히스토리 모드(history mode) 활용
var router = new VueRouter({
mode: 'history',
routes
});
네스티드 라우터
- 라우터로 페이지를 이동할 때, 최소 2개 이상의 컴포넌트를 화면에 나타낼 수 있게 함
- 네스티드 라우터를 이용하면 URL에 따라서 컴포넌트의 하위 컴포넌트가 다르게 표시됨
<body>
<div id="app">
<router-view></router-view> <!-- User (상위) 컴포넌트가 뿌려질 영역 -->
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue-router.js"></script>
<script>
var User = { // 상위 컴포넌트 템플릿 정의 (가시적으로 이해를 돕기 위해 button으로 정의함)
template: `
<button>
User Component
<router-view></router-view>
</button>
`
// <router-view></router-view> : 하위 컴포넌트가 네스티드 될 영역
// 위와 같이 template을 들여쓰기하여 표기하려면 ' 가 아닌 ` 로 묶어 표기해아 함
};
var UserProfile = { template: '<button>User Profile Component</button>' }; // 하위 컴포넌트 템플릿 정의 (Profile)
var UserPost = { template: '<button>User Post Component</button>' }; // 하위 컴포넌트 (Post)
var routes = [ // 네스티드 라우팅 정의
{
path: '/user',
component: User, // /user url에 User컴포넌트 매핑
children: [ // 하위 컴포넌트 라우팅 정의
{
path: 'posts', // /posts url에 UserPost컴포넌트 매핑
component: UserPost
},
{
path: 'profile',
component: UserProfile
},
]
}
];
var router = new VueRouter({
routes
});
var app = new Vue({ // 뷰 인스턴스에 라우터 추가
router
}).$mount('#app');
</script>
</body>
-
domain 뒤에 붙이기
- /user : 최상위 컴포넌트 위에 user(상위 컴포넌트)가 붙음
- /user/profile : 최상위 컴포넌트 위에 user(상위 컴포넌트) 위에 profile(하위 컴포넌트)가 붙음
- /user/posts
-
네스티드 라우터는 화면을 구성하는 컴포넌트의 수가 적을 때는 유용하지만 많은 컴포넌트를 표시할 때는 네임드 뷰가 좋음
네임드 뷰
- 네임드 뷰는 특정 페이지로 이동했을 때, 여러 개의 컴포넌트를 동시에 표시하는 라우팅 방식
- 네임드 뷰는 위 그림 처럼 같은 레벨에서 여러 개의 컴포넌트를 한 번에 표시하는 방식
<body>
<div id="app">
<router-view name="header"></router-view> <!-- name="header" 라는 name을 명시 -->
<router-view></router-view> <!-- name이 없는 경우 'default' -->
<router-view name="footer"></router-view>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vue-router.js"></script>
<script>
// 컴포넌트의 템플릿 정의
var Body = { template: '<div>This is Body</div>' };
var Header = { template: '<div>This is Header</div>' };
var Footer = { template: '<div>This is Footer</div>' };
var router = new VueRouter({
routes: [
{
path: '/', // 루트 url에 모두 표시; URL 값 '/'에 의해 네임드 뷰가 바로 실행 됨
components: { // <router-view>의 name 속성과 컴포넌트를 연결
default: Body, // default에 Body 컴포넌트 연결 (default는 <router-view> 태그에서 name을 정의하지 않은 곳에 매핑됨)
header: Header, // header에 Header 컴포넌트 연결. <router-view name="header"> 에 매핑
footer: Footer
}
}
]
})
var app = new Vue({
router
}).$mount('#app');
</script>
</body>
뷰 HTTP 통신
-
웹 앱 HTTP 통신의 대표적인 사례로는 jQuery의 ajax가 있음 (JavaScript)
-
ajax는 서버에서 받아온 데이터를 표시할 때, 화면 전체를 갱신하지 않고 화면 일부분만 변경할 수 있게 함
-
Vue에서는 뷰 리소스 또는 Axios를 사용하여 HTTP 통신을 함. Axios를 통해 Vue와 Spring Controller 간 데이터 전송 가능
-
뷰 리소스
-
Axios 액시오스
-
액시오스 또한 NPM을 이용하여 설치하거나 CDN(콘텐츠 전송 네트워크)을 이용하여 설치할 수 있음
-
Axios CDN
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
- 위 코드를 HTML에 추가하면 라이브러리를 로딩하여 사용할 수 있는 상태가 됨
1.
// HTTP GET 요청
axios.get('URL 주소').then().catch();
- .get() : HTTP GET 요청을 보냄
- .then() : 서버에서 보낸 데이터를 정상적으로 받아오면 then() 안에 정의한 로직 실행
- .catch() : 데이터를 받아올 때 오류 발생 시 catch() 안에 정의한 로직 수행
2.
// HTTP POST 요청
axios.post('URL 주소').then().catch();
- .post() : HTTP POST 요청을 보냄
- .then() : 서버에서 보낸 데이터를 정상적으로 받아오면 then() 안에 정의한 로직 실행
- .catch() : 데이터를 받아올 때 오류 발생 시 catch() 안에 정의한 로직 수행
3.
// HTTP 요청에 대한 옵션 속성 정의
axios({
method: 'get',
url: 'URL 주소',
...
});
-
HTTP 요청에 대한 자세한 속성들을 직접 정의가능
-
예시
<body>
<div id="app">
<button v-on:click="getData">프레임워크 목록 가져오기</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script> <!-- axios CDN -->
<script>
new Vue({
el: '#app',
methods: {
getData: function() {
axios.get('https://raw.githubusercontent.com/joshua1988/doit-vuejs/master/data/demo.json')
.then(function(response) {
console.log(response);
}).catch(function(){
console.log('Error!')
});
}
}
});
</script>
</body>
- '프레임워크 목록 가져오기' 버튼 클릭 후 '개발자 도구'로 객체 확인
뷰 템플릿
- 자바스크립트 표현식 사용 시 주의할 점
<!-- 1. -->
{{ var a = 10; }} <!-- X, 선언문은 사용 불가능 -->
{{ if (true) {return 100} }} <!-- X, 분기 구문은 사용 불가능 -->
{{ true ? 100 : 0 }} <!-- O, 삼항 연산자로 표현 가능 -->
<!-- 2. -->
{{ message.split('').reverse().join('') }} <!-- X, 복잡한 연산은 인스턴스 안에서 수행 -->
{{ reversedMessage }} <!-- O, 스크립트에서 computed 속성으로 계산 후 최종 값만 표현 -->
- 캐싱: 데이터나 값을 임시 장소에 미리 복사해 놓는 동작
- 뷰 디렉티브: HTML 태그 안에 v- 접두사를 가지는 모든 속성 ex) v-bind, v-if ...
뷰 디렉티브
v-if
지정한 뷰 데이터 값의 참, 거짓 여부에 따라 HTML 태그를 화면에 표시하거나 미표시v-for
지정한 뷰 데이터의 개수만큼 HTML 태그를 반복 출력v-show
v-if와 유사, v-if는 거짓시 태그를 완전 삭제하지만 v-show는 css효과만 display:none으로 주어 실제 태그는 남고 화면 상으로만 보이지 않음v-bind
HTML 태그의 기본 속성과 뷰 데이터 속성을 연결v-on
화면 요소의 이벤트를 감지 ex) v-on:click은 해당 태그의 클릭 이벤트를 감지하여 특정 메서드를 실행할 수 있음v-model
form(폼)에서 주로 사용되는 속성. 폼에 입력한 값을 뷰 인스턴스의 데이터와 즉시 동기화
computed 속성과 methods 속성의 차이점 (+ watch 속성)
- methods 속성은 호출할 때만 해당 로직이 수행되고, computed 속성은 대상 데이터의 값이 변경되면 자동적으로 수행됨
computed 장점
- computed 속성의 첫 번째 장점은 data 속성 값의 변화에 따라 자동으로 다시 연산
- computed 속성의 두 번째 장점은 캐싱 - 동일한 연산을 반복해서 하지 않기 위해 연산의 결과 값을 미리 저장하고 있다가 필요할 때 호출
- 만약 화면 여러 곳에 같은 값을 표시해야 한다면 캐싱을 제공하는 computed를 활용하면 좋음
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
computed: { // computed
reverseMsg: function() {
return this.message.split('').reverse().join('');
}
}
});
watch 속성
- watch 속성은 데이터 변화를 실시간으로 감지하여 자동으로 특정 로직을 수행
- 데이터 호출과 같이 시간이 상대적으로 더 많이 소모되는 비동기 처리에 적합
<body>
<div id="app">
<input v-model="message"> <!-- v-model 활용 -->
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
watch: { // watch
message: function(data) {
console.log("message의 값이 바뀝니다 : ", data);
}
}
});
</script>
</body>
.vue 파일 구조 / 싱글 파일 컴포넌트 체계
- .vue 파일은 아래와 같은 기본구조를 가진다.
<template>
<!-- HTML 태그 내용 -->
</template>
<script>
export default {
// 자바스크립트 내용
}
</script>
<style>
/* CSS 스타일 내용 */
</style>
Author And Source
이 문제에 관하여([Vue.js] 뷰 기초), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@kmdngmn/Vue.js-기본-구조저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)