image

Javascript Event Capturing

See the Pen Event Capturing by yceffort (@yceffort) on CodePen.

Event Capturing은 특정 요소에서 이벤트가 발생했을 때, 최상위 요소에서 부터 이벤트를 탐색하여 특정요소까지 찾아오는 이벤트 전파 방식을 의미한다. 위 예시에서 가장 내부의 element를 클릭했을 때, 최상위 요소 부터 해당 click이벤트를 전파시켜 이벤트가 실행되는 것을 볼 수 있다.

Javascript Event Bubbling

See the Pen Event Bubbling by yceffort (@yceffort) on CodePen.

반대로 Event Bubbling은 특정요소에서 이벤트가 발생했을 때, 해당 요소에서 부터 이벤트를 전파시키는 것을 의미한다. 위 예시에서 가장 내부의 three를 클릭했을때, three, two, one으로 이벤트가 전파되는 것을 볼 수 있다.

stopPropagation

stopPropagation는 이벤트의 전파 (Event Bubbling 및 Capturing)을 막는 것을 의미한다. 즉, 현재까지의 이벤트만 실행하고 이후의 이벤트를 막게된다.

preventDefault

preventDefault는 해당 DOM에서 내가 원하는 이벤트만 실행하고, 기본적인 (취소할 수 있는) 이벤트를 취소하는 것을 의미한다.

See the Pen preventDefault by yceffort (@yceffort) on CodePen.

해다아 예쩨에서는 preventDefault를 이용해서 a tag의 기본 이벤트인 href를 막는 것을 볼 수 있다.

Comment and share

javascript class

Class

클래스는 기본적으로 이렇게 생겼다.

1
2
3
4
5
6
7
8
class Member {
getName() {
return "이름";
}
}

let obj = new Member();
console.log(obj.getName());

특징

1. strict 모드에서 실행

딱히 'use strict';를 선언하지 않아도, 클래스의 코드는 기본적으로 strict모드에서 실행된다. 그렇게 되면, 당연히 strict모드의 여러가지 특징을 자동으로 따르게 된다.

2. 클래스 내 메서드 작성

1
2
3
4
5
6
7
8
9
class Member {
setName(name) {
this.name = name;
}

getName(name) {
this.name = name;
}
}

보이는 것처럼, function키워드와 :가 없이 메서드 이름만 사용한다. 그리고 메서드 사이에 ;가 불필요하다. 다만 function 을 선언하면 글로벌 오브젝트에 설정되는 것과 다르게, class는 그렇지 않다. 그리고 class의 object property는 for()문 등으로 열거할 수 없다.

3. 프로퍼티에 연결

1
2
3
4
5
class Member {
setName(name) {
this.name = name;
}
}

위 코드와

1
2
3
Member.prototype.setName = function(namn) {
this.name = name;
};

위 코드는 같다.

Constructor

constructor는 클래스 인스턴스를 생성하고, 생성한 인스턴스를 초기화하는 역할을 한다. new Member()를 실행하면, Member.prototype.constructor가 먼저 호출된다. 클래스에 이를 작성하지 않으면, prototype의 디폴트 constructor가 호출된다. 그리고 이 constructor가 없으면 인스턴스를 생성할 수 없다. 기존 es5문법에서는 자바스크립트 엔진이 디폴트 constructor를 호출해서 이를 활용할 수 없었지만, es6에서 부터는 개발자가 이를 정의할 수 있게 되었다.

1
2
3
4
5
6
7
8
9
10
11
12
class Member {
constructor(name) {
this.name = name;
}

getName() {
return this.name;
}
}

let newMember = new Member("라이오넬 멧시");
console.log(newMember.getName());

만약 constructor에서 이상한 값을 반환하면 어떻게 될까?

1
2
3
constructor() {
return 1;
}

constructor에서 numberstring을 반환하면, 이를 무시하고 생성한 인스턴스를 반환하게 된다.

그러나 object를 반환하면 어떻게 될까?

1
2
3
4
5
6
7
8
9
10
11
12
13
class Member {
constructor(name) {
return { name: "메켓트" };
}

getName() {
return this.name;
}
}

