프로토타입(Prototype) 뽀개버리기!! 

 

함수도 객체다

함수의 기본 기능인 코드 실행뿐만 아니라, 함수 자체가 일반 객체처럼 프로퍼티들을 가질 수 있다.

function add(x, y) { 
return x+y; 
}

add.status = 'OK';
console.log(add.status)
  1. add( ) 함수를 생성할 때 함수 코드는 함수 객체의 [[Code]] 내부 프로퍼티에 자동으로 저장된다(이것은 ECMAScript 명세서를 참조한 것이다* ).
  2. add() 함수에 마치 일반 객체처럼 status 프로퍼티를 생성하고 저장한 것을 확인할 수 있다.
  3. status 프로퍼티도 일반 객체에서의 접근 방식처럼 add.status를 이용해 접근 가능하다. 이처럼 자바스크립트에서 함수는 특정 기능의 코드를 수행하는 역할뿐만 아니라, 일반 객체처럼 자신의 프로퍼티를 가질 수 있는 특별한 객체라고 볼 수 있다.

함수는 객체이기 때문에, 일반 객체처럼 취급될 수 있다.

따라서 다음과 같은 동작이 가능하다.

  • 리터럴에 의해 생성
  • 변수나 배열의 요소,객체의 프로퍼티 등에 할당 가능
  • 함수의 인자로 전달 가능
  • 함수의 리턴값으로 리턴 가능
  • 동적으로 프로퍼티를 생성 및 할당 가능

이와 같은 특징 때문에 함수를 일급 객체라고 부른다.

더보기

일급 객체(First Class)

일급 객체라는 말은 컴퓨터 프로그래밍 언어 분야에서 쓰이는 용어로서, 위에 나열한 기능이 모두 가능한 객체를 일급 객체라고 부른다. 자바스크립트 함수가 가지는 이러한 일급 객체의 특성으로 함수형 프로그래밍이 가능하다.

정리하면,

자바스크립트 함수의 기능은 C나 자바와 같은 다른 언어 함수의 기능과 거의 비슷하다.

 

하지만 기본적인 기능 외에도, 자바스크립트에서 함수를 제대로 이해하려면, 함수가 일급 객체라는 것을 아는 것이다.

즉, 함수가 일반 객체처럼 값으로 취급된다는 것을 이해해야 한다. 이 때문에 함수를 변수나 객체, 배열 등에 값으로도 저장할 수 있으며, 다른 함수의 인자로 전달한다거나 함수의 리턴값으로도 사용 가능하다는 것을 알아야 한다.

하지만 일반 객체와는 조금 다르다

무엇이 다를까?

함수 객체만의 표준 프로퍼티가 정의되어 있다.

function add(x, y) { 
    return x+y; 
}

console.dir(add);

 

arguments, caller, length 등과 같은 다양한 프로퍼티가 기본적으로 생성된다. 이러한 프로퍼티들이 함수를 생성할 때 포함되는 표준 프로퍼티다.

참고로 ECMA5 스크립트 명세서에는 모든 함수가 length와 prototype 프로퍼티를 가져야 한다고 기술하고 있다.

  • arguments: 호출 시 전달되는 인자 값
  • caller: 자신을 호출한 함수를 나타낸다
  • name: 함수의 이름
  • length: 함수가 정상적으로 실행될 때 기대되는 인자의 개수
  • __proto__: 부모 역할을 하는 프로토타입 객체를 가리킨다. = [[Prototype]] = __proto__ → Function.prototype
  • prototype: 함수가 생성될 때 만들어지며, 단지 constructor 프로퍼티 하나만 있는 객체를 가리킨다. 그리고 prototype 프로퍼티가 가리키는 프로토타입 객체의 유일한 constructor 프로퍼티는 자신과 연결된 함수를 가리킨다. 즉, 자바스크립트에서는 함수를 생성할 때, 함수 자신과 연결된 프로토타입 객체를 동시에 생성하며, 이 둘은 각각 prototype 과 constructor 라는 프로퍼티로 서로를 참조하게 된다.

함수 객체는 항상 부모 역할을 하는 객체를 가리킨다: 암묵적 프로토타입 링크

모든 자바스크립트 객체는 자신의 프로토타입을 가리키는 [[Prototype]] 이라는 내부 프로퍼티를 가진다.크롬 브라우저는 이 프로퍼티가 __proto__ 프로퍼티로 구현되어 있다.

이것이 가리키는 부모 역할을 하는 프로토타입 객체는 Function.prototype 객체이다.

 

그리고 모든 함수는 Function Prototype 객체의 프로퍼티나 메소드를 마치 자신의 것처럼 상속받아 그대로 사용할 수 있다. - constructor, toString(), apply(), call(), bind()

 

그런데 ECMAScript 명세서에는 Function.prototype은 함수라고 정의하고 있다.
그리고 함수 역시 객체이므로, 자신의 부모 역할을 하는 프로토타입 객체를 가리킨다.
그렇다면 이러한 규칙에 의해 Function.prototype이 함수니까, 이것도 Function.prototype 객체, 즉, 자기 자신을 부모가 갖는 것인가?

 

ECMAScript 명세서에는 예외적으로 Function.prototype 함수 객체의 부모는 자바스크립트의 모든 객체의 조상격인 Object.prototype 객체라고 설명하고 있다.
때문에 Function Prototype 객체의 proto 프로퍼티는 Object.prototype 객체를 가리킨다.

 

함수는 Prototype 객체랑 같이 다닌다: prototype 프로퍼티

함수 객체의 prototype 프로퍼티는 함수가 생성될 때 만들어진다.

이 프로퍼티는 단지 constructor 프로퍼티 하나만 있는 객체를 가리킨다.

 

그리고 prototype 프로퍼티가 가리키는 프로토타입 객체의 유일한 constructor 프로퍼티는 자신과 연결된 함수를 가리킨다.

즉, 자바스크립트에서는 함수를 생성할 때, 함수 자신과 연결된 프로토타입 객체를 동시에 생성하며, 이 둘은 각각 prototype 과 constructor 라는 프로퍼티로 서로를 참조하게 된다.

 

예제로 살펴보자.

function myFunction() { 
    return true;
}

console.dir(myFunction.prototype);
console.dir(myFunction.prototype.constructor);

  1. myFunction() 이라는 함수를 생성했다. 함수가 생성됨과 동시에 myFunction() 함수의 prototype 프로퍼티에는 이 함수와 연결된 프로토타입 객체가 생성된다.
  2. myFunction.prototype은 myFunction() 함수의 프로토타입 객체를 의미한다. constructor, __proto__ 라는 두 개의 프로퍼티를 가진다.
    이 객체는 myFunction() 함수의 프로토타입 객체이므로 constructor 프로퍼티가 있다.
    이 객체 역시 자바스크립트 객체이므로, 예외 없이 자신의 부모 역할을 하는 __proto__ 가 있다.
  3. myFunction.prototype.constructor 의 결과 값을 보면, myFunction() 함수를 가리키고 있다.

함수 객체와 프로토타입 객체는 prototype 과 constructor 라는 프로퍼티를 통해 서로를 참조하는 관계이다.

 

프로토타입의 두 가지 의미

자바스크립트는 다른 언어와는 다르게 프로토타입 기반의 객체지향 프로그래밍을 지원한다.

객체지향 프로그래밍에서는 클래스를 정의하고 이를 통해 객체를 생성하지만, 자바스크립트에는 이런 개념이 없다.대신에 객체 리터럴이나 생성자 함수로 객체를 생성한다.

 

이렇게 생성된 객체의 부모 객체가 바로 '프로토타입' 객체이다.

모든 객체에는 자신의 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티([[Prototype]] 프로퍼티)가 있는데, 이러한 링크를 암묵적 프로토타입 링크(Implicit prototype link) 라고 부른다.

 

주의할 점은, 암묵적 프로토타입 링크와 prototype 프로퍼티를 구분해야 한다.

모든 객체는
자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를
자신의 부모 객체로 설정하는 암묵적 프로토타입 링크로 연결한다.

예제를 살펴보자.

function Person(name) {
    this.name = name;
};

var foo = new Person('foo');

console.dir(Person);
console.dir(foo);

 

Person() 생성자 함수로 생성된 foo 객체는 Person() 함수의 프로토타입 객체를 암묵적 프로토타입 링크로 연결한다.

따라서
Person 함수 객체의 prototype 프로퍼티 = foo 객체의 __proto__ 프로퍼티
가 성립한다.

prototype 프로퍼티는 함수 입장에서 자신과 링크된 프로토타입 객체를 가리키는 것이고,
__proto__(암묵적 프로토타입 링크) 프로퍼티는 객체의 입장에서 자신의 부모 객체인 프로토타입 객체를 가리키는 것이다.

 

프로토타입 체이닝

객체는 자기 자신의 프로퍼티 뿐만 아니라, 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티 또한 자신의 것처럼 접근하는 것이 가능하다.

이것을 가능하게 하는 것이 바로 프로토타입 체이닝이다.

자바스크립트에서는 특정 객체의 프로퍼티나 메서드에 접근하려고 할 때,
해당 객체에 접근하려는 프로퍼티 또는 메서드가 없으면
암묵적 프로토타입 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티를 차례대로 검색한다.

이것을 프로토타입 체이닝이라고 한다.

객체 리터럴 프로토타입 체이닝

리터럴로 생성된 객체는 내부적으로 Object() 함수 객체를 통해 생성된다.

Object() 함수는 암묵적 프로토타입 링크로 Object.prototype 객체를 가리킨다.

 

이에 따라 어떻게 프로토타입 체이닝이 이뤄지는지 예제를 살펴보자.

var myObject = {
    name: 'foo',
    sayName: function() {
        console.log('My Name is ' + this.name);
    }
}

myObject.sayName();
console.log(myObject.hasOwnProperty('name'));
console.log(myObject.hasOwnProperty('nickName'));
myObject.sayNickName();

왜 myObject 객체가 hasOwnProperty() 메서드를 호출할 때는 에러가 발생하지 않았을까?

 

객체 리터럴로 생성한 객체는 Object()라는 내장 생성자 함수로 생성된 것이다.

Object() 생성자힘수도힘수객체이므로prototype이라는프로퍼티 속성이 있다.

 

따라서 Object() 함수의 prototype 프로퍼티가 가리키는 Object.prototype 객체를 자신의 프로토타입 객체로 연결한다.

 

  1. myObject.sayName(); : 해당 객체 내에 메서드가 있어서 바로 수행된다.

  2. myObject.hasOwnProperty('name') : myObject 내에는 hasOwnProperty() 라는 메서드가 없다.
    따라서 암묵적 프로토타입 링크를 따라, 그것의 부모 역할을 하는 Object.prototpye 프로토타입 객체에서 hasOwnProperty() 메서드가 있는지 검색한다.
    hasOwnProperty() 메서드는 자바스크립트 표준 API 로 Object.prototype 객체에 포함되어 있다.
    따라서 코드가 정상적으로 수행된다.

생성자 함수로 객체를 생성하는 경우의 프로토타입 체이닝

생성자 함수로 객체를 생성하는 경우는 객체 리터럴 방식과 약간 다른 프로토타입 체이닝이 이뤄진다.

예제를 살펴보자.

function Person(name, age, hobby) {
    this.name = name;
    this.age = age;
    this.hobby = hobby;
};

var foo = new Person('foo', 30, 'tennis');

console.log(foo.hasOwnProperty('name')); // true
console.dir(Person.prototype);

foo.hasOwnProperty('name') 를 실행하면 먼저 foo 객체에서 hasOwnProperty() 메서드를 찾는다.

 

foo 객체에는 없으므로, 암묵적 프로토타입 링크를 통해 부모 객체인 Person.prototype 에서 hasOwnProperty() 메서드를 찾는다.

 

