@computed


getter함수에만 사용이 가능한, @observable과 유사한 기능을 갖습니다.


* Javascript es6 의 getter를 모르시는 분들은 이곳을 참고해 주세요.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/get


하지만 observable과 가장 큰 차이점은 최적화의 문제입니다.


observable은 값이 변경될때마다 무조건 렌더링을 다시 하지만,


실제 사용할 값을 computed해놓고 사용한다면, observable값이 변경되더라도, computed 에 명시된 값에 부합하지 않다면, 재 렌더링 하지 않습니다.


사용법은 역시 함수로 사용하는 방법과 데코레이터를 사용하는 방법이 존재합니다.


이번포스팅에서 부터는 함수로 사용하는 방법에 대해서는 예제를 만들지 않겠습니다.

( 거의 사용 하지 않습니다. 특히 타입스크립트에서는.. )


데코레이터를 사용하는 예제를 작성해 보도록 하겠습니다.


이전에 사용하던 프로젝트의 AgeStore.tsx 파일에 computed를 추가하겠습니다.


import { observable, computed } from 'mobx';

export default class AgeStore {
@observable
private _age: number = 30;

constructor(age: number) {
this._age = age;
}

@computed
get age(): number {
return (this._age > 40) ? this._age : 0;
}
public getAge(): number {
return this._age;
}
public setAge(age: number): void {
this._age = age;
}

}


만약 this._age가 40이 넘는다면 렌더링을 하고 그렇지 않으면 0을 렌더링 할것입니다.


그리고 age 컴포넌트를 변경하겠습니다.

<h1>{ageState.age}</h1>


이전에는 getAge() 를 렌더링 했지만 이번에는 computed로 지정한 age를 렌더링 합니다.


실행해 보겠습니다.



실행하였을때 0이 나오는것을 볼수 있습니다.

왜냐하면 실행하였을때 this._age의 값은 30이니까요.


여기서 나이증가 버튼을 계속 누르다 보면, 


짠 렌더링이 실시됩니다.


즉 this._age값은 계속 변화하였지만 computed로 인해 렌더링이 되지 않고 값만 변화하였다가

40이 넘어가는 순간부터 렌더링을 실시하게 된것입니다.


예제에서는 단순하게 하였지만, 좀더 정밀하게 구성한다면, 렌더링을 최적화 하는데 매우 큰 도움이 될것으로 예상됩니다.


* 만약 setter 함수를 사용하여 값을 변화시키고, 같은 이름의 getter 함수를 computed 해놓았다면,

setter 함수 호출후에는 반드시 getter 함수가 호출됩니다. 

하지만 이때 반드시 getter 함수 정의후 setter 함수를 정의 하여야 합니다.


https://mobx.js.org/refguide/computed-decorator.html


@action - 추후에 내용을 보강할 계획입니다.


action은 state를 수정하는 함수입니다. ( observable이 있는 프로퍼티를 수정 )

옵셔널한 기능이므로 사용해도, 안해도 됩니다.

하지만 mobx의 use-strict 모드를 사용한다면, state를 변경하는 함수에는 반드시 사용해야 합니다.


예시로 살펴보겠습니다.


우선 src/index.tsx에 use-strict 모드를 적용하겠습니다.


import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './containers';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

import { useStrict } from 'mobx';

useStrict(true);