let newMember = new Member("라이오넬 멧시");
console.log(newMember.name);
console.log(newMember.getName);

1
2
메켓트
undefined

name이 메켓트인 object를 반환하면서, newMember 클래스에는 name밖에 남지 않게 되었다. 그리고 getName은 존재하지 않아서 undefined가 출력된다.

getter, setter

1
2
3
4
5
6
7
8
9
class Member {
set setName(name) {
this.name = name
}

get getName(name) {
return this.name
}
}

Comment and share

ES6에서부터 생긴 arrow function은 일반적으로 ()=>{}의 모양을 하고 있으며, 동작도 비슷해보인다. 하지만 이 두 선언방식은 두가지 분명한 차이를 가지고 있다.

1. this와 arguments의 차이

화살표 함수는 thisarguments를 바인딩하지 않는다. 그 대신, 일반적인 thisarguments와 동일한 범위를 가지고 있다.

1
2
3
4
5
6
7
8
9
10
11
function createObject() {
console.log('Inside `createObject`:', this.foo);
return {
foo: 42,
bar: function() {
console.log('Inside `bar`:', this.foo);
},
};
}

createObject.call({foo: 21}).bar();

위 함수의 결과는

1
2
Inside `createObject`: 21
Inside `bar`: 42

가 된다. 그러나 화살표 함수에서는 약간 다르다.

1
2
3
4
5
6
7
8
9
function createObject() {
console.log('Inside `createObject`:', this.foo);
return {
foo: 42,
bar: () => console.log('Inside `bar`:', this.foo),
};
}

createObject.call({foo: 21}).bar();

결과는

1
2
Inside `createObject`: 21
Inside `bar`: 21

즉, 화살표 함수안에서의 thiscreateObject안의 this를 따르게 된다. 이는 화살표 함수가 현재 환경의 this를 따르게 하고 싶을 때 유용하다는 뜻이다. 이 말인 즉슨, 화살표함수에서는 bindcall을 사용할 수 없다는 뜻이기도 하다.

2. 화살표 함수는 new로 호출할수 없다.

es2015에서는 callable한 것과 constructable한 것과의 차이를 두고 있다. 어떤 함수가 constructable하다면, 이는 new로 호출되어야 한다. ex) new User() 그리고 만약 함수가 callable하다면, 이 함수는 new없이도 호출이 되어야 한다 .ex) 일반적인 함수 호출

일반적인 함수의 경우 callable하며 constructable하다. 그러나 화살표 함수는 오로지 callable할 뿐이다. 반대로 class의 경우에는 오로지 constructable할 뿐이다.

정리

서로 바꿔서 쓸 수 있는 경우

  • this, arguments를 쓰지 않는 경우
  • bind(this)를 사용하는 경우

서로 바꿔쓸 수 없는 경우

  • constructable 함수
  • prototype에 추가된 함수나 메소드
  • arguments를 함수의 인자로 사용하는 경우

Comment and share

Intersection Observer

Intersection Observer는 엘리먼트가 viewport에 노출되고 있는지 여부를 확인해주는 API다. 간단히 말해 브라우저의 어떤 요소가 화면에 노출되고 있는지 안되고 있는지를 확인해주는 라이브러리라고 생각하면 될 것 같다. 이 라이브러리가 없이 엘리먼트가 노출중인지 확인하려면 어떻게 해야할까? 이전까지 주로 사용되던 API는 getBoundingClientRect다. 이 메서드는 해당 엘리먼트의 크기와 viewport에서의 상대적인 위치를 알려준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function isInViewport(element) {
// viewport의 height, width
const viewportHeight = document.documentElement.clientHeight;
const viewportWidth = document.documentElement.clientWidth;
// 엘리먼트의 rect
const rect = element.getBoundingClientRect();

if (!rect.width || !rect.height) {
return false;
}

var top = rect.top >= 0 && rect.top < viewportHeight;
var bottom = rect.bottom >= 0 && rect.bottom < viewportHeight;
var left = rect.left >= 0 && rect.left < viewportWidth;
var right = rect.right >= 0 && rect.right < viewportWidth;

return (top || bottom) && (left || right);
}