Person.prototype 에도 없으므로, Person.prototype 의 프로토타입 객체에서 찾는다. Person.prototype 객체도 역시 자바스크립트 객체이므로 Object.prototype 을 프로토타입 객체로 가진다. 따라서 Object.prototype 에서 hasOwnProperty() 메서드를 찾는다.

 

Object.prototype 객체의 hasOwnProperty() 메서드를 실행한다.

 

위의 예제들을 통해 알 수 있듯이 Object.prototype 객체프로토타입 체이닝의 종점이다.

 

이와 같은 방식으로 자바스크립트의 숫자, 문자열, 배열 등에서 사용되는 표준 메서드들의 경우, Number.prototype, String.prototype, Array.prototype 등에 정의되어 있다.

 

프로토타입 체이닝 활용하기

빌트인 프로토타입에 메서드 추가하기

자바스크립트는 Object.prototype, String.prototype 등과 같이 표준 빌트인 프로토타입 객체에도 사용자가 직접 정의한 메서드들을 추가하는 것을 허용한다.

Object.prototype.testMethod = function() {
    console.log('test method');
}

프로토타입 객체에 동적으로 프로퍼티 추가/삭제하기

프로토타입 객체 역시 자바스크립트 객체이므로 일반 객체처럼 동적으로 프로퍼티를 추가/삭제하는 것이 가능하다.

그리고 이렇게 변경된 프로퍼티는 실시간으로 프로토타입 체이닝에 반영된다.

function Person(name) {
    this.name = name;
};

var foo = new Person('foo');

foo.sayHello(); // Uncaught TypeError: foo.sayHello is not a function

Person.prototype.sayHello = function() {
    console.log('Hello');
};

foo.sayHello(); // Hello

프로토타입 객체의 this 바인딩

프로토타입 객체는 메서드를 가질 수 있다.

만약 프로토타입 메서드 내부에서 this 를 사용한다면 이는 어디에 바인딩 될까?

 

객체의 메서드 호출 시 this 바인딩 규칙이 그대로 적용된다.

즉, 메서드 호출 패턴대로, this는 그 메서드를 호출한 객체에 바인딩된다.

function Person(name) {
    this.name = name;
};

var foo = new Person('foo');

Person.prototype.getName = function() {
    return this.name;
};

Person.prototype.name = 'person';

console.log(foo.getName()); // foo

console.log(Person.prototype.getName()); // person

foo.getName() 실행 시 getName() 메서드는 foo 객체에서 찾을 수 없으므로, 프로토타입 체이닝이 발생한다.
결과적으로 Person.prototype의 getName() 이 실행된다.
그런데 getName() 메서드를 호출한 객체는 foo 객체이므로, this 는 foo 객체에 바인딩이 된다.

 

Person.prototype.getName() 실행 시 Person.prototype 객체의 getName() 이 실행된다.
getName()을 호출한 객체는 Person.prototype 이므로 this는 Person.prototype 객체에 바인딩된다.

 

디폴트 프로토타입 객체를 다른 객체로 변경하기

디폴트 프로토타입 객체는 함수가 생성될 때 같이 생성되는 프로토타입 객체를 말한다.
이 객체를 다른 객체로 변경하는 것이 가능하다.

이러한 특징을 이용해서 객체지향의 상속을 구현하는 것이다.

 

여기서 주의할 점이 있다.

생성자 함수의 프로토타입 객체가 변경되면, 변경된 시점 이후에 생성된 객체들은 변경된 프로토타입 객체로 암묵적 프로토타입 링크가 이뤄진다.

 

반면, 생성자 함수의 프로토타입이 변경되기 이전에 생성된 객체들은 기존 프로토타입 객체로의 링크를 유지한다.

function Person(name) {
    this.name = name;
};

console.log(Person.prototype.constructor); // Person 함수 객체

var foo = new Person('foo');

console.log(foo.country); // undefined

Person.prototype = {
    country: 'korea'
};

console.log(Person.prototype.constructor); // Object 함수 객체

var bar = new Person('bar');

console.log(foo.country); // undefined
console.log(bar.country); // korea

console.log(foo.constructor); // Person 함수 객체
console.log(bar.constructor); // Object 함수 객체

Person.prototype 을 리터럴 객체로 변경한 후, Person.prototype.constructor 값이 Object() 생성자 함수로 출력된 것을 주목하자.

 

변경한 프로토타입 객체는 리터럴로 생성하여 단지 country 프로퍼티만 있다. 즉, 디폴트 프로토타입 객체처럼 constructor 프로퍼티가 없다.

 

이 경우에도 암묵적 프로토타입 링크는 존재하기 때문에 프로토타입 체이닝은 발생한다.

 

모든 자바스크립트의 리터럴 객체는 프로토타입 객체로 Object.prototype 을 가진다. 따라서 Object.prototype 의 constructor 가 참조하고 있는 Object() 함수를 연결하여, Object() 생성자 함수가 출력되는 것이다.

 

 

전역 수준 컴포넌트의 문제점

  • 빌드 단계가 없음
    • ECMAScript 2015 이상, Typescript와 같은 최신 문법을 사용할 수 없음
  • CSS 빌드 & 모듈화 기능이 없음
  • template이 모두 고유한 id를 가지도록 개발자가 관리해야 함

그래서 생겨난게...

단일 파일 컴포넌트(Single File Component)

단일파일 컴포넌트: <template />, <script />, <style />로 이루어진 확장자가 .vue인 파일

<template>
    <div id="comp-id">
	    ...
    </div>  
</template>

<script>
export default {
    name : 'comp-name',
    components : { ... },
    data() {
	    return {};
    },
    ...
}
</script>

<style>
    ...
</style>

 

Vue-CLI의 vue-loader라는 구성요소가 이 파일을 파싱하고, 다른 로더들을 활용해 하나의 모듈로 조합한다.

그 중 css-loader는 CSS스타일을 전처리하고 스타일 정보를 모듈화한다.

 

기본 프로젝트 구조

Vue-CLI를 통해 프로젝트를 만들게되면, 다음과 같은 폴더 구조가 생성된다.

컴포넌트에서의 스타일

컴포넌트에서 스타일을 작성할 때에는 다음과 같은 작성 방법이 있다.

  • 전역 CSS
  • 범위 CSS(Scoped CSS)
  • CSS 모듈(CSS Module)

전역 CSS

일반적인 CSS작성을 이야기한다.

단일 파일 컴포넌트 내의 <style> 태그 안에 작성하면 된다.

<style>
.class-name {
	color: black;
}
</style>

그러나, 이것은 전역 스타일이므로 페이지 전체에서 사용된다. 

때문에 다른 컴포넌트에서도 동일한 CSS클래스명을 사용하면 충돌이 발생한다.

따라서 특정 컴포넌트만의 스타일을 지정하는 범위 CSS, CSS 모듈 방식을 사용하는 것을 추천한다.

범위 CSS(Scoped CSS)

<style scoped>
.class-name {
	color: black;
}
</style>

범위 CSS를 사용할 때에는 다음 사항을 알아두자.

  • 범위 CSS는 Attribute Selector를 사용함
    • 브라우저에서 스타일을 적용하는 속도가 느림
    • ID, 클래스, 태그명 선택자를 사용해 스타일을 적용하여, 브라우저의 실행 속도가 느려지는 문제를 보완하는 것이 좋음
  • 부모 컴포넌트에서 범위 CSS를 적용하기 위해 생성되는 attribute가 자식 컴포넌트의 루트 요소에도 등록됨
    • 부모 컴포넌트에 적용된 범위 CSS는 하위 컴포넌트에도 반영됨

CSS 모듈(CSS Module)

CSS 스타일을 마치 객체처럼 다룰 수 있게 해준다.

<template>
  <div>
    <button v-bind:class="$style.hand"> CSS Module을 적용한 버튼 </button>
    <div :class="[$style.box, $style.border]">Hello World</div>
  </div>
</template>

<script>
export default {
    created() {
        console.log(this.$style);
    }
}
</script>

<style module>
.hand { cursor:pointer; background-color:purple; color:yellow; }
.box { width:100px; height:100px; background-color:aqua; }
.border { border:solid 1px orange; }
</style>

위 코드는 아래의 HTML로 생성된다.

<div>
	<button class="_1l2s2YbgGPFeFA2amkytu7_0"> CSS Module을 적용한 버튼 </button>
	<div class="_3PFteHfWSgjUHmj2ZptXNB_0 fYMDrU15gNkPkiiXEBCss_0">Hello World</div>
</div>

hand, box, border과 같이 코드에 주어진 스타일명이 아닌, 충돌하지 않도록 생성된 다른 이름이 클래스명으로 사용된다.

 

슬롯

부모 컴포넌트에서 자식 컴포넌트로 전달할 정보가 HTML 태그를 포함하고 있다면, props를 사용해 전달하기 쉽지 않다.

슬롯을 이용하면 부모 컴포넌트에서 자식 컴포넌트로 HTML 마크업을 전달할 수 있다.

 

기본 슬롯(Default Slot)

<자식 컴포넌트>

<template>
	<div>
	...
		<div class="content">
			<!-- 슬롯 정의 -->
			<slot></slot>
		</div>
	... 
	</div>
</template>

<부모 컴포넌트>

<template>
	<div>
    ...
    	<child-component>
        	<div>
            이 안에 들어오는 모든 것들이 slot으로 자식 컴포넌트에게 전달된다.
            그리고 자식 컴포넌트에서 마크업으로 렌더링하게 된다.
            </div>
        </child-component>
    ...
    </div>
</template>

명명된 슬롯(Named Slot)

슬롯에 이름을 부여하여, 컴포넌트에 여러 개의 슬롯을 작성할 수 있다.

 

<자식 컴포넌트>

<template>
    <div>
    	<header>
            <slot name="header"></slot>
        </header>
        <section id="content">
             <slot name="content"></slot>
        </section>
        <footer>
            <slot name="footer"></slot>
        </footer>
    </div>
</template>

<부모 컴포넌트>

<template>
    <div>
    	<child-component>
            <template v-slot:header>
                <!-- header slot 안에 채울 HTML 코드 작성 -->
            </template>
             <template v-slot:content>
                <!-- header slot 안에 채울 HTML 코드 작성 -->
             </template>
            <template v-slot:footer>
                <!-- header slot 안에 채울 HTML 코드 작성 -->
            </template>
        </child-component>
    </div>
</template>

참고로, 기본 슬롯의 이름은 'default'이다.

범위 슬롯(Scoped Slot)

부모 컴포넌트에서 슬롯 내부에 들어갈 컨텐츠를 작성할 때,

자식 컴포넌트로부터 필요한 정보를 전달받아서 (부모 컴포넌트에서) 출력할 내용을 커스터마이징하기 위한 용도로 범위 슬롯이 사용된다.

 

<자식 컴포넌트>

<template>
    <div class="child">
        <slot name="type1" :cx="x" :cy="y"></slot>
        <slot name="type2" :cx="x" :cy="y"></slot>
    </div>
</template>

<부모 컴포넌트>

<template>
    <div class="parent">
        <child>
            <!-- 자식으로부터 전달받은 props를 감싼 객체를 그대로 전달받아 사용 -->
            <template v-slot:type1="p1">
                <div>{{p1.cx }} + {{p1.cy}} = 
                {{ parseInt(p1.cx) + parseInt(p1.cy) }}</div>
            </template>
            <!-- 자식으로부터 전달받을 props를 지정하여 사용 -->
            <template v-slot:type2="{cx, cy}">
                <div>{{cx }} 더하기 {{cy}} 는 
                {{ parseInt(cx) + parseInt(cy) }}입니다.</div>
            </template>
        </child>
    </div>
</template>

 

동적 컴포넌트(Dynamic Component)

화면의 동일한 위치에, 상황에 따라 다른 컴포넌트를 표현할 때 사용한다.

성능상의 이유로 상태를 유지하거나 재-렌더링을 피하길 원할 때, <keep-alive>를 이용한다.