ReactDOM.render(
<App />,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();


그리고 실행하면 컴파일에서는 오류가 전혀 발생하지 않습니다.


그러나 실행화면에서 버튼을 눌러 state를 변경하려고 하는 순간



에러를 발생시킵니다.


대충 봐도 action을 쓰라고 써있습니다.


그렇다면 action 을 사용해야겠습니다.


AgeStore.tsx 파일에 적용하겠습니다.


import { observable, computed, action } from 'mobx';


export default class AgeStore {
@observable
private _age: number = 30;

constructor(age: number) {
this._age = age;
}

@computed
get age(): number {
return (this._age > 40) ? this._age : 0;
}
public getAge(): number {
return this._age;
}
@action
public setAge(age: number): void {
this._age = age;
}

}


간단하게 observable된 프로퍼티 값을 바꾸는 함수에 @action을 추가하면 됩니다.


https://mobx.js.org/refguide/action.html


@inject와 Provider


이전에 Redux에서 쓰던 Provider와 똑같이 사용합니다.

다른점은, 이전에 redux에서는 provider로 넘긴 props를 사용하기 위해서는

컴포넌트를 컨네이터로 감싼뒤 mapStateToProps, mapDispatchToProps를 작성하여 사용가능하게 만들었습니다. 하지만 mobx에서는 @inject 선언으로 사용이 가능합니다. 심플하게..


예제를 만들어 보겠습니다.


src/index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './containers';
import registerServiceWorker from './registerServiceWorker';
import './index.css';

import { useStrict } from 'mobx';
import { Provider } from 'mobx-react';
import { AgeStore } from './stores';

useStrict(true);

const ageState = new AgeStore(30);

ReactDOM.render(
<Provider store = {ageState}>
<App />
</Provider>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();


AgeStore를 Provider를 통해 props로 내려주겠습니다.


그리고 Age 컴포넌트에서 사용해 보겠습니다.


src/components/Age.tsx

import * as React from 'react';

import { observer, inject } from 'mobx-react';

import { AgeStore } from '../stores';

@inject('store')
@observer
class Age extends React.Component<{ store?: AgeStore; }, {}> {
constructor(props: {}) {
super(props);
}

componentWillReact() {
console.log('componentWillReact');
}
componentWillUpdate() {
console.log('componentWillUpdate');
}
componentDidUpdate() {
console.log('componentDidUpdate');
}

render() {
console.log('render');
const store = this.props.store as AgeStore;
return (
<div className="Age">
<h1>{store.age}</h1>
<button onClick={() => store.addAge()}>나이증가</button>
</div>
);
}
}

export default Age;


정말 간단하게 @inject('store') 명령으로 가져오는것이 가능합니다.


하지만 여기서 중요한것은 typescript이므로 props의 타입을 지정해줘야 하는데 

stroe를 타입으로 지정해주면 됩니다.

또한 사용하기 위해서는

const store = this.props.store as AgeStore;

처럼 명시해주어야 사용이 가능합니다.


* 저는 이번 예제를 좀더 깔끔하게 사용 하기 위해 addAge함수를 store에 넣었습니다.

import { observable, computed, action } from 'mobx';

export default class AgeStore {
@observable
private _age: number = 30;

constructor(age: number) {
this._age = age;
}

@computed
get age(): number {
return (this._age > 40) ? this._age : 0;
}
public getAge(): number {
return this._age;
}
@action
public setAge(age: number): void {
this._age = age;
}
@action
public addAge(): void {
this._age = this._age + 1;
}

}



autorun


autorun은 observers 자체를 가지지 않는 리액션 함수를 만들고자하는 경우에 사용할 수 있습니다. 

일반적으로 로깅, 지속성 또는 UI 업데이트 코드와 같이 반응적인 코드에서 명령형 코드로 연결해야하는 경우입니다. autorun과 computed는 비슷하게 보이지만, 완전히 다르게 동작합니다.

autorun을 사용하면, 종속성 중 하나가 변경 될 때마다 무조건 다시 트리거 되는 반면에,

computed는 상황에 따라 트리거 됩니다.


따라서 자동으로 실행되어야 하지만, 새로운 값을 결과로 내놓지 않는 함수가 있다면 autorun을 사용하고, 그렇지 않다면 computed를 사용하는 것이 좋습니다.

* 그래서 autorun 은 거의 대부분 로그를 출력하는데 이용됩니다.


예제를 통해 살펴보겠습니다.


src/stores/AgeStore.tsx

import { observable, computed, action, autorun } from 'mobx';

export default class AgeStore {
@observable
private _age: number = 30;

constructor(age: number) {
this._age = age;
}

@computed
get age(): number {
return (this._age > 40) ? this._age : 0;
}
public getAge(): number {
return this._age;
}
@action
public setAge(age: number): void {
this._age = age;
}
@action
public addAge(): void {
this._age = this._age + 1;
}

public ageLog(): Function {
return autorun(() => {
if (this.getAge() > 50) {
window.alert('나이가 너무 많아');
}
console.log('Age', this.getAge());
});
}

}


age 로그라는 함수를 만들어 autorun 함수를 return 하게 하였습니다.

여기서는 getAge하였는데 그 값이 50 이상일 경우에는 경고창을 띄우도록 하였고,

그렇지 않으면 콘솔을 띄우도록 하였습니다.


src/components/Age.tsx

import * as React from 'react';

import { observer, inject } from 'mobx-react';

import { AgeStore } from '../stores';

@inject('store')
@observer
class Age extends React.Component<{ store?: AgeStore; }, {}> {

private store = this.props.store as AgeStore;

constructor(props: {}) {
super(props);
this.store.ageLog();
}

componentWillReact() {
console.log('componentWillReact');
}
componentWillUpdate() {
console.log('componentWillUpdate');
}
componentDidUpdate() {
console.log('componentDidUpdate');
}

render() {
console.log('render');
return (
<div className="Age">
<h1>{this.store.age}</h1>
<button onClick={() => this.store.addAge()}>나이증가</button>
</div>
);
}
}

export default Age;


그리고 Age컴포넌트에서는 생성자 함수에서 ageLog를 실행하도록 하였습니다.


프로젝트를 실행해보겠습니다.



실행후 버튼을 누르면, age가 50이 되기 전까지는 증가된 값이 그냥 콘솔만 표시됩니다.

( 물론 이전에 computed때문에 렌더링은 되지 않습니다.)


하지만 50이 넘어가는 순간 경고창을 띄웁니다.


이런식으로 autorun을 사용하게 되는데, 경고창이 아닌 에러를 발생시켜 다른 작업을 할수도 있고,


활용법은 많을것으로 예상됩니다.


https://mobx.js.org/refguide/autorun.html



이번 포스팅까지가 mobx의 포스팅였습니다.

아마 다음 포스팅까지는 조금 시간이 걸릴듯 합니다.


남은 포스팅은 todoApp 예제와, react 서버사이드 렌더링 구현,  테스팅, nextjs 가 남아있는데,


그 전에 선수지식이 필요할듯 하여 좀더 공부를 한 뒤 포스팅을 할 예정입니다.


빠르게 공부하여 다시 react-typescript의 포스팅을 이어가도록 하겠습니다.


감사합니다.


이번 포스팅의 소스코드입니다.

https://github.com/JaroInside/tistory-react-typescript-study/tree/14.mobx-part3



참고 슬라이드 - http://slides.com/woongjae/react-with-typescript-4#/
참고 동영상 - https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoHx0OjUduzaFSZ4_cUqXLm0

'React > React&typeScript' 카테고리의 다른 글

14.mobx-part2  (0) 2017.08.01
14.mobx-part1  (5) 2017.07.28
13. redux - part5  (0) 2017.07.26
13. redux - part4  (0) 2017.07.26
13. redux - part3  (0) 2017.07.25
블로그 이미지

Jaro

대한민국 , 인천 , 남자 , 기혼 , 개발자 jaro0116@gmail.com , https://github.com/JaroInside https://www.linkedin.com/in/seong-eon-park-16a97b113/

,

이전포스팅에서 간단하게 mobx를 사용해보았습니다.


이번 포스팅에서는 mobx에 있는 여러 기능들에 대해 알아보는 포스팅을 해보도록 하겠습니다.


observable



observable은 관찰하고자 하는 값에 사용합니다.


obsevable을 사용하는 방법에는 2가지가 있습니다.


1. 일반 function 으로 사용

2. 데코레이터로 사용


일반 function 으로 사용하는것은 매우 간단하게 사용이 가능합니다.


이전 포스팅에서 사용한 프로젝트를 재 사용하여 예제를 만들어 보겠습니다.


stores 폴더에 AgeStore2.tsx 파일을 만들고 함수형태의 observable을 사용해 보겠습니다.


import { observable } from 'mobx';

const Age2 = observable({
age: 35
});

export default Age2;


그냥 관찰하고자 하는 값에 observable 함수를 사용하기만 하면 됩니다.



stores/index.tsx

import AgeStore from './AgeStore';
import Age2 from './AgeStore2';

export { AgeStore, Age2 };


그리고 Age 컴포넌트에서 사용해보겠습니다.


import * as React from 'react';

import { AgeStore, Age2 } from '../stores';

import { observer } from 'mobx-react';

const ageState = new AgeStore(30);

@observer
class Age extends React.Component<{}, {}> {
constructor(props: {}) {
super(props);
this.addAge = this.addAge.bind(this);
this.addNumber = this.addNumber.bind(this);
}
render() {
return (
<div className="Age">
<h1>{ageState.getAge()}</h1>
<button onClick={() => this.addAge()}>나이증가</button>
<h1>{Age2.age}</h1>
<button onClick={() => this.addNumber()}>나이증가</button>
</div>
);
}
addAge() {
const age = ageState.getAge();
ageState.setAge(age + 1);
console.log(ageState.getAge());
}
addNumber() {
Age2.age = Age2.age + 1;
}
}

export default Age;


이전 코드에서 Age2를 추가하고, 이벤트 핸들러를 추가한다음, 적용시켰습니다.



이상없이 작동하는것을 확인할수 있습니다.


데코레이터로 사용하는 방법은 이미 사용해보았기 때문에 사용 방법은 아실거라 생각합니다.

다만 기억하실점은, 데코레이터로 사용을 하신다면, 반드시 클래스의 프로퍼티에만 사용이 가능함을 기억하셔야 합니다.

다른곳에서는 사용이 불가합니다.


만약 여러 프로퍼티를 사용한다면 여러개의 observable을 사용하면 됩니다.



observer


observer는 observable된 값 변화에 따라 반응하는 컴포넌트에 사용합니다.


역시 observable과 같이 사용하는 방법에는 2가지 방식이 존재합니다.


1. 데코레이터 없이 사용하는 방법

2. 데코레이터로 사용하는 방법


데코레이터 없이 사용하는 방법은 observer(<컴포넌트>); 의 형식으로 사용하면 됩니다.

만약 state를 전혀 사용하지 않는 컴포넌트일 경우, 일반적으로는 stateless component, 즉 SFC를 사용하였는데, mobx를 사용할 경우에는 SFC 대신 observer를 사용하면 됩니다.


즉 이전 예제의 Age 컴포넌트를

const Age = observer(() => {
function addAge(): void {
const age = ageState.getAge();
ageState.setAge(age + 1);
console.log(ageState.getAge());
}
function addNumber(): void {
Age2.age = Age2.age + 1;
}
return (
<div className="Age">
<h1>{ageState.getAge()}</h1>
<button onClick={() => addAge()}>나이증가</button>
<h1>{Age2.age}</h1>
<button onClick={() => addNumber()}>나이증가</button>
</div>
);
});


이렇게 사용이 가능합니다.


데코레이터를 사용할 경우에는 컴포넌트 클래스의 상단에 사용하면 되는데,

만약 observer 데코레이터를 사용할 경우에는 주의할 점이 있습니다.


바로 Lifecycle이 mobx의 라이프 사이클로 바뀐다는 점입니다.


이전에 Lifecycle 포스팅에서 ( http://jaroinside.tistory.com/18 ) react의 update lifecycle은 

  • componentWillReceiveProps(nextProps)
  • shouldComponentUpdate(nextProps, nextState)
  • componentWillUpdate(nextProps, nextState)
  • render()
  • componentDidUpdate(prevProps, prevState)

  • 이렇게 5개의 라이프 사이클이 존재하고 순서대로 작동한다고 하였습니다.


    하지만 observer 데코레이터를 사용하면 라이프사이클은,


    - componentWillReact

    - componentWillUpdate

    - render

    - componentDidUpdate


    의 순서로 실행되게 됩니다. ( 주의 ! )


    - 확인을 위한 코드 -

    import * as React from 'react';

    import { AgeStore, Age2 } from '../stores';

    import { observer } from 'mobx-react';

    const ageState = new AgeStore(30);

    @observer
    class Age extends React.Component<{}, {}> {
    constructor(props: {}) {
    super(props);
    this.addAge = this.addAge.bind(this);
    this.addNumber = this.addNumber.bind(this);
    }

    componentWillReact() {
    console.log('componentWillReact');
    }
    componentWillUpdate() {
    console.log('componentWillUpdate');
    }
    componentDidUpdate() {
    console.log('componentDidUpdate');
    }

    render() {
    console.log('render');
    return (
    <div className="Age">
    <h1>{ageState.getAge()}</h1>
    <button onClick={() => this.addAge()}>나이증가</button>
    <h1>{Age2.age}</h1>
    <button onClick={() => this.addNumber()}>나이증가</button>
    </div>
    );
    }
    addAge() {
    const age = ageState.getAge();
    ageState.setAge(age + 1);
    }
    addNumber() {
    Age2.age = Age2.age + 1;
    }
    }

    export default Age;


    - 첫번째 render 는 첫 render 입니다 -




    이번 포스팅은 조금 짧게 하였습니다.

    다음 포스팅에서는 이전 예제에서 다뤄보지 않은 computed,  action, inject, provider등을 포스팅 하겠습니다.

    감사합니다.


    소스코드 주소입니다.


    https://github.com/JaroInside/tistory-react-typescript-study/tree/14.mobx-part2



    참고 슬라이드 - http://slides.com/woongjae/react-with-typescript-4#/

    참고 동영상 - https://www.youtube.com/playlist?list=PLV6pYUAZ-ZoHx0OjUduzaFSZ4_cUqXLm0


    'React > React&typeScript' 카테고리의 다른 글

    14.mobx-part3  (0) 2017.08.03
    14.mobx-part1  (5) 2017.07.28
    13. redux - part5  (0) 2017.07.26
    13. redux - part4  (0) 2017.07.26
    13. redux - part3  (0) 2017.07.25
    블로그 이미지

    Jaro

    대한민국 , 인천 , 남자 , 기혼 , 개발자 jaro0116@gmail.com , https://github.com/JaroInside https://www.linkedin.com/in/seong-eon-park-16a97b113/

    ,

    이벤트


    이벤트는 키보드로 키를 입력하거나, 마우스 클릭과 같이 다른 것에 영향을 미치는 것을 의미합니다. 이벤트는 사용자가 발생시킬수도 있고, application 스스로 발생시킬 수도 있습니다.


    이벤트 관련 용어


    이벤트 연결, 이벤트 이름, 이벤트 속성, 이벤트 핸들러, 이벤트 모델


    예시를 사용하여 살펴보겠습니다.


    간단하게 index.html 파일을 생성하고, 코드를 작성해주세요.


    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    #EventDom {
    width: 500px;
    height: 500px;
    background-color: red;
    }
    </style>
    <title>Event Study</title>
    </head>
    <body>
    <div id="EventDom"></div>
    <script>
    window.onload = whenLoad;

    function whenLoad() {
    // 변수 선언
    let dom = document.getElementById('EventDom');

    // 이벤트 연결
    function whenClick() { alert('CLICK'); };
    dom.onclick = whenClick;
    }
    </script>
    </body>
    </html>


    화면에 가로 500px, 세로 500px 의 dom 을 생성하고, EventDom 이라는 id를 부여하였습니다.


    한번 index.html을 브라우져로 열어 클릭하여 확인해보시기 바랍니다.


    이벤트 연결

    여기서 이벤트 연결은 총 2가지 입니다.


    첫번째는 winodw의 onload 속성에 이벤트를 연결하였고, 두번째는 dom의 onclick 속성에 이벤트를 연결하였습니다.


    이벤트 이름

    이벤트 연결이 2가지 이므로 이름 또한 2가지가 존재합니다.

    window객체의 경우에는 load, dom 객체의 경우에는 click이 이벤트 이름이 됩니다.


    이벤트 속성

    이벤트 연결에서 언급하였듯, 이벤트를 연결하는 객체의 속성을 이벤트 속성이라고 합니다.

    window에서는 onload, dom에서는 onclick 이 이벤트 속성입니다.


    이벤트 핸들러

    window 객체의 onload에서의 이벤트 핸들러는 whenLoad 입니다.

    dom 객체의 onclick에서의 이벤트 핸들러는 whenClick입니다.


    즉 이벤트 핸들러는 이벤트가 일어났을때 이벤트에 따라 작업을 하게 하는 객체 혹은 함수를 의미합니다.


    이벤트 모델

    문서 객체에 이벤트를 연결하는 방법을 이벤트 모델이라고 합니다.

    이벤트 모델은 DOM level 단계에 따라 두가지로 분류할수 있으며, 각기 두가지로 또 나뉩니다.

    즉 총 4가지의 방법으로 이벤트를 연결할수 있습니다.


    * DOM Level 0

    - 고전 이벤트 모델

    - 인라인 이벤트 모델

    * DOM Level 2

    - 마이크로소프트 인터넷 익스플로러 이벤트 모델

    - 표준 이벤트 모델


    위 예제에서 사용한 방법은 자바스크립트에서 문서 객체의 이벤트 속성을 사용해 이벤트를 연결하는 방법으로, DOM Level0 의 고전 이벤트 모델입니다.


    인라인 이벤트 모델은 HTML 페이지의 가장 기본적인 이벤트 연결방법으로, 위 예제의 dom click 이벤트를 인라인 이벤트 방식으로 바꿔보겠습니다.


    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    #EventDom {
    width: 500px;
    height: 500px;
    background-color: red;
    }
    </style>
    <script>
    function whenClick(e) { alert('CLICK'); };
    </script>
    <title>Event Study</title>
    </head>
    <body>
    <div id="EventDom" onclick="whenClick(event)"></div>
    </body>
    </html>


    * script를 head에 포함시킨 이유는, 아래에 넣을경우, EventDom이 생성될때 onclick에 whenClick이 아직 명시되지 않은 상태이므로 원하는대로 이벤트가 바인딩 되지 않습니다.

    따라서 head에 넣어주어야 정상적으로 작동하게 됩니다.


    예제와 같은 방법으로, 태그의 이벤트 속성에 인라인 형식으로 이벤트를 연결하는 방식을 인라인 이벤트 모델이라고 합니다.


    한번 index.html을 브라우져로 열어 클릭하여 확인해보시기 바랍니다.


    DOM Level0 의 두 이벤트 모델은 한번에 하나의 이벤트 핸들러만 가질수 있는 단점이 있습니다.

    이러한 단점을 보완하기 위해 만들어진 이벤트 모델이 DOM Level 2 이벤트 모델로써, 

    마이크로 소프트 익스플로러 모델과 표준 이벤트 모델이 있습니다.


    하지만 마이크로 소프트 익스플로러 모델의 경우 ie 10까지만 지원하고 11 부터는 표준 이벤트 모델과 동일한 함수를 사용합니다. 따라서 마이크로 소프트 익스플로러 모델은 낮은 버젼의 웹 개발을 하는 경우가 아니라면 굳이 신경쓰지 않아도 될거 같습니다.


    표준 이벤트 모델은 웹 표준을 만드는 W3C에서 공식적으로 지정한 이벤트 모델입니다.

    역시 여러 이벤트 핸들러를 추가할수 있으며, 이벤트 캡쳐링을 지원합니다.

    ( 표준 이벤트 모델은 익스플로러 9부터 지원합니다.)


    표준 이벤트 모델에서의 이벤트 연결, 제거 시에는 아래와 같은 메서드를 사용합니다.


    addEventListener(eventName, handler, useCapture)

    removeEventListener(eventName, handler)


    예제로 만들어 보겠습니다.


    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    #EventDom {
    width: 500px;
    height: 500px;
    background-color: red;
    }
    </style>
    <title>Event Study</title>
    </head>
    <body>
    <div id="EventDom"></div>
    <script>
    window.addEventListener('load', function() {
    let dom = document.getElementById('EventDom');

    dom.addEventListener('click' , function() {
    console.log('클릭');
    });
    dom.addEventListener('click' , function() {
    dom.style.backgroundColor="blue";
    });
    dom.addEventListener('click' , function() {
    alert('클릭');
    });
    });
    </script>
    </body>
    </html>


    addEventListener를 이용하여 EventDom 클릭시 3개의 이벤트가 발생하도록 하였습니다.


    만약 이전의 고전 이벤트 모델처럼 이벤트 핸들러를 작성하고


    dom.onclick = whenClick;

    과 같은 방식으로 이벤트를 여러번 연결한다면,


    가장 마지막에 연결한 이벤트만 연결되겠지만, 표준 이벤트 모델을 이용한다면 여러 이벤트를 연결할수 있습니다.


    이벤트 전달


    이벤트 전달이란, Dom이 여러개일때, 어떠한 이벤트가 먼저 발생해 어떤순서로 발생하는지 정하는것을 의미합니다.


    이벤트 전달방식에는 2가지 방식이 존재합니다.


    1. 이벤트 캡쳐링

    이벤트 캡쳐링은 이벤트의 흐름이 상위노드에서 하위 노드로 흘러가는것을 의미합니다.


    즉 어느 한 Dom에 이벤트가 발생하면 그 Dom이 속한 최상위 노드에서부터 이벤트가 실행되어 실제 이벤트가 발생한 Dom까지 순차적으로 이벤트가 발생하는 구조입니다.


    사용법은 addEventListener(eventName, handler, useCapture) 에서 useCapture를 true로 해주시면 됩니다.


    예제를 통해 알아보겠습니다.


    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    #GrandParent {
    width: 500px;
    height: 500px;
    background-color: red;
    }

    #Parent {
    width: 400px;
    height: 400px;
    background-color: orange;
    }

    #Me {
    width: 300px;
    height: 300px;
    background-color: yellow;
    }

    #Child {
    width: 200px;
    height: 200px;
    background-color: green;
    }

    #GrandChild {
    width: 100px;
    height: 100px;
    background-color: blue;
    }
    </style>
    <title>Event Study</title>
    </head>
    <body>
    <div id="GrandParent">
    <div id="Parent">
    <div id="Me">
    <div id="Child">
    <div id="GrandChild"></div>
    </div>
    </div>
    </div>
    </div>
    <script>
    window.addEventListener('load', function(){
    var GrandParent, Parent, Me, Child, GrandChild;

    GrandParent = document.getElementById("GrandParent");
    Parent = document.getElementById("Parent");
    Me = document.getElementById("Me");
    Child = document.getElementById("Child");
    GrandChild = document.getElementById("GrandChild");

    GrandParent.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    }, true);

    Parent.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    }, true);

    Me.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    }, true);

    Child.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    }, true);

    GrandChild.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    }, true);
    });
    </script>
    </body>
    </html>


    이 코드는 5개의 사각형이 존재하며 최상위에는 GrandParent가 존재하고, 

    가장 아래에는 GrandChild가 존재합니다. 각각 click하였을때, 콘솔로 자신의 id를 표시하게 만들었습니다.


    만약 이 상태에서 Me, 즉 노란색 부분을 클릭하게 되면 이벤트는 어떤 순서로 발생이 되는지 해보겠습니다.



    노란색 부분, 즉 Me Dom을 클릭을 하였는데, 상위 노드의 이벤트부터 차례대로 실행되는것을 확인할수 있습니다.


    즉 


    document -> html -> body -> GrandParent -> Parent -> Me 의 순서로 이벤트가 발생하였지만

    document , html , body 에는 클릭 이벤트가 없으므로 GrandParent 부터 콘솔에 나타난것입니다.


    * 인터넷 익스플로러는 캡쳐링을 지원하지 않습니다.


    2. 이벤트 버블링

    이번에는 이벤트 버블링을 살펴보겠습니다.

    이벤트 버블링은  이벤트의 흐름이 하위 노드에서 상위 노드로 흘러가는것을 의미합니다.

    즉 어느 한 Dom에 이벤트가 발생하면 그 Dom에서부터 이벤트가 시작하여 Dom이 속한 최상위 노드까지 이벤트가 실행되는 구조입니다.


    예제입니다.


    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    #GrandParent {
    width: 500px;
    height: 500px;
    background-color: red;
    }

    #Parent {
    width: 400px;
    height: 400px;
    background-color: orange;
    }

    #Me {
    width: 300px;
    height: 300px;
    background-color: yellow;
    }

    #Child {
    width: 200px;
    height: 200px;
    background-color: green;
    }

    #GrandChild {
    width: 100px;
    height: 100px;
    background-color: blue;
    }
    </style>
    <title>Event Study</title>
    </head>
    <body>
    <div id="GrandParent">
    <div id="Parent">
    <div id="Me">
    <div id="Child">
    <div id="GrandChild"></div>
    </div>
    </div>
    </div>
    </div>
    <script>
    window.addEventListener('load', function(){
    var GrandParent, Parent, Me, Child, GrandChild;

    GrandParent = document.getElementById("GrandParent");
    Parent = document.getElementById("Parent");
    Me = document.getElementById("Me");
    Child = document.getElementById("Child");
    GrandChild = document.getElementById("GrandChild");

    GrandParent.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    });

    Parent.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    });

    Me.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    }, true);

    Child.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    });

    GrandChild.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    });
    });
    </script>
    </body>
    </html>


    addEventListener(eventName, handler, useCapture) 에서 useCapture를 쓰지 않으면 버블링이 적용됩니다. ( default가 false)


    위의 스크린샷은 실행후 파란색 사각형, GrandChild를 클릭했을때 일어난 이벤트입니다.


    아까와는 반대로 클릭한 곳에서 이벤트가 일어났지만, 그 후에 이벤트가 일어난 Dom이 속해있는 상위노드의 이벤트도 모두 일어났음을 확인할수 있습니다.


    그렇다면, 이벤트 캡쳐링, 버블링 모두 제대로 알지 않으면 원치않는 이벤트를 일으키게 된다는것을 느낄수 있습니다.


    이러한 상황을 막기 위해서는 이벤트의 흐름을 차단할 필요가 있습니다.


    이벤트 캡쳐링의 경우에는 useCapture를 false로 지정하면 막을수 있지만, 그렇게 되면 버블링이 작동하게 됩니다. 그렇다면 버블링을 막는 방법을 알면 될것 같습니다.


    이벤트 버블링을 막는 방법에는 2가지 방법이 있습니다.


    하나는 e의 cancelBubble 값을 true로 하는 방법과


    e의 stopPropagation함수를 호출하는 방법입니다.


    적용해 보겠습니다.


    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    #GrandParent {
    width: 500px;
    height: 500px;
    background-color: red;
    }

    #Parent {
    width: 400px;
    height: 400px;
    background-color: orange;
    }

    #Me {
    width: 300px;
    height: 300px;
    background-color: yellow;
    }

    #Child {
    width: 200px;
    height: 200px;
    background-color: green;
    }

    #GrandChild {
    width: 100px;
    height: 100px;
    background-color: blue;
    }
    </style>
    <title>Event Study</title>
    </head>
    <body>
    <div id="GrandParent">
    <div id="Parent">
    <div id="Me">
    <div id="Child">
    <div id="GrandChild"></div>
    </div>
    </div>
    </div>
    </div>
    <script>
    window.addEventListener('load', function(){
    var GrandParent, Parent, Me, Child, GrandChild;

    GrandParent = document.getElementById("GrandParent");
    Parent = document.getElementById("Parent");
    Me = document.getElementById("Me");
    Child = document.getElementById("Child");
    GrandChild = document.getElementById("GrandChild");

    GrandParent.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.stopPropagation();
    });

    Parent.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.stopPropagation();
    });

    Me.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.cancelBubble = true;
    });

    Child.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.stopPropagation();
    });

    GrandChild.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.cancelBubble = true;
    });
    });
    </script>
    </body>
    </html>


    실행하여 확인하면, 더이상 버블링이 일어나지 않는것을 확인할수 있습니다.




    * 추가


    preventDefault()


    이 메소드는 이벤트 전달을 차단함과 동시에 그 dom이 가지고 있는 기본적인 기능또한 차단하는 기능을 가지고 있습니다.


    위의 코드에 a 링크를 추가하고, test.html로 링크를 시켜놓겠습니다.


    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    #GrandParent {
    width: 500px;
    height: 500px;
    background-color: red;
    }

    #Parent {
    width: 400px;
    height: 400px;
    background-color: orange;
    }

    #Me {
    width: 300px;
    height: 300px;
    background-color: yellow;
    }

    #Child {
    width: 200px;
    height: 200px;
    background-color: green;
    }

    #GrandChild {
    width: 100px;
    height: 100px;
    background-color: blue;
    }

    #test {
    width: 50px;
    height: 50px;
    color: white;
    }
    </style>
    <title>Event Study</title>
    </head>
    <body>
    <div id="GrandParent">
    <div id="Parent">
    <div id="Me">
    <div id="Child">
    <div id="GrandChild">
    <a id="Test" href="test.html">테스트링크</a>
    </div>
    </div>
    </div>
    </div>
    </div>
    <script>
    window.addEventListener('load', function(){
    var GrandParent, Parent, Me, Child, GrandChild, Test;

    GrandParent = document.getElementById("GrandParent");
    Parent = document.getElementById("Parent");
    Me = document.getElementById("Me");
    Child = document.getElementById("Child");
    GrandChild = document.getElementById("GrandChild");
    Test = document.getElementById("Test");

    GrandParent.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.stopPropagation();
    });

    Parent.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.stopPropagation();
    });

    Me.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.cancelBubble = true;
    });

    Child.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.stopPropagation();
    });

    GrandChild.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.cancelBubble = true;
    });
    });
    </script>
    </body>
    </html>


    이 코드를 실행시키고 a링크된 텍스트를 클릭하면 당연히 text.html로 페이지는 변하게 됩니다.

    하지만 이러한 이벤트를 막고싶을때 preventDefault 메서드를 사용하면 됩니다.


    Test.addEventListener('click', function(e){
    console.log("My Name is " + this.id);
    e.cancelBubble = true;
    e.preventDefault();
    });


    이렇게 한다면, 버블링도 막고, a링크 원래의 기능도 막을수 있습니다.


    'Javascript' 카테고리의 다른 글

    Hoisting - 호이스팅  (0) 2017.08.03
    블로그 이미지

    Jaro

    대한민국 , 인천 , 남자 , 기혼 , 개발자 jaro0116@gmail.com , https://github.com/JaroInside https://www.linkedin.com/in/seong-eon-park-16a97b113/

    ,