물론 이 함수로도 충분히 확인이 가능하다. 그러나 문제는 이 함수를 시도 때도 없이 불러야 할것이다. 문서가 처음로딩 되었을때, 스크롤 이벤트가 발생했을때, 브라우저 크키가 변경되었을때, (모바일의 경우) 화면이 회전 됐을 때 등 고려해야할 사황이 너무나도 많다. 그리고 결정적으로 getBoudingClientRect()는 호출이 될 때 마다 레이아웃을 다시 그리기 때문에 성능에 매우 부담이 간다. 참고

사용법

1
2
3
4
5
6
7
var options = {
root: document.querySelector("#scrollArea"),
rootMargin: "0px",
threshold: 1.0
};

var observer = new IntersectionObserver(callback, options);

root: 엘리먼트 노출을 감시할 viewport 영역이다. null이 기본값이고, null이라면 브라우저 전체를 감시하게 된다.

rootMargin: css의 margin property와 사용법이 같다. root에 margin 값을 주는 것이다.

threshold: number, 또는 array of number가 가능하다. 여기서 숫자는 viewport의 n%가 되었을 때 이벤트를 호출할 것인지 결정하는 것이다. array라면 해당 숫자만큼 노출될때 마다 이벤트가 발생하게 된다. 기본값은 0 으로, 단 1픽셀이라도 노출될 경우 이벤트가 발생된다.

callback: 타겟이 노출될때 실행되는 콜백함수다. 여기서는 두개의 값을 반환한다.

entries: IntersectionObserverEntry의 array이며, 각각 얼마나 노출되었는지 값이 나온다.

observer: callback을 호출한 IntersectionObserver

1
2
var target = document.querySelector("#listItem");
observer.observe(target);

See the Pen basic intersect example by yceffort (@yceffort) on CodePen.

콘솔창을 보면, 50% 이상 나온 엘리먼트가 찍히는 것을 볼 수 있다.

활용해보기

무한스크롤

See the Pen Infinite Scroll by Intersection Observer by yceffort (@yceffort) on CodePen.

문서의 맨 마지막에 height가 10px인 #watch_end_of_document를 넣어서, 이 엘리먼트가 브라우저에 노출되게 되면 스크롤의 마지막에 온것으로 간주하고 그 때마다 새로운 아이템들을 로딩하도록 명령을 내렸다.

이미지 레이지 로딩

See the Pen YoVvya by yceffort (@yceffort) on CodePen.

콘솔 - 네트워크 창으로 가보면 이미지가 동적으로 로딩되는 것을 알 수 있다. 각각의 엘리먼트들에 observer를 걸어두고, viewport에 걸칠때 마다 backgroundImage를 줘서 이미지가 로딩되도록 하였다.

Comment and share

사용한 오픈소스

React

자세한 설명은 생략 한다

Nextjs

NextJs 리액트에서 서버사이드 렌더링을 할 수 있도록 해주는 프레임워크다. angular나 react 등은 SPA라서 불편한 점이 더러 있는데, React에서 NextJS를 활용하면 react를 ssr(server side rendering)이 되도록 바꿔줄 수 있다. 그리고 자동으로 code splitting이 되고, 파일 시스템을 기준으로 라우팅이 되며, .. 뭐 이런저런 장점이 있다.

koa

express를 만든 개발자들이 따로 떨어져 나와서 만든 web framework가 바로 koa다. express와 비교했을 때는 koa가 비교적 가볍고, node.js v7의 async/await 를 자유자재로 쓸 수 있다는 데 있다. 그리고 es6를 도입해서 generator도 사용할 수 있다. IBM이 express를 인수해버린 관계로, 많은 개발자들이? koa로 넘어가는 추세라고 하는데, 아직은 잘 모르겠다.

Styled Component

Styled Component