<template>
<div>
  <div class="header">
    <nav>
      <ul>
        <li>
          <a href="#" @click="changeMenu('home')">Home</a>
        </li>
        <li>
          <a href="#" @click="changeMenu('about')">About</a>
        </li>
        <li>
          <a href="#" @click="changeMenu('contact')">Contact</a>
        </li>
      </ul>
    </nav>
  </div>

  <div class="container">
    <!-- keep-alive로 감싸서 캐싱하여 여러번 실행되지 않도록 함 -->
    <keep-alive>
     <!-- 동적으로 컴포넌트가 바인딩됨 -->
      <component v-bind:is="currentView"></component>
    </keep-alive>
  </div>
  
</div>
</template>

<script>
import Home from './components/Home.vue';
import About from './components/About.vue';
import Contact from './components/Contact.vue';

export default {
  components : { Home, About, Contact },
  data() {
    return { currentView : 'home' }
  },
  methods : {
    changeMenu(view) {
      this.currentView = view;
    }
  }
}
</script>

 

재귀 컴포넌트(Recursive Component)

템플릿에서 자기 자신을 호출하는 컴포넌트를 재귀 컴포넌트라고 한다.

재귀적으로 컴포넌트를 사용하려면, 반드시 name 옵션을 지정해야 한다.

<template>
  <ul>
    <li v-for="s in subs" v-bind:class="s.type">
        {{s.name}}
        <tree :subs="s.subs"></tree>
    </li>    
  </ul>
</template>

<script>
export default {
    name : 'tree',
    props : [ 'subs' ]
}
</script>

 

 

Vue 컴포넌트? 

작고

독립적이며

재사용할 수 있는

 

-> 뷰 세계에서 가장 기본적인 구성 요소

 

컴포넌트 시스템

= 컴포넌트의 조합

컴포넌트 여러개가 모여서 컴포넌트 시스템을 이루어 대규모 애플리케이션 구축이 가능하다.

 

각각의 컴포넌트들은 트리구조로 이루어져 있으며, 서로 상호작용을 하여 애플리케이션 서비스를 이룬다.

컴포넌트 작성과 사용

<script type="text/javascript">
Vue.component('hello-component', {
    template : '<div>hello world!!!</div>'
})
</script>

파라미터

  • 태그명: 컴포넌트를 사용할 태그

    • 태그명은 대소문자를 구별하지 않으므로, 가능한 케밥 표기법을 따르는 것이 좋다!

  • 옵션: 기타 정보(template, props 등)

템플릿 작성 방식

1. 인라인 템플릿

위처럼, template 옵션에 템플릿 문자열을 사용하는 방식을 인라인 템플릿(inline template)이라고 한다.

그리 권장되는 방법은 아니다.

 

2. template 태그 사용

<template id="helloTemplate">
    <div>hello world!!!</div>
</template>

<script type="text/javascript">
  Vue.component('hello-component', {
    template : '#helloTemplate'
  })
</script>

 

3. <script type="text/x-template"> 태그 사용

<script type="text/x-template" id="helloTemplate">
    <div>hello world!!!</div>
</script>

<script type="text/javascript">
  Vue.component('hello-component', {
    template : '#helloTemplate'
  })
</script>

 

사용하기

이렇게 작성된 컴포넌트를 사용할 때는 등록한 태그명을 사용한다.

<hello-component></hello-component>

 

전체 코드를 보면 아래와 같다.

  
<!DOCTYPE html>
<html>
<head>...</head>

<!-- 템플릿 등록 -->
<template id="helloTemplate">
    <div>hello world!!!</div>
</template>

<!-- 컴포넌트 작성 -->
<script type="text/javascript">
  Vue.component('hello-component', {
    template : '#helloTemplate'
  })
</script>

<!-- 컴포넌트 사용 -->
<body>
  <div id="app">
    <hello-component></hello-component>
    <hello-component></hello-component>
    <hello-component></hello-component>
  </div>
</body>

<script type="text/javascript">
var v = new Vue({
  el : '#app'
})
</script>

</html>

 

작성 시 주의사항 1 - 자식 요소가 정해져 있는 HTML 요소

HTML 요소들은 자식 요소로 포함할 수 있는 요소들이 정해져있는 경우가 있다.

아래의 select, option 태그가 그러하다.

<select name='fruits'>
  <option value='' selected>-- 선택 --</option>
  <option value='apple'>사과</option>
  <option value='banana'>바나나</option>
  <option value='lemon'>레몬</option>
</select>

브라우저는 구문분석을 수행하다가, 자식 요소로 정해져있는 태그가 아닌, 다른 것이 왔을 때에 오류를 내뿜는다.

아래 코드를 살펴보자.

<script type="text/javascript">
  Vue.component('option-component', {
    template : '<option>hello</option>'
  })
</script>

<body>
    <div id="app">
        <select>
            <option-component></option-component>
            <option-component></option-component>
        </select>
    </div>
</body>

<script type="text/javascript">
Vue.config.devtools = true;
var v = new Vue({
  el : '#app'
})
</script>

 

브라우저는 태그 구문 분석하는 작업을 먼저 수행한 후, Vue 컴포넌트를 렌더링한다.

option-component를 select 태그 안에서 사용할수 있다고 브라우저에 등록되어있지 않으므로, Vue 컴포넌트 렌더링 단계까지 가기 전에 구문분석 오류를 내뱉는다.

 

이 문제를 해결하기 위해서 is 특성(Attribute)을 이용할 수 있다.

<select>
        <option is="option-component"></option>
        <option is="option-component"></option>
</select>

Single File Component로 작성된 경우나 <script type="text/x-template"> 태그 안에서 사용된 경우에는 굳이 is 특성을 사용하지 않아도 된다.

 

작성 시 주의사항 2 - 템플릿 요소 안에는 루트 요소가 하나

<!-- 틀린 예 -->
<template id="helloTemplate">
    <div>hello world!!!</div>
    <div>hello world!!!</div>
</template>

<!-- 옳은 예 -->
<template id="helloTemplate">
	<div>
      <div>hello world!!!</div>
      <div>hello world!!!</div>
    </div>
</template>

 

컴포넌트 옵션 - data 옵션

data 옵션은 컴포넌트 내부의 로컬 상태 정보를 저장하기 위한 용도로, 아래와 같이 작성한다.

<template id='timeTemplate'>
    <div>
        <span>{{nowTS}}</span>
        <button v-on:click="timeClick">현재 시간</button>
    </div>
</template>

<script>
Vue.component('time-component', {
    template : '#timeTemplate',
    data : function() {
        return { nowTS : 0 };
    },
    methods : {
        timeClick : function(e) {
            this.nowTS = (new Date()).getTime();
        }
    }
})
</script>

 

위 코드처럼 정상적으로 렌더링되려면 data 옵션에는 객체를 리턴하는 함수가 주어져야 한다.

이렇게 함수를 지정하는 이유는, 동일한 컴포넌트가 여러 번 사용되더라도 동일한 객체를 가리키는 것이 아니라, 새로운 객체가 리턴되도록 하여서 서로 다른 객체를 참조하도록 하기 위함이다.

 

같은 맥락에서, 아래 코드처럼 data 옵션을 지정하면 같은 객체를 참조하므로, 아래와 같이 작성해서는 안된다.

var data = { nowTS : 0 };
Vue.component('time-component', {
    template : '#timeTemplate',
    data : function() {
        return data;
    }
})

컴포넌트 옵션 - props와 event

 

 

부모 컴포넌트는 Props를 통해 자식 컴포넌트에 정보를 전달하고,

자식 컴포넌트는 event를 발생시켜서 상태를 전달한다.

 

부모에서 자식으로 단방향으로만 props를 이용한 정보 전달이 가능하다.

자식 컴포넌트에서 props를 바꿀 수 없으며, 부모로부터 전달 받는 것을 사용하는 것만 가능하다.

 

 

 

 

 

 

 

 

자식 컴포넌트에서 props 정의

props 명을 배열로 나열하거나, 엄격한 유효성 검증이 필요한 경우에는 아래처럼 객체 형태로 정의할 수 있다.

<template id="listTemplate">
    <li>{{message}}</li>
</template>

<script>
Vue.component('list-component', {
    template : '#listTemplate',
    props : [ 'message' ]    // props 명을 배열로 나열
})
</script>
<template id="listTemplate">
    <li>{{message}}</li>
</template>

<script type="text/javascript">
Vue.component('list-component', {
    template : '#listTemplate',
    props : { 
        message : { 
        	type: String, 
        	default: '안녕하세요' 
        },
        count : { 
        	type: Number, 
        	required: true 
        },
        countries: {
        	type: Array,
        	default: () => ['Korea']
        }
    } // props 명을 객체 형태로 정의
})
</script>

※ 전달받을 값이 배열이나 객체인 props에 default 값을 부여할 때에는 반드시 함수를 사용해야 한다.

부모 -> 자식 컴포넌트에 props 전달

props를 통해 정보를 전달하는 방법은 HTML 요소의 attribute에 값을 부여하는 방식과 비슷하다.

전달하는 방법은 2가지가 있다.

 

  • 값을 바로 부여하기 (String)
  • 변수나 객체를 바인딩하여 부여하기

값을 바로 부여하는 방법은 해당 props의 type이 String인 경우에만 동작한다.

그 외에는 v-bind를 통해 바인딩함으로써 전달한다.

<ul>
  <list-component message="Hello"/> <!-- 값을 그대로 부여 -->
    <list-component v-bind:count="100"/> <!-- Number type의 값을 바인딩하여 전달 -->
  <list-component :count="21"/> <!-- v-bind 축약 형태 -->
  <list-component :message="message"/> <!-- 변수를 바인딩하여 전달 -->
</ul>

 

 props를 표기할 때 주의할 점은, 컴포넌트 작성 시 props를 카멜 표기법으로 사용했다면, 이를 케밥 표기법으로 바꾸어 사용해야 한다.

태그 작성 시 attribute는 대소문자를 구분하지 않기 때문이다.

 

Event를 이용한 정보 전달

자식 컴포넌트에서는 이벤트를 발신(emit)하고, 부모 컴포넌트에서는 v-on 디렉티브를 이용해 이벤트를 수신한다.

 

<자식 컴포넌트>

<template id="childTemplate">
    <div>
    	<!-- HTML 요소의 이벤트 리스너 등록 -->
        <button v-on:click="clickEvent" :data-lang="buttonInfo.value">
	        {{ buttonInfo.text }}
        </button>
    </div>
</template>

<script>
Vue.component('child-component', {
    template : '#childTemplate',
    props : [ 'buttonInfo' ],
    methods : {
        clickEvent : function(e) {
            /**
            * 상위 컴포넌트에 'timeclick' 이벤트 발신
            * 이벤트를 발신할 때 필요한 정보들을 인자로 전달
            */
            this.$emit('timeclick', e.target.innerText, e.target.dataset.lang);
        }
    }
})
</script>

 

<부모 컴포넌트>

<template id="parent-template">
    <div>
        <child-component v-for="s in buttons" :button-info="s" 
			             @timeclick="timeclickEvent"/>
                         <!-- timeclick 이벤트 수신 및 리스너 등록 -->
        <hr />
        <div>{{ msg }}</div>
    </div>
</template>

<script>
Vue.component('parent-component', {
    template : '#parent-template',
    data : function() {
        return { 
	        msg: "",
            buttons : [ 
                { text : "Hello", value : "영어" }, 
                { text : "씬짜오", value : "베트남어" },
                { text : "니하오", value : "중국어" }
            ]
        };
    },
    methods : {
    	/** 
        * timeclick 이벤트 리스너
        * 자식 컴포넌트에서 이벤트를 발신할 때 넘겨준 2개의 인자를 전달받음
        */
        timeclickEvent : function(k, v) {
            this.msg = k + ", " +v;
        }
    }
})
</script>

 

 

 

Event Bus 객체를 이용한 통신

비어있는 Vue 인스턴스를 만들어, global한 이벤트버스로써 데이터 전달 역할을 한다.

var eventBus = new Vue();

 

 

<!-- 이벤트 버스 객체 -->
<script>
var eventBus = new Vue();
</script>

<!-- 첫번째 자식 컴포넌트 -->
<template id="chidl1Template">
    <div>
        <button v-on:click="clickEvent">child1 button!!</button>
        <div>{{currentTime}}</div>
    </div>
</template>

<script>
Vue.component('child1-component', {
    template : '#chidl1Template',
    data : function() {
        return { currentTime : '' };
    },
    methods : {
        clickEvent : function() {
			var d = new Date();
			var t = d.toLocaleTimeString() + " " + d.getMilliseconds() + "ms";

			// 이벤트 버스를 통해 click1 이벤트 발신
			eventBus.$emit('click1', t);
			this.currentTime = t;
        }
    }
});
</script>

<!-- 두번째 자식 컴포넌트 -->
<template id="chidl2Template">
    <ul>
        <li v-for="t in timelist">{{t}}</li>
    </ul>
</template>
<script type="text/javascript">
Vue.component('child2-component', {
    template : '#chidl2Template',
    data : function() {
        return { 
            timelist : []
        };
    },
    created : function() {
        // 이벤트 버스를 통해 click1 이벤트 발신
        eventBus.$on('click1', this.child1Click);
    },
    methods : {
        child1Click : function(time) {
            this.timelist.push(time);
        }
    }
});
</script>

<body>
    <div id="app">
        <child1-component></child1-component>
        <hr />
        <child2-component></child2-component>
    </div>
</body>

 

 

 

 

- End -

 

 

 

 Vue.js 공식 홈페이지에서는 단위 테스팅(Unit Testing)을 위한 테스트 러너로써 Karma를 적극 권장하고 있다.

 

어떤건지 궁금해서 한번 써본 적은 있지만, 실무에 적용해보기에 앞서 너란 놈에 대해 깊은 공부가 필요함을 느끼고...

 

정말 오랜만의 포스팅 주제로 삼았다.

 

 

KARMA

공식 홈페이지에 가보면, 이렇게 소개하고 있다.

 

A tool which spawns a web server that executes source code against test code for each of the browsers connected.

 

위의 영어가 너무 어려워서, 아래처럼 아름다운 한글로 풀어봤다.

 

카르마는 어떤 웹 서버를 생성하는 도구이다.

이 웹 서버는, 다양한 브라우저들과 연결되어 있다.

그리고, 이 웹서버는 연결되어 있는 각 브라우저 위에서 테스트를 수행한다.

 

그렇다. 이 친구의 목적은 하나다.

개발자들이 다양한 브라우저 환경에서 단위 테스트를 손쉽게 할 수 있는 아름다운 테스팅 환경을 제공하는 것.

 

그렇다면, 브라우저가 연결되어있어야 테스트가 가능한데,

카르마가 생성한 테스팅을 수행하는 웹서버는 어떻게 브라우저를 감지할까?

 

 

브라우저 감지

아래 두 가지의 방법으로 브라우저를 감지한다.

  1. 개발자가 손수 브라우저를 열어준다.
  2. 자동으로 감지하도록 할 브라우저를 카르마 config 파일에 지정해준다.

1. Manually

첫 번째 방법, 즉 손수 진행하려면 

테스트를 수행하려는 브라우저를 열고, 카르마 웹 서버가 돌고 있는 URL을 찍어준다.

 

http://<hostname>:<port>/ 

이렇게 주소를 찍고 들어올텐데, 여기서 <hostname>은  카르마 서버가 돌고 있는 그것일테고,

<port>는 그 서버가 listening하고 있는 그것임.

 

근데 로컬에서 띄우고 별 일 없으면 그 주소는 그냥 http://localhost:9876/ 일 것이다.

 

이렇게 손수 브라우저를 띄우는 방법은, pc가 아니라 모바일 환경에서 테스팅할 때 유용하다.

(물론 카르마 서버랑 같은 네트워크여야 하겠지)

 

2. Auto

그러나 웹 개발자라면, 손쉽게 자동으로 감지하도록 카르마에게 시키는 것이 아름다워 보인다.

그러려면 configuration 파일에 자동으로 띄울 브라우저 리스트를 지정해주어야 하는데, 아래처럼 하면 된다.

browsers: ['Chrome']

이렇게 하면 카르마는 알아서 자동으로 브라우저를 감지하고, 테스트를 모두 수행하면 자동으로 죽인다.

(configuration 파일에 대한 자세한 내용은, 이 포스트에서는 다루지 않기로 한다.)

 

 

이 때, 카르마는 브라우저 런처(browser launcher)를 이용해서 브라우저를 띄우는데,

사용 가능한 브라우저 런처 리스트는 아래와 같다.

대부분의 이 브라우저 런처들은 플러그인 형태로 끼워지는 것이다.

그러므로, 우선적으로 런처가 설치되어야 한다. 아래 예시처럼.

# Install the launcher first with NPM:
$ npm install karma-firefox-launcher --save-dev

 

이거슨 누군가가 미리 만들어놓은 브라우저 런처 플러그인이고, 우리가 손수 커스텀 플러그인을 만들어도 된다. 응 그냥 갖다 쓸래

 

 

그럼 이제 자동으로 띄운 브라우저에서, 어떻게 테스트 명령을 내리고 또 테스트를 실행한 결과를 받아오는지 알아보자.

 

 

웹 소켓을 통한 연결

카르마는 웹 소켓을 이용해 브라우저가 카르마 웹 서버와 커넥션을 가지도록 한다.

 

카르마는 config 에 정의된 브라우저들을 띄운 후, 시작 페이지를 카르마 서버의 URL 로 설정한다.

그리고 이 페이지가 브라우저에서 실행될 때, 웹 소켓을 통해 서버에 연결되는 것이다.

 

카르마 서버는 웹 소켓 연결을 확인하면, 클라이언트 페이지에게 테스트 실행을 지시한다. 

 

웹 소켓으로 연결되어 있기 때문에 언제든지 테스트 수행 명령을 내릴 수 있는데,

이는 카르마가 테스팅 환경으로써 넘나 훌륭한 이유라고 할 수 있다.

클라이언트 페이지의 요청이 없어도, 언제든지 카르마 서버의 필요에 의해 테스트 수행 명령을 내릴 수 있기 때문이다.

덕분에 파일의 변화가 일어날 때마다 자동으로 테스트가 실행될 수 있다.

 

 

파일 변화 자동 감지

카르마는 FS Model이라는 놈을 가지고 있다. 

소스코드 파일의 가장 마지막 버전의 타임스탬프라고 보면 된다.

 

그리고 또, FS Watcher 라는 놈도 있다.

이거슨 테스트 중인 프로젝트의 파일들에 변화가 일어나는지 감시하고, 이를 FS Model에 반영하는 역할을 한다.

 

시나리오는 대충 이렇다.

 

1. 소스코드에 변화가 일어난다.

2. FS Watcher는 이를 감지하여, FS Model의 API를 이용해 FS Model을 업데이트한다.

3. FS Model은 변경 사항이 생기면 이벤트를 발생시킨다.

4. 카르마 웹 서버는 FS Model의 변경 이벤트를 Listen하고 있다가, 변경 이벤트가 발생하면 context.html을 재생성한다.

5. 그리고 config에 restartOnFileChange가 true인 경우, 웹 소켓 커넥션을 통해 클라이언트 페이지에게 테스트 재시작을 지시한다.

 

이는 매번 소스코드에 변경이 일어날때마다 reload를 위해 해야 했던 많은 번거로운 작업들을 줄여준다.

진정한 의미의 테스트 자동화라 할 수 있겠다.

 

 

IFrame 기반 테스트

설명을 위해 위의 시나리오를 이어나가 보겠다.

 

6. 서버로부터 테스트 실행 지시를 받은 클라이언트 페이지는 iframe을 연다.

7. 그리고 서버에 context.html 을 요청해서 받아와, iframe 안에 페이지를 생성한다.

 

context.html테스트 프레임워크 어댑터소스코드(테스트의 대상), 그리고 테스트코드를 포함한다.

즉, 테스트에 필요한 모든 요소가 iframe 안에 들어간다고 보면 된다.

(테스트 프레임워크 어댑터는 밑에서 다시 설명하겠음.)

 

테스트 실행 명령이 떨어질 때마다, 클라이언트 페이지는 이 iframe을 다시 로드한다.

즉, 서버는 매번 바뀐 내용을 감지하여 context.html을 생성할테고, 바뀐 내용을 가지고 테스트를 실행하게 되는 것이다.

 

8. context.html 페이지는 로드가 끝나면, onload 이벤트를 발생시킨다.

9. 컨텍스트 페이지(context.html)의 onload 이벤트를 감지한 클라이언트 페이지는, postMessage를 통해 컨텍스트 페이지(context.html)를 클라이언트 페이지에 연결(Listen 이벤트 등록)한다.

 

 

<참고1 - postMessage>

window.postMessage() 메소드는 Windows 오브젝트 사이에서 안전하게 cross-origin 통신을 할 수 있게 한다.

예로, 페이지와 생성된 팝업 간의 통신이나, 페이지와 페이지 안의 iframe 간의 통신에 사용할 수 있다.


이렇게 연결이 되고나면, 이제 남은 일은 테스트를 실행하는 것. 그리고 그 결과를 잘 보여주는 것.

 

글탐 그 일은 누가 하느냐?

컨텍스트 페이지(context.html)에 포함된 테스트 프레임워크 어댑터를 기반으로 테스트 코드가 실행되고,

테스트 프레임워크 리포터가 결과를 전달받아 예쁘게 잘 보여준다.

 

밑으로 고고.

 

 


<참고2 - 테스트 프레임워크 & 테스트 러너>

자알~ 가다가, 갑자기 '테스트 프레임워크' 하니까 갑자기 헷갈리고, 급 의욕 떨어질 것 같아서 설명한다.

아니, 카르마는 테스트 프레임워크가 아닌건가??
흠. 위에 설명을 보니, 카르마는 테스트 러너라네.
읭? 테스트 러너가 테스트 프레임워크 아녔어? 먼솔?

카르마는 테스트 러너로, 자동화된 테스트를 보다 간단하고 빠르게 만들어주는 테스트 환경 툴이다.
그리고 앞으로 언급할 테스트 프레임워크는 Mocha, Jasmine과 같이 자동화된 테스트를 지원해주는 도구이다.
테스트 러너 위에서 테스트 프레임워크를 이용한 테스트가 이뤄진다고 볼 수 있겠다.

읭? 그런데 우리는 이전에 카르마 없이도 Mocha와 같은 테스트 프레임워크로 테스트 잘만 하였는데?

그럴 것이다. 왜냐면 우리가 잘 아는 테스트 프레임워크들도 기본적으로 테스트 러너를 제공하기 때문이다.

그러나.
그 제공한다는 러너가 빈약하기 짝이 없다.

예를 들어 Mocha의 경우, Node 기반의 러너를 제공한다.
Node 테스트만 진행한다면 문제 없겠지만 브라우저 테스트의 경우에는 얘기가 다르다.
DOM 과 같은 브라우저 API는 없기 때문에 cross-browser 문제를 이 방식으로 테스트 하는 데에는 한계가 있다.

물론, 대부분의 테스트 프레임워크에서 사용하는 HTML 러너와 같은 방식으로 브라우저에서도 실행할 수 있지만, 개발자는 테스트를 실행하려면 매번 브라우저를 열고, 러너를 다시 로드해야 하는 불편함을 감수해야 한다.
우리는 이런거 안하려고 카르마 쓰는 거고.

 

테스트 프레임워크 어댑터