시작

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
"name": "hello-world",
"version": "0.0.1",
"description": "hello-world",
"main": "main.js",
"scripts": {
"build": "tsc --outDir dist server/index.ts && next build",
"start": "NODE_ENV=production node dist",
"dev": "concurrently 'tsc -w --outDir dist server/index.ts' 'npm run watch-server -- --delay 2'",
"watch-server": "nodemon --exec 'node dist' --watch dist -e '*'"
},
"author": "",
"license": "UNLICENSED",
"dependencies": {
"@zeit/next-typescript": "^1.1.1",
"@zeit/next-css": "^1.0.1",
"@zeit/next-stylus": "^1.0.1",
"formik": "^1.5.7",
"isomorphic-fetch": "^2.2.1",
"koa": "^2.7.0",
"koa-body": "^4.1.0",
"koa-bodyparser": "^4.2.1",
"koa-morgan": "^1.0.1",
"koa-mount": "^4.0.0",
"koa-proxies": "^0.8.1",
"koa-router": "^7.4.0",
"next": "^8.1.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"styled-components": "^3.4.10"
},
"devDependencies": {
"@types/isomorphic-fetch": "0.0.35",
"@types/koa": "^2.0.48",
"@types/koa-bodyparser": "^4.3.0",
"@types/koa-morgan": "^1.0.4",
"@types/koa-mount": "^3.0.1",
"@types/koa-router": "^7.0.40",
"@types/next": "^8.0.5",
"@types/node": "^12.0.4",
"@types/react": "^16.8.22",
"babel-eslint": "^10.0.1",
"babel-plugin-styled-components": "^1.10.0",
"concurrently": "^4.1.0",
"nodemon": "^1.19.1",
"npm": "^6.9.0",
"typescript": "^3.5.1"
}
}

./typings/koa-proxies/index.d.ts

애석하게도 koa-proxies의 typing이 존재하지 않는다. ./typings/koa-proxies에 아래와 같이 추가하자.

1
2
3
4
5
6
declare module "koa-proxies" {
import { Middleware } from "koa";
namespace koaProxies {}
function koaProxies(name: string, options?: any): Middleware;
export = koaProxies;
}

타입스크립트로 nextjs를 사용하기 위하여 @zeit/next-typescript를 사용하였다.

./next.config.js

별도의 설정은 넣지 않았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const withCSS = require("@zeit/next-css");
const withStylus = require("@zeit/next-stylus");
const withTypescript = require("@zeit/next-typescript");

module.exports = withTypescript(
withStylus(
withCSS({
webpack: config => ({
...config,
plugins: [...(config.plugins || [])],
node: {
fs: "empty"
}
})
})
)
);

./.babelrc

1
2
3
{
"presets": ["next/babel", "@zeit/next-typescript/babel"]
}

./pages/index.tsx

nextjs의 유일한 제약은 pages 폴더다. pages에 렌더링 할 페이지를 만들어 둬야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
import * as React from "react";
import styled from "styled-components";

const MainHeading = styled.div`
font-size: 50px;
color: red;
`;

export default class IndexPage extends React.PureComponent {
render() {
return <MainHeading>hello?</MainHeading>;
}
}

./server/index.ts