Mocha, Jasmine과 같은 기존의 테스트 프레임워크를 사용하려면,

해당 프레임워크를 카르마에서 사용하기 위한 어댑터가 필요하다.

 

각 테스트 프레임워크에는 테스트를 실행하고 결과를 보고하기 위한 서로 다른 API가 있다.

어댑터는 기본적으로 테스트 프레임워크와 카르마 클라이언트 매니저 API 간의 통신을 변환하는 역할을 한다.

(클라이언트 매니저 설명은 밑에 있음)

 

카르마 config 파일에 원하는 테스트 프레임워크 어댑터를 꽂아주면 된다.

물론, 당연히 해당 플러그인이 설치되어 있어야 할 것이다.

// karma.conf.js
module.exports = function(config) {
  config.set({
    basePath: '../..',
    frameworks: ['jasmine'],
    //...
  });
};

 

 

클라이언트 매니저

클라이언트 매니저 카르마 서버와 테스트 프레임워크 어댑터가 통신하기 위한 API를 제공한다.

 

읭? 

혼돈의 카오스를 막기 위해 그림을 제공하겠다.

클라이언트 구조

카르마 서버(A)   <----> 클라이언트 매니저(B) <--(iFrame)--> 테스트 프레임워크 어댑터(C) <----> 테스트 프레임워크(D)

 

매니저(B)는 서버(A)가 프레임워크(D)와 통신하기 위한 통로이고,

어댑터(C)는 매니저(B)와 프레임워크(D)가 서로 알아들을 수 있도록 통신 내용을 변환하는 통역자 역할이라고 이해하면 된다.

 

이렇게 매니저를 통해서 클라이언트 페이지로부터 테스트 수행 지시가 떨어지면,

10. 컨텍스트 페이지의 프레임워크 어댑터는 테스트를 수행 지시를 프레임워크에 전달한다.

11. 테스트 프레임워크는 테스트를 수행한다.

12. 성공/실패 여부는 이벤트를 발생시킴으로써 클라이언트 페이지로 postMessage를 통해 전달된다.

13. 클라이언트 페이지는 다시 웹 소켓을 통해 카르마 서버에 결과를 전달한다.

 

 

테스트 프레임워크 리포터

14. 서버는 클라이언트로부터 메시지를 받으면, '브라우저' 이벤트를 발생시킨다.

15. 리포터는 서버의 '브라우저' 이벤트를 감지하여 결과 데이터를 얻는다.

 

리포터는 데이터를 인쇄하거나 파일로 저장하거나, 다른 서비스로 데이터를 전달할 수도 있다.

 

여기서 중요한 점은, 어댑터와 리포터는 거의 항상 쌍으로 제공된다는 점이다.

 

카르마는 사용자가 어떤 테스트 프레임워크를 사용할지 알 수 없고, 알 필요도 없다.

해당 프레임워크의 데이터 포맷 역시 알지 못한다.

 

결과 데이터는 테스트 프레임워크에 의해 생성되는 것이고,

어댑터에 의해 전달되는 것이고,

리포터에 의해 표현되는 것이다.

 

어떤 프레임워크를 사용하느냐에 따라 어댑터와 리포터는 거의 쌍으로 결정된다고 생각하면 된다.

 

 

 

마치며...

그냥 카르마는 대충 이런거다 설명하려다보니 생각보다 깊게 들어간 것 같다.

이렇게까지 깊게 알 필요는 없었는데;

 

웹 어플리케이션 단위 테스트를 알아보다 보니, 너무나도 많은 테스팅 툴이 있는데

어떤 툴은 다른 툴을 내부적으로 사용하고 있고,

어떤 애들은 비교당하고 있고...

 

도대체 뭐가 뭔지 뒤죽박죽이여서 카르마부터 파보자, 하고 알아봤더니

전체적인 그림이 그려지는 것 같다.

 

굿굿.

하.. 그래서 난 무슨 프레임워크 쓰지.

 

 

 

 

- end 

 



뷰 최신 버전인 2.5.17 버전에서 트랜지션을 사용해보아따.


참고로 그 사용 방법은 Vue.js 공식 페이지에 기가막히게 잘 나와있다.




뷰 트랜지션을 써보다가 마주친 문제를 설명하기에 앞서, 그놈을 어떤식으로 사용하는지 알아보즈아~.




뷰(Vue) 트랜지션 사용하기


뷰 트랜지션을 이용하여, 로고 3개를 차례로 뿅뿅뿅 나타나게하는 효과를 적용해보려고 한다.


뷰 템플릿(template) 작성

 
<transition-group name="fade" v-on:after-enter="fadeNext" tag="div" class="text-center">
     <img v-show="fade[0]" key="0" class="logo" src="../assets/logo.png">
     <img v-show="fade[1]" key="1" class="logo" src="../assets/logo.png">
     <img v-show="fade[2]" key="2" class="logo" src="../assets/logo.png">
</transition-group>
  


먼저 template 부분인데, 아주아주 간단하다.


  1. 그냥 "transition-group" 이라는 태그를 사용해주면 되고, 

  2. 효과 이름을 정해서 name="내가 정한 트랜지션 이름" 의 형태로 속성을 넣어주고,

  3. 자식 엘리먼트들에다가 'key'를 부여해주자.

  4. 그리고, 이 효과가 나타나게 해주는 switch 역할을 하는 v-show="내가 정한 switch 데이터 변수명" 을 추가해준다(요놈 스위치 변수는 아래 script 코드 부분에서 추가할거심).

  5. 마지막으로, 우리는 세 개의 로고가 뿅뿅뿅 차례차례 뜨도록 하는게 목적이니까, 첫번째 로고가 다 뜨면 다음 로고가 뜨도록 하는 메소드를 호출해주어야 한다. 
    요거슨 뷰에서 트랜지션 훅(hook)을 제공해주니, 그거슬 이용하면 된다. 
    사용하는 방법은 다양한데, 나는 'v-on' 디렉티브를 이용하는 방법을 선택했다.
    v-on:after-enter="내가 정한 메소드명" 형태로 집어넣어 주면 된다(훅 메소드도 아래 script 코드 부분에서 가할 것이니 인내하자).



<참고>


트랜지션 훅(hook) 메소드는 총 8가지가 있다.


v-on:before-enter
v-on:enter
v-on:after-enter
v-on:enter-cancelled

v-on:before-leave
v-on:leave
v-on:after-leave
v-on:leave-cancelled


각각이 어떤 것인지는 뷰 가이드에 두말하면 입아프게 너무나도 자세히 나와 있으니, 


조오기 위에 링크 눌러서 참고하시길.





아직까지는 아무런 효과를 적용해주지 않았으므로 걍 아래와 같은 정적인 상태가 보일 거심.









뷰 스타일(style) 코드 작성

얘가 뿅뿅 나타나도록 CSS 코드로 Fade-in 효과를 만들어보자.


  
.fade-enter {
  opacity: 0;
}
.fade-enter-active {
  transition: opacity .5s;
};
 


이 코드는, "fade" 라는 이름의 뷰 트랜지션을 정의해준다.


트랜지션 진입 시 처음 상태"fade-enter"로 정해주고,

트랜지션 효과"fade-enter-active"로 정해준다.


위 코드를 통해 "fade"라는 뷰 트랜지션은

=> 처음에는  'opacity: 0' 에서 시작해서 0.5초동안 opacity가 1로 변하는 트랜지션

이라고 이해하면 된다.




뷰 스크립트(script) 코드 작성

이제 중요한 것 2가지가 남았다.


1. 처음에 트랜지션 효과가 일어나도록 해주는 스위치를 만들어주는 것과,


2. 첫번째 엘리먼트에 트랜지션 효과가 끝났으면, 그 다음 엘리먼트에 효과가 일어나도록 하는 훅 메소드를 만들어주는 것.


 
let fadeIdx = 0

export default {
  ...
  data() {
    return {
      fade: [false, false, false]
    }
  },
  methods: {
    fadeNext: function() {
       this.fade.splice(fadeIdx++, 1, true)
    }
  },
  mounted() {
    setTimeout(this.fadeNext, 1000)
  }
}
...
 


fade 배열은 각각의 로고 이미지들의 트랜지션을 시작시켜주는 스위치들의 배열이다.


default 값이 모두 false 이므로, 어디선가 누군가가 값을 true로 바꿔주지 않으면, 트랜지션 효과는 일어나지 않는다.



fadeNext 함수다음 순서의 트랜지션 스위치를 켜는 역할을 한다.


즉, fade 배열에서, 다음 인덱스의 스위치를 켜는 역할을 한다.


인덱스 값의 default가 0이므로, 맨 처음 트랜지션도 해당 함수로 켜면 된다.


(위 코드에서는 mount가 되면, 1초 뒤에 맨 처음 로고의 트랜지션이 시작되도록 해놔씀)






결과물, 그리고...







읭...?


뭔가 이상하지 않음?


원하던 아름다운 그림이 아닐 거시다.


'by WANZARGEN' 이 위에있다가, 로고 트랜지션이 시작되어야 제자리로 내려간다...


by WANZARGEN 요거슬 괜히, 그냥, 한 번 짱인 척 할라고 넣은 것이 결코 아녀. 



그 뿐만이 아님.


얘네들이 제자리에서 뿅뿅뿅 하고 뜨는게 아니라, 하나가 생길때마다 왼쪽으로 밀려난다는...





문제점


이러한 현상이 나타나는 이유는 아주 심플하다.


뷰의 트랜지션은 기본적으로 v-if, v-show 를 사용하여 트랜지션을 시작시키도록 되어있다.



뭔말이냐,


아까 위에서 '트랜지션 스위치' 역할을 하는 변수가 있다고 했잖음?


그 친구가 true면  v-if = true  혹은  v-show = true  가 되고, 해당 엘리먼트가 화면에 보여지기 시작한다는 말이 된다.



이렇게 엘리먼트가 화면에 출력되는 때가 트랜지션이 시작되는 때라고 이해하면 된다.




요까지만 말해도 이해하는 당신, 아래 '참고2'는 제끼십쇼.





<참고2>


    • v-if

      이 친구는 그 대응 값이 false이면 DOM 트리에 아예 렌더링 자체가 안된다.


    • v-show

      이 친구는 그 대응 값이 false이면 렌더링은 되지만, 출력이 안된다.

      그 출력이 안되는 방식이 중요한데, 해당 엘리먼트의 스타일을 "display: none" 처리 해버린다.

      "display: none" 처리를 하게 되면, 해당 엘리먼트가 차지하는 영역 또한 사라지게 된다.

      해당 영역을 살리고 싶으면,  "visibility" 속성을 이용하면 된다.

      "visibility" 속성을 'hidden' 으로 설정하면 엘리먼트가 차지하는 영역은 살리되, 안보이게 할 수 있다.

      또 다른 방법으로는, "opacity" 속성 값을 0으로 두어 투명하게 하는 방법이 있다.

 



자신의 영역이 없다가, 트랜지션 스위치가 켜지는 때에 영역이 생겨버리니...


그에 따른 부작용으로 '안예쁜', '의도한 바 없는' 애니메이션이 되버린 것이다.




비교해서 보자.


원하지 않은 결과물

 이것이 내가 원한 그 것

 

 



 

로고의 영역이 없다가 생기니까 'by WANZARGEN' 문구가 위에 있다가 내려오는 것이고,


로고 이미지를 감싸고 있는 엘리먼트가 '가운데 정렬'인 상황에서,


엘리먼트들이 하나씩 늘어나니, 그에 따라 왼쪽으로 밀리는 것이다.



ㅠ_ㅠ


왜 이런걸 고려하지 않았을까.. 




솔루션


시간이 좀 걸려도, 트랜지션을 하루 이틀 쓸게 아니라서 

나는 뷰의 '커스텀 디렉티브'를 만들어 해결했다.


참고로 vuetifyjs 등 디자인 프레임웍을 이용해 해결하면 더 쉽고 빠르게 원하는 트랜지션을 만들 수 있을 것이지만..

나는 커스텀 디렉티브를 좀 더 심도있게 다뤄볼 겸 + 아주 라이트하고 특정한 상황에도 동작하는게 필요해서 직접 만들어 해결했다.


















  1. vue맨 2018.12.24 11:38

    정말 내용이 간지러운데를 제대로 긁어주시네요 효자손이세요? 다음번에는 믹스인에 대해서좀 알아보고 적어주세요 제발요 부탁입니다.

  2. 허허 2019.06.20 23:12

    열심히 하시네요 ㅎㅎ

자바스크립트를 공부하다보면 클로저 함수라는 놈을 만나게 된다.


그런데 요놈, 한마디로 설명하기엔 내 머릿속에 정리가 잘 안되어 있어서, 이 참에 제대로 짚고 넘어가련다.


그래서 이번 포스트는 요놈, 클로저와 스코프 체인을 다뤄보려 한다.



스코프 체인(Scope chain)?

스코프 체인이 무엇인가 설명하기 전에, 먼저 글로벌 객체와 콜 객체를 알아야 한다.

글로벌 객체 & 콜 객체?

자바스크립트가 실행되면 내부적으로 글로벌 객체를 만든다.
요 글로벌 객체에는 글로벌 변수글로벌 함수가 담겨있다.
 
같은 맥락으로,
자바스크립트의 어떤 함수가 있는데, 요 함수가 실행되면 내부적으로 Call 객체를 만든다.
콜 객체에는 그럼 뭐가 있겠느냐구. 
함수 내에서 정의된 로컬 변수와 내부 함수가 있을 것이다.

콜 객체? 듣보인데? 

콜 객체가 이해가 안가면 Arguments 로 이해하면 좀 감이 온다.
우리가 함수 내부에서 arguments를 통해 인수에 접근할 수 있는 거슨, 바로 arguments가 이 콜 객체의 프로퍼티이기 때문이라는 사실.
요 객체에, 우리가 함수 내에서 정의한 로컬 변수도, 내부 함수도 다 프로퍼티로 저장 된다는 사실.

참고로, 콜 객체는 다른 말로 activation 객체라고도 하니, 구글링할 때 참고하자.




글엏담연. 

이게 대관절 스코프 체인이랑 무슨 관련이 있는건데?


'스코프' 라는 단어에서 우리는 무언가 '범위'를 가지고 장난치나보다, 하고 예상할 수 있다.


똑똑한 우리가 이미 잘 알고 있듯, 변수는 본인이 생성된 블록 범위 안에서만 유효하다.

스코프 체인의 '스코프'는, 바로 이 변수들과 함수들의 유효 범위와 비슷하다.





넘나 친절한 예시를 통해 더 친절하게 설명해보게따-.


var name = 'global'

function func1() {
  var name = 'call 1'
  console.log('func1() name: ', name)

  function func1_func1() {
    var name = 'call 1.1'
    console.log('func1_func1() name: ', name)

    function func1_func1_func1() {
      var name = 'call 1.1.1'
      console.log('func1_func1_func1() name: ', name)
    }

    func1_func1_func1()
  }

  func1_func1()
}

function func2() {
  var name = 'call 2'
  console.log('func2() name: ', name)

   //익명함수
   return function() {
    var name = 'call anonymous'
    console.log('anonymous func() name: ', name)
  }
}

console.log('global name: ', name)
func1()
var anony = func2()
anony()



위 코드의 마지막 줄에, 


func1_func1_func1()


라는 코드를 넣고 실행하면, 당연히 우리는


ReferenceError: func1_func1_func1 is not defined


라는 에러를 마주하게 된다.


왜?

func1_func_func1 함수의 유효 범위, 즉 스코프를 벗어난 엉뚱한 데에서 실행을 해버렸으니까.



이렇듯, 자바스크립트의 모든 변수와 함수들은 스코프를 가지고 있고,

이 스코프는 무엇으로 인해 정해지냐 하면-.

바로바로 아까 위에서 설명한 '글로벌 객체'와 '콜 객체'에 저장되어 있는 변수와 함수들에 의해 정해지는 것이다.



위의 예시 코드를 스코프 별로 구분하여 어떤 객체가 생성되는가 보면, 아래와 같다.


 scope

 내용

 생성 객체

 출력

script(global)

 var name = 'global'
 function func1() {...}
 function func2() {...}

 console.log('global name: ', name)
 func1()
 var anony = func2()
 anony()