가장 중요한 서버 부분이다. koa를 사용한 이유는 */api/*로 요청이 오는 호출에 대해서는 외부에 있을지도 모르는 api서버를 활용하기 위함이다. 이를 별도로 처리 하지 않는다면 CORS이슈가 있을수 있기 때문이다. 그래서 koa를 통해서 nextjs를 호출하는 방식으로 바꾸었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import * as next from "next";
import * as Koa from "koa";
import * as morgan from "koa-morgan";
import * as Router from "koa-router";
import * as proxy from "koa-proxies";
import * as bodyparser from "koa-bodyparser";
import * as mount from "koa-mount";

const isDev = process.env.NODE_ENV !== "production";

function renderNext(nextApp: next.Server, route: string) {
return (ctx: Koa.Context) => {
ctx.res.statusCode = 200;
ctx.respond = false;

nextApp.render(ctx.req, ctx.res, route, {
...((ctx.request && ctx.request.body) || {}),
...ctx.params,
...ctx.query
});
};
}

async function main() {
const nextApp = next({ isDev });
const app = new Koa();
const router = new Router();

await nextApp.prepare();
const handle = nextApp.getRequestHandler();

router.get("/", renderNext(nextApp, "/index"));

app
.use(morgan("combined"))
.use(bodyparser())
.use(
proxy("/api", {
target: "https://jayg-api-request.test.com",
rewrite: (path: string) => path.replace(/^\/api/, ""),
changeOrigin: true
})
)
.use(
mount("/health", (ctx: Koa.Context) => {
handle(ctx.req, ctx.res);
ctx.status = 200;
})
)
.use(router.routes())
.use(
mount("/", (ctx: Koa.Context) => {
handle(ctx.req, ctx.res);
ctx.respond = false;
})
)
.listen(3000);
}

main();

Comment and share

고민지점

  • Global 로 관리하는 Colorset Red, Blue, Green, Black이 있다.
  • 이 색들은 각각 지정된 칼라코드가 있다
  • 그러나 때로는 그 컬러코드에 맞게 안쓰는 경우도 있다
  • 그러나 때로는 저 네개를 다 안쓰고 1~3개만 쓰는 경우가 있다.

Union types

Union Type

어떤 라이브러리에서 받는 파라미터의 값을 number와 string으로 제한한다고 하자. 그렇다면 코드는 아래와 같을 것이다.

1
2
3
4
5
6
7
8
9
10
function numberOrString(parameter: any) {
if (typeof parameter === "number") {
return console.log("this is number")
}
if (typeof parameter === "string") {
return console.log("this is string")
}

throw new Error(`Expecting number of string, but got ${typeof parameter}`)
}

물론 이런식으로 처리할수도 있다. 그러나 문제는 컴파일 상에서만 괜찮다는 것이다. any는 typescript에서 어떤 값이든 들어갈 수 있으므로, 이 에러는 런타임상에서만 발생하게 된다.

일반적인 객체지향 코드에서는, 두 타입으로 하나 hierarchy 를 만들어서 처리할 수도 있지만, 약간 그건 과한 느낌이 있기도 하다. 이 코드를 이렇게 처리할 수도 있다.

1
2
3
function numberOrString(parameter: string|number) {
// do something...
}

이런 방식을 Union Type 이라고 한다. Union Type는 하나의 값에 여러가지 타입을 표현할 수 있게 해준다. 사용할 값을 |로 구별해서 넣어주면 된다.

처음 문제로 돌아와서, Global한 컬러로 지정하려는 값이 네개 있다고 헀다. 이제 이것은 이렇게 처리하면 된다.

1
export type GlobalColors = 'Red' | 'Blue' | 'Green' | 'Black'

자 그럼 이것이 어떻게 동작하는지 보자.

1
2
3
function getColor(parameter: GlobalColors) {
console.log(parameter)
}

getColor()GlobalColors가 아닌 다른 값을 넣으면

ts1

vscode에서 (물론 plugin덕분이지만) 네개의 값만 강제하는 것을 볼 수 있다. 만약 다른 값을 넣는다면 컴파일상에서 에러가 난다.

ts2

한 가지 신기 (당연) 한점은, 이 코드는 자바스크립트로 컴파일 되지 않는다는 것이다. 이점은 enum이랑 다른데, 암튼 지간에 저건 자바스크립트에서 처리할 수 없는 일이다. 이러한 타입체크는 나중에 컴파일 하게 된다면, *.d.ts에서 처리해준다는 것이다.

enum

enum은 열거형이다. 그렇다.

1
2
3
4
5
6
enum GlobalColorSet {
Red,
Blue,
Green,
Black
}

를 컴파일하면

1
2
3
4
5
6
7
var GlobalColorSet;
(function (GlobalColorSet) {
GlobalColorSet[GlobalColorSet["Red"] = 0] = "Red";
GlobalColorSet[GlobalColorSet["Blue"] = 1] = "Blue";
GlobalColorSet[GlobalColorSet["Green"] = 2] = "Green";
GlobalColorSet[GlobalColorSet["Black"] = 3] = "Black";
})(GlobalColorSet || (GlobalColorSet = {}));

로 나온다. 복잡한데, 결과적으로는 아래와 같다.

1
var GlobalColorSet = {0: "Red", 1: "Blue", 2: "Green", 3: "Black", Red: 0, Blue: 1, Green: 2, Black: 3}

일반적인 map과 다르게 key로 값을 얻을 수 있고, 값으로도 key를 얻을 수 있다.

그러나 enum은 const로 선언될 경우 컴파일 결과가 조금다르다.

1
2
3
4
5
6
const enum ConstGlobalColorSet {
Red,
Blue,
Green,
Black
}

다른게 아니고, 사실 아무것도 컴파일 되지 않는다. 이는 읽기 전용으로 생성된 객체이기 때문에 (const) 수정할 객체 자체가 생성되지 않는 것이다. 또한 앞서 보았던 값으로 키를 얻는 행위 또한 불가능해진다.

아무튼, 글로벌하게 쓸 색상을 enum으로 선언했다.

1
2
3
4
5
6
const enum ConstGlobalColorSet {
Red = '11, 11, 11',
Blue = '22, 22, 22',
Green = '33, 33, 33',
Black = '44, 44, 44',
}

Record

Record<K, V>로 쓰인다. 여기서 K는 key이고, V는 Value다. keyof Record<K, T>k로, Record<K, T>[K]T다. (느낌이 그렇다는 것) 밑에 예시를 보자.

이거는

1
2
3
4
//이거는
type ColorProperties = Record<GlobalColors, string>
//이거와 같다
type ColorProperties = { red: string, blue: string, green: string, black: string }

(말보다 코드가 쉽다) 위에 경우와 마찬가지로, js로 컴파일 됐을 때는 위 두 코드는 아무런 js로 변환되지 않는다.

1
2
colorProp1['purple'] = 1;
colorProp1['orange']

위의 코드는 당연히 타입스크립트 컴파일에서 에러가 난다.

Partial

Partial은 key를 옵셔널하게 해준다.

1
2
3
4
// 이거는
let PartialColorProperties = Partial<ColorProperties>
// 이거와 같다.
let PartialColorProperties = { red?: string, blue?: string, green?: string, black?: string }

정리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 글로벌 색으로 4가지를 선언한다.
export type GlobalColors = 'Red' | 'Blue' | 'Green' | 'Black'

// 기본값으로 색상을 선언한다.
const enum ConstGlobalColorSet {
Red = '11, 11, 11',
Blue = '22, 22, 22',
Green = '33, 33, 33',
Black = '44, 44, 44',
}

// 기본 색상값외에 다른 색상을 사용하고 싶다면
const CUSTOM_COLORS: Partial<Record<GlobalColors, string>> = {
gray: '55, 55, 55',
}

Comment and share

Promise

1
new Promise(executor)

executorresolvereject 인수를 전달할 실행함수를 의미한다. 실행함수는 resolvereject를 받아 즉시 실행된다. 실행함수는 보통 비동기 작업을 시작한 후, 모든 작업을 끝내면 resolve를 호출해서 Promise를 이행하고, 오류가 발생한 경우 reject를 호출해 거부된다.

Promise는 다음 중 하나의 상태를 가진다.

  • 대기(pending): 이행되거나 거부되지 않는 초기 상태
  • 이행(fullfiled): 연산이 성공적으로 완료됨
  • 거부(rejected): 연산이 실패

promise

Promise.all(iterable)

iterable내에 모든 프로미스를 이행하는데, 대신 어떤 프로미스가 거부를 하게 되면 즉시 거부하는 프로미스를 반환한다. 모든 프로미스가 이행되는 경우, 프로미스가 결정한 값을 순서대로 배열로 반환한다.

Promise.race(iterable)

iterable내에 가장 빠르게 이행/거부 한 값을 반환한다.

Promise.reject()

주어진 이유로 거부하는 Promise 객체를 반환한다

Promise.resolve()

주어진 값으로 이행하는 Promise를 반환한다. then이 있는 경우, 반환된 프로미스는 then을 따라가고 마지막 상태를 취한다.

1
2
3
4
5
6
7
8
9
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
});
}

Comment and share

구조 분해 할당

구조 분해 할당은 배열이나 객체의 속성을 말그대로 분해하여, 분해 한 값을 개별변수에 담을 수 있게 도와주는 표현식이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
let a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20

// rest 패턴을 이용하여 나머지를 모두 할당 받을 수 있다.
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]

// 객체 리터럴 식에서 구조 분해 할당을 하기 위해서는, 객체 리터럴 형식과 마찬가지로 { }로 표기하면 된다.

({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20

({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}

function f() {
return [1, 2];
}

var a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2

// 값을 아래 처럼 무시할 수도 있다.
function f() {
return [1, 2, 3];
}

var [a, , b] = f();
console.log(a); // 1
console.log(b); // 3

객체 구조 분해

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
var o = {p: 42, q: true};
var {p, q} = o;

console.log(p); // 42
console.log(q); // true

var o = {p: 42, q: true};
var {p: foo, q: bar} = o;

// p와 q는 무시된다.
console.log(foo); // 42
console.log(bar); // true

var metadata = {
title: "Scratchpad",
translations: [
{
locale: "de",
localization_tags: [ ],
last_edit: "2014-04-14T08:43:37",
url: "/de/docs/Tools/Scratchpad",
title: "JavaScript-Umgebung"
}
],
url: "/en-US/docs/Tools/Scratchpad"
};

var { title: englishTitle, translations: [{ title: localeTitle }] } = metadata;

console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"

var people = [
{
name: "Mike Smith",
family: {
mother: "Jane Smith",
father: "Harry Smith",
sister: "Samantha Smith"
},
age: 35
},
{
name: "Tom Jones",
family: {
mother: "Norah Jones",
father: "Richard Jones",
brother: "Howard Jones"
},
age: 25
}
];

for (var {name: n, family: { father: f } } of people) {
console.log("Name: " + n + ", Father: " + f);
}


function userId({id}) {
return id;
}

function whois({displayName: displayName, fullName: {firstName: name}}){
console.log(displayName + " is " + name);
}

// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"

var user = {
id: 42,
displayName: "jdoe",
fullName: {
firstName: "John",
lastName: "Doe"
}
};

console.log("userId: " + userId(user)); // "userId: 42"
whois(user); // "jdoe is John"

let key = "z";
let { [key]: foo } = { z: "bar" };

console.log(foo); // "bar"

1
2
{ innerHeight } // {innerHeight: 441}
{( innerHeight )} // 441

여기에서 //는 전역객체 window일 것이다.

객체 리터럴 표기법과 JSON의 차이

  • JSON은 "key": value 구문만 허용한다. key값은 큰 따옴표로 묶여 있어야한다. 그리고 값은 단축(명)일 수는 없다.
  • JSON에서 값은 문자열, 숫자, 배열, true, false, null또는 다른 JSON객체만 가능하다.
  • 함수는 JSON값에서 할당될 수 없다.
  • Date 객체는 JSON.parse()를 거치고 나면 문자열이 된다.

Comment and share

함수형 컴포넌트

1
2
3
4
5
6
7
import React from 'react';

function Hello(props) {
return (
<div>hello {props.name}</div>
)
}

함수형 컴포넌트는 컴포넌트에서 라이프사이클, state 등의 기능을 제거한 상태이므로 메모리 사용량이 다른 컴포넌트에 비해 적다. 따라서 성능을 최적화 하기 위해서는 위와 같이 함수형 컴포넌트를 많이 쓰는 것이 좋다.

Comment and share

React Component Life Cycle

라이프 사이클은 총 10가지다. Will접두사는 어떤 작업을 작동하기전에 실행하는 메소드가, Did는 어떤 작업을 한 후에 실해오디는 메서드다. 이 메서드들은 컴포넌트 클래스에서 덮어써서 선언하여 사용할 수 있다.

라이프사이클은 총 3가지 카테고리로 나눌 수 있는데, mount, unmount, update다.

Mount

DOM이 생성되고, 웹 브라우저 상에 나타나는 것을 mount라고 한다. 이 때 호출되는 메서드는 다음과 같다.

  1. 컴포넌트 만들기
  2. constructor: 컴포넌트를 새로 만들 때 마다 호출되는 클래스 생성자 메서드
  3. getDerivedStateFormProps: props에 있는 값을 state와 동기화 시키는 메서드다.
  4. render: UI를 렌더링하느 메서드
  5. componentDidMount: 컴포넌트가 웹 브라우저 상에 나타난 후 호ㅓ출하는 메서드

update

컴포넌트를 업데이트 하는 경우는 아래 4가지다.

  1. props가 바뀔때
  2. state가 바뀔때
  3. 부모컴포넌트가 리렌더링 될때
  4. this.forceUpdate를 통하여 강제로 렌더링을 트리거할 때

호출되는 메서드들은 아래와 같다.

  1. props변경 / 부모 컴포넌트가 리렌더링
  2. getDerivedStateFromProps: props에 있는 값을 state와 동기화 시키는 메서드다.
  3. state가 변경
  4. shouldComponentUpdate: 컴포넌트가 리렌더링 해야하는지 결정하는 메서드다. 여기에서 false가 리턴되면 아래 메서드들을 더이상 호출 하지 않는다.
  5. forceUpdate 호출
  6. render
  7. getSnapshotBeforeUpdate: Component 변화를 DOM에 반영하기 전에 호출하는 메서드
  8. 웹브라우저의 dom이 변화
  9. componentDidUpdate: 컴포넌트의 업데이트 작업이 끝난 후 호출하는 메서드

Unmount

컴포넌트를 DOM에서 제거하는 것을 말한다.

  1. 언마운트
  2. componentWillUnmount: 컴포넌트가 웹 브라우저 상에서 사라지기 전에 호출되는 메서드다.

render() { ... }

컴포넌트의 모양새를 정의한다. 라이프사이클 메서드중 유일하게 필수 메서드이기도 하다. 여기에서 this.props this.state에 접근할 수 있으며, 리액트 요소를 반환한다. 다만 이 안에서는 절대 state를 변경해서는 안되며, 웹브라우저에 접근해서도 안된다.

constructor

컴포넌트의 생성자 메서드로 컴포넌트를 만들 때 최초로 실행된다. 여기에서 초기 state를 정할 수 있다.

getDerivedStateFromProps

v16.3 이후에 등장한 메서드로, props로 받아온 값을 state에 동기화 시키는 용도로 사용되며, 컴포넌트를 마운트하거나 props를 변경할 때 호출된다.

componentDidMount

컴포넌트를 만들고, 첫 렌더링을 마친 후 실행된다. 이 안에서 다른 자바스크립트 라이브러리나 프레임워크 함수를 호출하거나, 이벤트 등록, setTimeOut, setInterval 네트워크요청과 같은 비동기 요청을 실행하면 된다.

shouldComponentUpdate

propsstate를 변경했을 때, 리렌더링을 시작할지 여부를 결정하는 메서드다. 여기에서는 반드시 true, false를 반환해야 한다. false를 반환하면 이 후의 과정이 모두 멈춘다.

이 안에서 this.props this.states로 접근할 수 있고, 다음 값을 this.nextProps this.nextState로 다음 값을 접근할 수 있다. 성능을 최적화 하거나, 알고리즘을 작성하여 리렌더링을 방지하기 위해서 사용하기도 한다.

getSnapshotBerforeUpdate

v16.3 이후에 등장한 메서드로, render를 호출한 후 DOM에 변화를 반영하기 바로 직전에 호출하는 메서드다. 여기에서 반환하는 값을 componentDidUpdate에서 세번째 파라미터인 snapshot으로 값을 전달 받을 수 있다. 주로 스크롤바의 위치와 같이 업데이트하기 직전에 값을 참고할 일이 있을 때 활용한다.

1
2
3
4
5
6
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevState.array !== this.state.array) {
const {scrollTop, scrollHeight} = this.list
return {scrollTop, scrollHeight};
}
}

componentDidUpdate

리렌더링을 완료한 후에 실행한다. 업데이트가 끝난 이후이므로, DOM관련 처리를 해도된다. 여기에서 prevProps prevState를 활용하여 이전의 값에 접근할 수 도 있다. getSnapshotBerforeUpdate에서 반환한 값이 있다면 여기서 snapshot값을 전달 받을 수 있다.

componentWillUnmount

컴포넌트를 DOM에서 제거할 때 실행한다. componentDidMount에서 등록한 이벤트, 타이머, DOM이 있다면 어기에서 제거해야 한다.

component-life-cycle

출처: https://code.likeagirl.io/understanding-react-component-life-cycle-49bf4b8674de

Comment and share

Author's picture

yceffort

yceffort


programmer


Korea