글로벌 객체

 
{
  name: 'global',
  func1: function() {...},
  func2: function() {...},

  anony: function() {...}


global name:  global 

 func1()

 var name = 'call 1'
 console.log('func1() name: ', name)

 function func1_func1() {...}

 func1_func1()


콜 객체 1

{
  name: 'call 1',
  func1_func1: function() {...}


func1() name:  call 1 

func1_func1()


 var name = 'call 1.1'
 console.log('func1_func1() name: ', name)

 function func1_func1_func1() {...}

 func1_func1_func1()


콜 객체 1.1

{
  name: 'call 1.1',
  func1_func1_func1: function() {...}
}


 func1_func1() name:  call 1.1

 func1_func1_func1()

  var name = 'call 1.1.1'
  console.log('func1_func1_func1() name: ', name)


콜 객체 1.1.1

{
  name: 'call 1.1.1'
}


func1_func1_func1() name:  call 1.1.1 

 func2()

 var name = 'call 2'
 console.log('func2() name: ', name)

 return function() {...}


콜 객체 2

{
  name: 'call 2'
} 


익명 함수는 여기 없음!


 func2() name:  call 2

 anony()

 var name = 'call anonymous'
 console.log('anonymous func() name: ', name)


콜 객체 anonymous

{
  name: 'call anonymous'
}


 anonymous func() name:  call anonymous




친절미 넘치게 


보기 좋은 그림으로까지 또 보여주자면, 아래와 같은 그림을 만나볼 수 있다.





이거슬 글로 정리하자면,

글로벌 객체와, 콜 객체가 생성된 순서대로 연결된 것을 스코프 체인이라 한다.



아까 위에서 func1_func1_func1() 를 맨 아랫줄에 넣고 실행하면, 오류를 뱉는다고 했는데 그 이유는,

'func1_func1_func1'을 실행하라는 명령을 만나면, 해당 라인이 실행되고 있는 스코프( =글로벌객체)에 'func1_func1_func1'가 있는지를 찾아보는데,

온데간데 없기 때문에 에러를 뱉는 것이다.


반대로 'func1_func1' 함수 내에서 'func1_func1_func1' 함수가 실행 가능한 이유는,

그렇지만 'func1_func1' 함수의 스코프( =Call 1.1 객체)에는 'func1_func1_func1' 프로퍼티가 존재하므로 실행이 가능한 것이다.



당연하게 여겨왔던 것들에 대한 경이로움을 느끼는 순간이 아니라고 말할 수 없을 것이다아-.




요까지가 스코프 체인에 대한 설명이었다.


근데 설명을 보다가 요놈 익명함수가 좀 거슬렸을 거신데.

요게 바로 클로저 함수를 설명하기 위한 키 포인트이므로 아래로 ㄱㄱ





클로저(Closure) 함수

새로운 예시 코드를 통해 클로저 함수를 설명하도록 하게따-.


var name = 'global' function func2() { var name = 'call 2' console.log('func2() name: ', name) //익명함수 return function(newName) { if(newName != undefined) name = newName console.log('anonymous func() name: ', name) } } console.log('global name: ', name) var anony = func2() anony() anony('abc') anony()


이 친구의 출력 결과는, 아마도 


>> global name:  global

>> func2() name:  call 2

>> anonymous func() name:  global

>> anonymous func() name:  abc

>> anonymous func() name:  abc


를 예상했을 수 있겠지만, 안타깝게도 틀렸다.


이 친구도 흐름표를 통해 만나러 가즈아-!



 #

 scope

 내용

 글로벌/콜 객체

 출력

script(global)

 var name = 'global' 
 function func2() {...}

 console.log('global name: ', name)
 var anony = func2()
 anony()

 anony('abc')

 anony()


글로벌 객체

  

  name: 'global',
  func2: function() {...},

  anony: function() {...}


global name:  global 

 func2()

 var name = 'call 2'
 console.log('func2() name: ', name)

 return function() {...}


콜 객체 2

{ name: 'call 2' } 


 func2() name:  call 2

 3

 anony()

 if(newName != undefined) name = newName
 console.log('anonymous func() name: ', name)


콜 객체 2
{ name: 'call 2' } 


콜 객체 anony

{ name: *(콜 객체 2 name) }


 anonymous func() name:  call 2

 anony('abc')

 if(newName != undefined) name = newName
 console.log('anonymous func() name: ', name)


콜 객체 2
{ name: 'abc' }


콜 객체 anony

name: *(콜 객체 2 name) } 

  anonymous func() name:  abc

 anony()

 if(newName != undefined) name = newName

 console.log('anonymous func() name: ', name)


콜 객체 2
{ name: 'abc' } 


콜 객체 anony

name: *(콜 객체 2 name) } 


  anonymous func() name:  abc



  1.  글로벌 스코프에서 var anony = func2() 라인이 실행되므로, 글로벌 객체의 anony 프로퍼티에 익명함수가 대입된다.

  2.  var anony = func2() 라인이 실행되면서 func2 함수가 실행된다.
    이 때, 새로운 로컬 변수 name이 콜 객체 2에 저장된다.
    그리고 익명함수를 읽어 반환 하는데, 이 때 name 이라는 변수가 필요하다는 것을 알아차린다.
    그래서, 스코프 체인에서 가장 가까운 상위 스코프인 콜 객체 2의 name의 주소를 기억한다.
    (만약 콜 객체 2에 name이 없다면, 그 상위인 글로벌 객체의 name 주소를 기억할 것이다.)
    즉, 외부 함수로부터 생성된 콜 객체 2의 name 프로퍼티를 참조하는 것이다.

  3. anony() 라인이 실행되면, 아까 기억해둔 콜 객체2의 name 값을 가져와 출력하고,

  4. anony('abc') 가 실행되면, 또 아까 기억해둔 콜 객체2의 name 값을 바꾼 후 출력하고,

  5. 마지막 anony() 라인이 실행되면, 또다시 콜 객체2의 name 값을 가져와 출력한다.



따라서 정상적인 실행 결과는 아래와 같다.


>> global name:  global

>> func2() name:  call 2

>> anonymous func() name:  call 2

>> anonymous func() name:  abc

>> anonymous func() name:  abc




클로저 함수를 정리해서 한 마디로 설명하자면,


상위 스코프의 로컬 변수를 참조하는 함수 내의 함수

라고 설명할 수 있겠다.



보통은 함수 내에서 사용된 로컬 변수는, 해당 함수의 실행이 종료되면 파기되는 것이 맞다.


그런데, 이와 같이 클로저 함수에 의해서 계속 참조되고 있는 경우에는, 해당 로컬 변수를 파기하지 않고 계속 보관하는 것이다.










여기까지가 스코프 체인과 클로저 함수에 대한 원리(?)였다.


사실 포스트를 작성하면서 느낀거지만... 


어느 시점에 콜 객체가 생성되고, 또 그것들은 어디에 저장되며, 정확히 언제 파기되는 것인지 등등 더 제대로 알아야 할 것이 많음을 깨닫고 있다시와..ㅜ



그래서 다음엔 '호이스팅'을 좀 다뤄볼까 한다.


다루다 보면, 자연스레 알게될 것들이 많을 것 같다.




근데.. 언제? ...ㅎㅎ.......



  1. 소문듣고왔습니다 2018.05.24 09:50

    클로저 만드신분이세요? 감사합니다

  2. 양탕구리2 2018.05.30 14:05

    대단합니다

오늘은.


요즘 아주 핫한 아마존 웹 서비스(AWS)에 대한 간단한 소개를 하고...


EC2를 만들다가 내가 겪었던 짜증나는 AWS 자동 전화인증 문제와, 해결하기 위해 거쳐야 했던 귀찮은 과정을 설명하겠다.



지금당장 전화인증 문제 해결을 위해 이 글을 읽고있는 당신, 빠르게 아래로 스크롤 ㄱㄱ.




자. 아마존 웹 서비스를 쓸거라고 하면서도 이게 정확히 뭔지 모르는 사람들이 많다.


대관절 EC2 인스턴스를 만든다는게 뭔 말인지 모르고 이 글을 읽고 있을지도 모르는 안타까운 영혼들(사실 그게 나였음)을 위해 짧게나마 설명하겠다.



아마존 웹 서비스(AWS)란?

AWS 는 Amazon Web Services 의 약자다. 이거슨 아마존이 제공하는 웹 서비스로, 각종 원격 컴퓨팅 서비스를 제공한다.

AWS 사이트에 가보면.. 엄청나게 많은 서비스들이 있다.



이 중에 '컴퓨팅'에 속하는 서비스들이 클라우드 컴퓨팅 서비스들이고, 

아래처럼 개발할 응용프로그램의 용도에 맞게 여러 버전으로 서비스가 제공되고 있다.




저것들 중, 위에서 두 번째 'Amazon EC2'를 우리는 사용할건데

EC2가 뭔지 알려면 먼저 클라우드 컴퓨팅에 대한 사전지식이 필요하다. 궈궈.



클라우드 컴퓨팅(Cloud Computing) 이란?

이건 인터넷 기반 컴퓨터의 일종으로, 공유하는 데이터나 컴퓨터 처리 자원들을 다른 장치에다가 두고, 필요할 때 요청해서 가져오는 그런 방식이다.

우리의 아마존은 컴퓨팅 파워, 데이터베이스 스토리지, 기타 IT 리소스들을 우리에게 제공해주는거다.


거기에 서버를 구축해 두든, 데이터들을 쌓아두든 내맘이고, 

우리는 인터넷이라는 친구를 통해 소켓 통신을 하든, HTTP 웹 통신을 하든.. 상관없다.



클라우드 컴퓨팅이 뭐였는지 알았다면, 이제 본격적으로 EC2가 뭔지 알아보자.


EC2(Elastic Compute Cloud) 란?

이 친구는 안전하고 크기 조정이 가능한 컴퓨팅 파워를 클라우드에서 제공하는 웹 서비스이다.

여기서 중요한 대목은 '크기 조정이 가능하다'는 부분이다. 
그니까 얘는 클라우드 컴퓨팅 서비스인데, 특이한 점은 '크기 조정이 가능하다'는 점이다.

우리가 보통 컴퓨터를 살 때를 생각해보자.

메모리는 어느정도로 할지, CPU는 뭘로 할지 등 컴퓨터 처리 자원들의 스펙들을 정해서 그에 맞는 컴퓨터를 구매한다.

처음엔 게임 롤 정도만 돌아가면 되겠지... 하고 그에 맞춰서 샀는데, 오버워치를 돌리면서 컴퓨터가 힘들어하더니

배틀그라운드 돌리니까, 비행기 뜨는 소리가 들리더니 못버티고 녹아버리는 상황을 상상해보자. ㅎㅎㅎㅎ...


처음부터 넉넉하게 배틀그라운드 돌릴 정도로 사양 맞춰서 샀으면 좋았을텐데.

그때는 그럴 줄 몰랐고, 그럴 돈도 없었다는게 문제다.


EC2의 '탄력적'이라는, 즉 '크기 조정이 가능하다'는 특징은 바로 이런 상황을 극복할 수 있도록 해주는 강력한 친구라고 할 수 있겠다.


서버를 구축해두었는데, 갑자기 내가 만든 서비스가 대박나면서 사용자가 급증했다고 하자.

그러면 당연히 트래픽이 엄청나질테고, 못버티는 수준이 돼버리면 서버가 녹아버리고 말거다..

그치만 EC2에 서버를 구축해두면, 알아서 버틸 수 있는만큼의 컴퓨팅 파워를 갖춰주는 대신에 우리에게 요금을 청구한다.

우리의 서버가 요구하는 수준에 맞게 용량을 확장하거나 축소할 수 있는 큰 장점이 있다.

그래서 이놈이 핫한거다. ㅇㅈ?




후.

서론이 길었다.


 자 그러면 지금부터는

 EC2 인스턴스를 생성, 즉 클라우드 컴퓨팅을 하기 위해 아마존에다가 내 컴퓨터를 하나 파고 거기에 연결하는 방법을

앞으로의 포스팅에 쭉 작성할까 한다.


이번 포스트에서는 간단하게 가입하는 것부터.



AWS 가입하기

여기로 가서 회원가입을 한다.

참고로 가입을 할 때, 해외 결제가 가능한 신용카드를 등록해야 하니 참고할 것.



↑ 여기에 알아서 내용을 채워 넣고 [계속] 버튼을 누르고



↑ 또 요기에 내용을 입력해야 한다.
참고로, 모든 내용은 '영어'로 입력해야 한다는 사실.
친절한듯 불친절한 이웃 네이버에게 물어보면 알려주니까 걱정말자.



다음으로 ↑ 이렇게 결제정보 입력 창이 뜬다.




↑ 이제 이렇게 전화번호를 입력해주면 전화가 온다.



↑ 이걸 전화오면 키패드에 입력해주면 된다.


문제는, 가끔가다 이게 정확히 입력했는데 틀린다고 하거나 아예 전화가 안오는 경우가 왕왕 있다. 내가 그랬다. ㅡ ㅡ


나는 잘못한게 없는데, 전화가 안걸려와서 여러번 시도하게되면 12시간동안 인증을 할 수 없게된다...




그렇다. 난 여기서 막혔다.


아니... EC2 생성하기까지 가장 어려운 부분은 어이없게 AWS 전화 인증을 받는 부분이다.



위와 같은 '전화 거는 중...' 만 뜨고 전화는 안오는 상황이 계속되어서 난감했었는데. 


고객센터가 생각보다 나쁘지 않아서 잘 해결되었다.





AWS 계정 전화 인증 문제 해결하기



나와 같은 문제로 열받고 있다면 
https://console.aws.amazon.com/support/home
이리로 가보자.


요기서 My support cases > Create case 버튼을 누르면 새로운 QnA 케이스를 만드는 아래와 같은 화면이 뜬다.



Subject 와 Description 을 제외한 부분은 위와 같이 선택하면 된다.


Subject 와 Description 은 물론 영어로 작성해주어야 하는데, 어려우면 구글 번역기 돌리자.



나는 아래와 같이 작성해서 올렸다.



< 나 → AWS >


I can not validate my account because of no calling from aws.
You give me the pin number, but there's no call to my phone. 

So I can not input the pin number u gave. and I tried it three times, and then I can't try it anymore during 12 hours. 

What is the problem? 

Only AWS has this problem with my phone. 

Please check it ASAP. 




그랬더니 메일로 이런 답신이 왔다.


< AWS → 나 >


Hello, I am very sorry for the frustrations that have been experienced while activating this account with our automated phone verification system.
To help get you up and running as quickly as possible, we can manually verify the phone number on your account with a direct call from someone here at AWS.
 To request a manual call back, click on this case in the Support Center, and then click "Reply".
Please provide the date, a contact phone number (including country/region/area code) and a time frame with your local time zone in which you would prefer to be reached.

...



이 말이 뭐냐면.


너가 이런 어처구니 없는 일을 겪어 당황했을텐데 넘나 미안해. 내가 도와주기 위해 직접 너한테 전화할게. 밑에 URL 눌러서 relply 버튼 누르고 거기다가 니네 나라가 어딘지, 지역은 어딘지, 그 지역 코드는 뭔지, 니네 로컬 타임존이 뭔지, 그리고 넌 언제 전화를 받고싶은지 알려줘. 안뇽.


이거다.


그래서 나는 알려주었고, 내가 말한 시간에 전화가 걸려왔다.


영어로 전화하면서 해결하면 되는데,


저쪽에서 말하면 0.5초씩 늦게 들려서 대화하기가 좀 어렵고,  발음이 많이 부정확해서 알아듣는 데에 좀 애를 먹었다.






여튼 이런 삽질을 거치면, EC2 생성을 할 준비가 정말로 완전히 끝난다.


아, 참고로 이게 일반전화로 하면 실패하는 확률이 적다고 하니 참고하자.



사실 .. EC2 인스턴스 생성은 워낙에 여기저기 설명이 잘 되어 있는게 많아서.. 다른거는 어려움이 없다.






'Programming > ETC' 카테고리의 다른 글

AWS :: AWS EC2 & AWS 전화 인증 안될 때  (2) 2017.12.06
  1. 양탕구리 2018.05.29 20:22

    프로그래밍 공부 시작 6개월차 취준생입니다.

    핸드폰으로 자바관련 검색하다가
    좋은 블로그인것 같아서 컴터에 북마크 하려고 했습니다.

    왠걸! 이미 제 북마크에는 이 블로그가 있었습니다.
    쫄깃쫄깃한 글만 쓰는 Wanzergen이란 사람은 대체...

    시간날때마다 블로그 모든 글 정주행 가겠습니다.

    항상 좋은 글 써주셔서 정말정말 감사합니다.

  2. 양탕구리2 2018.05.30 14:06

    전화인증 냠냠 하게됬습니다 고맙습니다

아놔. 요즘 배그에 빠져서는 블로그는 나몰라라 하고 있다 ㅠㅠ

매번 쓸때마다 오랜만이긴 한데.

역시나 오랜만이니까 기초지식 다뤄보겠다.



이번에는 자바의 클래스 패스와 그것을 시스템 환경변수에 설정하는 목적에 대해 포스팅 해보겠다.


클래스 패스(Class Path)란?

자바 가상머신이 프로그램을 실행할 때, 클래스 파일을 찾는 데에 기준이 되는 파일 경로라고 할 수 있다.
즉, 자바 가상머신이 클래스 파일을 찾는 경로다.

물론, 이 클래스 패스는 개발자인 너, 나, 우리가 지정해준다.
지정해주지 않으면 자바 가상머신은 현재 디렉토리에서 필요한 클래스들을 가져와서 쓴다.

현재 디렉토리는 현재 명령 프롬프트가 가리키고 있는, 위치하고 있는 그 경로를 말한다.

>> java 파일명.class
이러한 명령어를 명령 프롬프트 창에다가 입력해서 실행시킬텐데, 이 상황에서의 디렉토리를 말한다.

다시 돌아와서.

A 클래스에서 B 클래스를 사용하고 있다고 하자. 
그리고 A, B 클래스가 같은 폴더에 있다고 하자.

이럴 땐 그냥 두 클래스가 있는 디렉토리로 가서

 >> java A.class

이렇게 해주면 아주 잘 돌아간다.

그치만 만약, B 클래스가 'bbb'라는 폴더 안에 있다면 약간의 문제가 발생한다.
위와 같이 실행하면, 중간에 오류를 뱉어낸다.
A 클래스에서 B 클래스를 사용하고 있는데, 해당 디렉토리에는 B 클래스가 없기 때문에 그러하다.

그래서 우리는 다른 디렉토리에 있는 B 클래스를 사용하기 위해, 
자바 가상머신이 클래스를 탐색하는 경로에 B클래스가 들어 있는 경로를 지정해주면 된다.




클래스 패스 지정하기

클래스 패스는 우리가 직접 지정해줄 수가 있다. 

>> set classpath=경로

이렇게 지정해주면 된다. 참 쉽다.

경로는 전체 경로가 싹 다 들어가는데, 예를 들면 'C:\workspace\bbb' 이런게 들어갈 수 있겠다.
C드라이브 밑에, workspace폴더 밑에, bbb라는 디렉토리를  클래스 패스로 지정하겠단 뜻이다.

근데 방금 말했듯이, 저렇게 명령어를 넣게 되면, 
저렇게 클래스 패스를 지정하게 되고, 그러면 자바 가상머신은 실행할 때, 저 경로만 탐색하게 된다.
즉, 클래스 패스를 추가하는게 아니라, 말 그대로 다시 설정해주는 명령어다.

A클래스가 있는 workspace 디렉토리와, B클래스가 있는 workspace/bbb 디렉토리를 둘 다 지정해주려면,  
경로 부분에 두 개의 경로를 모두 지정해주면 된다.
두 경로를 구분해주는 것은 세미콜론(;) 되시겠다.

>> set classpath=경로1;경로2
Ex)
>> set classpath=C:\workspace;C:\workspace\bbb

근데, 디렉토리 세계에서 마침표(.)는 현재 디렉토리를 의미한다.
그래서 내가 지금 명령 프롬프트 상에서 위치한 디렉토리를 클래스 패스로 지정하고싶다 하면

>> set classpath=.

일케 입력해주면 된다.


이렇게 입력해주는 클래스 패스는 언제 어디서나, 평생펑생 유효할까?

그렇지 않다.

이것을 지정해준 명령 프롬프트 창 내에서만 유효하다.
그래서 새로운 명령 프롬프트 창을 띄우면, 또 지정해줘야 한다.


이거 너무 불편하니까, 미리 클래스패스를 고정해놓곤 하는데
우리 윈도우 유저분들. 징그럽게 많이 해서 이제는 눈 감고도 하는 바로 그거.

환경변수 셋팅. 두둥-.

바로 여기에서 클래스 패스를 지정해놓으면, 알아서 자바 가상머신은 그 경로에서 클래스 파일을 탐색하게 된다.
새로운 명령 프롬프트를 띄우더라도.



클래스 패스 고정하기


나의 컴은 맥북이라 이걸 캡쳐할 수 없는 안타까움이 있지만. 대충 말로 떼워보자면
윈도우에서 '새 사용자 변수' 창을 띄워서 환경 변수를 등록하는게 있는데
여기서 변수 이름에 'classpath'를 넣고, 변수 값에 경로를 주면 된다.

자바 처음 배울 때
아무것도 모르는데 일단 JDK를 설치하고, 환경변수를 설정하라고 하는데...
하라는대로 하긴 하겠는데... 
이랬었던 기억이 난다. 새록새록.


경로 집어 넣을 때, 

C:\Program Files\java\jdk0.0.0\bin;

이렇게 집어 넣었었는데, 꼭 마지막에 세미콜론(;) 빼먹거나, 'bin' 빼먹고서는 안된다고 하는 애들 있었다.

'bin'은 자바 소스(.java 파일)가 컴파일 되어 새롭게 생성되는 .class 파일이 모여있는 곳이다.
실제로 자바 가상머신이 실행할 수 있는 형태의 파일은 .java 파일이 아니라, .class 파일이고,
그것들이 모여있는 위치가 'bin'폴더이기 때문에 꼭 여기까지 써주어야 한다는 사실을 기억하자.





-----


사실 패키지에 대한 것까지 오늘 쓸라그랬는데..
넘나 졸린 것 ㅠㅠ 
낼 출근 똥망. 


  1. ㅁㄴㅇㄹ 2018.12.18 09:53

    감사합니다 이해잘되는거같아요 잘봣습니다

  2. 초보개발자 2020.12.29 12:40

    감사합니다~ Programming/Java 관련 글을 다봤습니다! 매일 코딩은 하지만정확히 어떻게 구동되는지 기본적인 지식이 없었는데 간단하게 설명해주셔서 잘 보고갑니다!

이번 포스트에서는 프로세스와 스레드의 차이가 뭣인지 설명하도록 하겠다.



1. 프로세스(process)란?

프로세스는 짧게 말해, 실행 중인 프로그램이라고 할 수 있다.

이 대목에서 또 프로그램은 뭐고, 프로세스는 뭔지 헷갈릴 거신데. 뭣이냐면.

프로그램은 하드디스크 등에 저장되어 있는 '실행코드'를 의미한다. 
그니까, 맘만 먹으면 실행할 수 있는거긴 한데 정적인 상태의 어떤 파일(?)을 프로그램이라 한다.

프로세스는 그 프로그램을 구동했을 때, 그 프로그램 자체 + 메모리 상에서 실행되고 있는 작업 단위를 말한다.
쉽게 말해, 프로그램을 실행한게 프로세스인데, 이 프로그램을 여러번 구동시키면 여러 개의 프로세스가 생기는거다.

정적인 프로그램과, 동적인 프로세스가 뭔지 대충 이해가 됐길 바란다.


자. 그럼 스레드는 뭐하는 놈일까.

2. 스레드(thread)란?

어떤 프로그램 안에서, 특히 프로세스 안에서 실행되는 흐름의 단위 라고 우리의 위키는 얘기하고 있다.

그니까, 어떤 프로그램을 실행시킨게 바로 프로세스이지 않음?
그 프로세스는 운영체제로부터 프로그램을 돌리기 위해 얼마만큼의 메모리를 포함해서 이것 저것 필요한 자원을 할당받는다.
그리고 스레드는 프로세스에 할당된 자원을 마구 써재끼는, 실제로 실행되는 그런 흐름으로써, 한 놈일수도 있고, 두 놈일 수도 있고, 더 많을 수도 있다.

즉, 프로세스의 업무를 여러개로 나누면 스레드가 되는건데, 얘네들은 서로 프로세스에 할당된 자원을 함께 공유하면서 열심히 작업을 처리하는 애들이다.

스레드가 나오게된 이유는, 한 개의 프로세스에 처리해야 할 작업이 많을 때, 다수 개의 스레드를 통해 동시에 처리하기 위함인데,
이게 바로 멀티스레딩이다.



멀티스레딩(multithreading)은 뭘까?

이건 한 프로세스를 다수의 실행 단위, 즉 다수의 스레드로 구분해서, 여러 스레드가 독립적으로 작업을 병렬로 처리할 수 있는 방법이다.

예를 들어, 수강신청을 100명이 동시에 하러 들어왔다고 해보자.
근데 A학생이 수강신청을 하러 먼저 들어와서, 나중에 들어온 B학생은 A학생이 수강신청을 모두 할 때까지 기다려야 한다면?
옳지 못하다!
수강신청 하루 온종일 해야할지도... 

그래서 스레드가 있는건데, 100명의 수강생을 각각 처리해줄 우리의 스레드가 있으면 A학생도 동시에 수강신청을 진행하(는것 처럼 느껴지)게 되는 거시다.
스레드가 100개까지 생성될 수 있다 했을 때, 101번째 사람부터는 100개의 스레드 중 하나가 비어서 일을 처리할 수 있는 상태가 될 때까지 기다리면 된다.

수강신청할 때 분명 꿀교양수업을 신청했는데, 완료버튼을 누르고 났더니 이미 차버려서 못했다고 뜨는 어처구니없는 상황은..
바로 이렇게 스레드 여러개가 남의 수강신청을 다 처리해주고 있기 때무닌 거시다.ㅠ_ㅠ

이렇게 독립된 작업을 동시에 처리할 수 있는게 바로 멀티스레드다.

근데 사실, 이게.. 동시라고 볼 수는 없다.
여러 스레드가 독립적으로 작업을 처리할 뿐이지, 동시에 처리하는 건 아니다.

1번 스레드가 처리하다가, 2번 스레드가 처리하다가. 이거를 번갈아가면서 하는거다.
하지만 느껴지기엔 마치 동시에 하는 것 처럼 느껴진다. 워-낙에 빠르니까.









'Programming > OS' 카테고리의 다른 글

OS :: 프로세스(process)와 스레드(thread)의 차이  (1) 2017.10.16
  1. korea HEN 2017.10.17 01:56

    쓰레드를 보려고 목놓아 기다렸는데 드디어 나왔네요 ^^

플젝이 끝난지 벌써 1달이 훌쩍... 오랜만에 기술용어들을 접하면서 멘붕이 왔다.


아니... 나 이거 분명 아는건데? 왤케 생소한거? 똥망의 냄새.


아 난 기억력이 정말 똥파리 수준이라는것조차 망각하고 말았다. 이래서 내가 블로그를 시작했지 참.

멘탈이 탈탈 털리고 정신을 차리고 아주 기초적인 용어들을 다시 잡아볼까 한다.


그 시작은 바로 에이잭스! 뚜둔-.



AJAX란?

에이잭스로 말할 것 같으면, 일단 위키는 이렇게 설명하고 있다.

Ajax는 비동기적인 웹 앱 제작을 위해 어떠한 "조합"을 이용하는 웹 개발 기법이다.

즉, 얘는 그 자체가 하나의 기술을 말하는건 아니고, 그냥 같이 쓰는 몇 개의 기술이 있는데 걔네들을 묶어서 이렇게 부르는 거다.



그렇담, 묶어서 쓴다는 그 기술들은  뭔데?

- HTML & CSS
- DOM & JavaScript
XML & XSLT & XMLHttpRequest

이런 것들이 있다고 한다.

각각의 기술들에 대한 설명은 차차 하도록 하고.


왜 이런 조합, 그러니까 AJAX가 생겨나게 된걸까 그 배경을 한번 살펴보자.




AJAX 탄생 배경

에이줵스 이전의 웹 애플리케이션이 어떻게 동작했느냐 하면은(사실 아직도 이 방법 계속 많이많이 쓰고 있음.)


클라이언트에서 어떤 요청을 서버한테 보내면  -> 서버는 그 요청에 맞게 데이터를 가공해서 새로운 웹 페이지를 생성해 버린다.

 -> 그리고 이렇게 생성한거를  다시 클라이언트에게 응답으로 주는 방식이었다.


자. 이 대목에서 중요한건, 서버쪽에서 지가 새로운 웹페이지를 생성해서 준다는 데에 있다.

요거요거 바로 JSP 방식이다. 이 얘긴 또 기니까 다음에 차차 하기로 하고.


고런데 이 때 약간의 문제가 뭐냐 하면, 대역폭 낭비가 생겨버린다는 거다.

뭔 말이냐 하면.

처음에 클라이언트가 주는 페이지나, 서버가 새로 생성해서 돌려준 그 페이지나 별 차이가 없는데, 서버가 굳---이 페이지를 새롭게 만들어 주는 바람에,

중복되는 HTML코드가 전송되면서 대역폭이 낭비된다 이거다.


그래서 생긴게 바로 아작스, 두둥-.




AJAX는 어떻게 돌아가냐면,

얘는, 클라이언트가 요청을 서버한테 보내면 -> 서버가 거기에 필요한 데이터만 가공해서 클라이언트한테 넘기고,

 -> 그걸 건네 받은 클라이언트는 지가 알아서 그걸 처리해서 화면에 보여주든 말든 한다.



이렇게 하면 클라이언트와 서버 간에 교환되는 데이터량이 훨씬 줄어들고, 웹 서버에서 처리해야하는 데이터 양도 확 줄어들기 때문에, 애플리케이션의 응답성이 좋아진다!


또한, 할 일이 안그래도 많은 서버한테 페이지 생성까지 시키니까 서버가 부담이 이만저만이 아니었는데,

이 방법을 써먹으면 데이터만 싹 보내고 끝이니까 서버의 부하를 줄일 수 있다.


클라이언트쪽에서도 좋은게, 무조건 서버쪽에서 보내준 화면을 띄울 수 밖에 없었던 기존 방법과는 달리!

서버에서 데이터만 보내주면 자기 입맛에 맞게 처리할 수 있다. 

좀 유식하게 말하면, 클라이언트에게 처리를 위임할 수 있다.






음. 일단 밤이 깊었으니까 오늘은 여까지.

+ Recent posts