🚧작성중 🚧

원문-reactjs-interview-questions

Table of Contents

No. Questions
Core React
1 리액트란 무엇인가?
2 리액트의 주요 기능은 무엇인가?
3 JSX란 무엇인가?
4 element와 component의 차이점은 무엇인가?
5 리액트에서 컴포넌트를 어떻게 만드는가?
6 클래스 / 함수 컴포넌트는 각각 언제 사용해야 하는가?
7 순수한 컴포넌트는 무엇인가?
8 state는 무엇인가?
9 props는 무엇인가?
10 state와 props의 차이는 무엇인가?
11 왜 state를 바로 업데이트 하면 안되는가?
12 setState() 콜백의 용도는 무엇인가?
13 HTML과 React의 이벤트 핸들링 차이는 무엇인가?
14 JSX 콜백에 메소드나 이벤트 핸들러를 바인딩하는 방법은 무엇인가?
15 이벤트 핸들러나 콜백에 파라미터를 전달하는 방법은?
16 리액트의 synthetic event는 무엇인가?
17 인라인 조건식은 무엇인가?
18 key props는 무엇이며, 배열의 요소에서 사용함으로써 얻을 수 있는 이점은 무엇인가?
19 ref의 목적은 무엇인가?
20 ref는 어떻게 생성하는가?
21 forward refs란 무엇이인가?
22 callback ref와 findDOMNode중 어떤것이 더 선호되는가?
23 string ref가 왜 legacy가 되었는가?
24 Virtual DOM 은 무엇인가?
25 Virtual DOM은 어떻게 작동하는가?
26 Shadow DOM과 Virtual DOM의 차이는 무엇인가?
27 What is React Fiber?
28 React Fiber의 목적은 무엇인가?
29 controlled components는 무엇인가?
30 uncontrolled components는 무엇인가?
31 createElementcloneElement의 차이는 무엇인가?
32 React에서 lifting state up은 무엇인가?
33 Component Lifecycle의 각 phase에는 어떤 차이가 있는가?
34 Component Lifecycle에는 어떤 method가 있는가?
35 Higher-Order 컴포넌트는 무엇인가?
36 HOC 컴포넌트에서 props proxy를 어떻게 만드는가?
37 Context란 무엇인가?
38 자식 prop는 무엇인가?
39 React에서 주석을 어떻게 쓰는가?
40 props 변수가 있는 super 생성자의 목적은 무엇인가?
41 reconciliation은 무엇인가??
42 동적 key name으로 setState하는 방법은?
43 렌더가 될 때 마다 호출되는 function의 일반적인 실수는 무엇인가?
44 lazy함수가 named exports를 지원하는가?
45 리액트가 class 속성에 class 대신 className을 쓰는가?
46 fragments란 무엇인가?
47 fragment가 div 컨테이너보다 좋은 이유는?
48 react에서 portals란 무엇인가?
49 stateless 컴포넌트란?
50 stateful 컴포넌트란?
51 React props에서 유효성 검사를 하는 방법은?
52 React의 장점은?
53 React의 한계는?
54 React v16에서 error boundaries는?
55 React v15에서 error boundaries는?
56 정적 타입 체킹을 하는 최선의 방법은?
57 react-dom package의 쓰임새는?
58 react-dom의 render 메서드의 목적?
59 ReactDOMServer란?
60 React에서 InnerHtml를 쓰는 방법은?
61 React에서 스타일을 쓰는 방법은?
62 React에서 이벤트는 어떻게 다른가?
63 constructor에서 setState를 쓴다면?
64 index를 키로 쓸 경우 어떤 일이 벌어지는가?
65 componentWillMount() method안에서 setState()를 쓰는 것이 바람직한가?
66 initial state에서 props를 쓰면 어떻게 되는가?
67 어떻게 조건부로 컴포넌트를 렌더링하는가?
68 DOM 엘리먼트에서 스프레드 props를 쓸 때 주의해야 할 점은?
69 React에서 decorator를 쓰는 방법은?
70 컴포넌트를 메모이제이션 하는 법은?
71 서버사이드렌더링을 하는 방법은?
72 React에서 프로덕션 모드를 키는 방법은?
73 CRA는 무엇이고 이점은 무엇인가?
74 마운팅시 라이프사이클 메서드의 순서는?
75 React v16에서 deprecated된 라이프 사이클 메서드는?
76 getDerivedStateFromProps() 의 목적은?
77 getSnapshotBeforeUpdate()의 목적은?
78 Hooks api가 render props와 HOC를 대체하는가?
79 네이밍 컴포넌트를 위한 최상의 방법은?
80 컴포넌트 클래스에서 메소더의 순서를 정하는 방법은?

What is React

리액트는 오픈소스 프론트엔드 자바스크립트 라이브러리로, 특히 싱글 페이지 어플리케이션의 사용자 인터페이스 구축을 위해 사용된다. 웹가 모바일 앱의 뷰단을 다르기 위하여 사용되고 있다. 리액트는 페이스북에서 일아흔 Jordan Walke가 만들었다. 최초로 리액트 기반으로 만들어진 서비스는 2011년에 페이스북 뉴스 피드이며, 2012년에는 인스타그램도 리액트로 만들어 졌다.

👆

What are the major features of React?

리액트의 주요 기능은 무엇인가?

  • RealDOM을 조작하는데 많은 비용이 소모되어 대신 VirtualDOM을 활용하고 있다.
  • 서버사이드렌더링을 지원한다
  • 단방향 데이터흐름 또는 단방향 데이터 바인딩을 따른다
  • 뷰를 개발하는데 있어 재사용 가능한 컴포넌트 사용

👆

What is JSX?

JSX는 ECMA Script의 XML 신택스 확장 표기법이다. (Javascript XML의 약자다.) 기본적으로, React.createElement()함수에 문법 슈가를 제공하며,HTML 스타일의 템플릿 구문화함께 javascript를 표현할 수 있다.

아래 예제에서, return안에 있는 <h1> 구문이 자바스크립트 함수의 render function 으로 제공된다.

1
2
3
4
5
6
7
8
9
class App extends React.Component {
render() {
return (
<div>
<h1>{"Welcome to React world!"}</h1>
</div>
);
}
}

👆

What is the difference between Element and Component?

element는 DOM노드나 컴포넌트 단에서 화면에 보여주고 싶은 요소를 그리는 하나의 오브젝트를 의미한다. elementelement의 props에서 포함될 수 있다. 리액트에서 element를 만드는건 많은 비용이 들지 않는다. 한번 만들고 나면, 더이상 변경이 불가능하다.

리액트에서 element를 만드는 예시는 아래와 같다.

1
const element = React.createElement("div", { id: "login-btn" }, "Login");

위 함수는 아래와 같은 object를 리턴한다

1
2
3
4
5
6
7
{
type: 'div',
props: {
children: 'Login',
id: 'login-btn'
}
}

그리고 ReactDOM.render()이 아래와 같은 DOM을 만들어 줄 것이다.

1
<div id="login-btn">Login</div>

반면에 컴포넌트는 다양한 방식으로 선언가능하다. 컴포넌트는 render()와 함께 쓴다면 클래스가 될 수도 있다. 좀더 단순한 방법으로, 함수로도 선언이 될 수 있다. 두 방식 모두 props를 input으로 받으며, JSX를 리턴한다.

1
2
3
4
5
const Button = ({ onLogin }) => (
<div id={"login-btn"} onClick={onLogin}>
Login
</div>
);

JSX는 이를 React.createElement() 함수로 트랜스파일 시킬 것이다.

1
2
const Button = ({ onLogin }) => React.createElement( 'div', { id: 'login-btn',
onClick: onLogin }, 'Login' )

👆

How to create components in React?

두 가지 방법이 존재한다.

  1. 함수형 컴포넌트: 컴포넌트를 만드는 가장 심플한 방식이다. props를 첫번째 파라미터로 받는 받는 순수 자바스크립트 함수를 만들고, React Element를 반환하면 된다.

1
2
3
function Greeting({ message }) {
return <h1>{`Hello, ${message}`}</h1>;
}

  1. 클래스 컴포넌트: ES6의 클래스를 활용하여 컴포넌트를 정의할 수도 있다. 위 컴포넌트를 클래스 컴포넌트로 바꾼다면 이렇게 될 것이다.

1
2
3
4
5
class Greeting extends React.Component {
render() {
return <h1>{`Hello, ${this.props.message}`}</h1>;
}
}

👆

When to use a Class Component over a Function Component?

컴포넌트가 state나 라이프 사이클 메소드를 필요로 할 때 클래스 컴포넌트를, 그렇지 않으면 함수형 컴포넌트를 활용하면 된다.

근데 요즘은 useState을 사용하면 함수형 컴포넌트에서도 state사용이 가능하다

👆

What are Pure Components?

React.PureComponentReact.Component에서 shouldComponentUpdate가 없다는 것만 제외하면 동일하다. propsstate에 변화가 있을 경우, PureComponent는 두 변수에 대해서 얕은 비교를 한다. 반면 Component는 그런 비교를 하지 않는다. 따라서 ComponentshouldComponentUpdate가 호출 될 때마다 다시 render한다.

👆

What is state in React?

state란 컴포넌트가 살아있는 동안에 걸쳐 변화할 수도 있는 값을 가지고 있는 object다. 따라서 state를 가능한 간단하게, 그리고 state의 구성요소를 최소화하는 노력을 기울여야 한다. 다음은 User Component에 message state를 관리하는 예제다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class User extends React.Component {
constructor(props) {
super(props);

this.state = {
message: "Welcome to React world"
};
}

render() {
return (
<div>
<h1>{this.state.message}</h1>
</div>
);
}
}

stateprops와 비슷하지만, 컴포넌트가 완전히 소유권을 쥐고 있다는 것이 다르다.다른 어떤 컴포넌트도 한 컴포넌트가 소유하고 있는 state에 접근할 수 없다.

👆

What are props in React?

props는 컴포넌트의 input 값이다. HTML 태그 속성과 유사한 규칙을 사용하여 ReactComponent에 전달할 수 있는 단일 값 또는 객체 다. 이런 데이터 들은 부모 컴포넌트에서 자식 컴포넌트로 보낼 수 있다.

리액트에서 props를 쓰는 주요 목적은 컴포넌트에 아래와 같은 기능을 제공하기 위해서다.

  • 컴포넌트에 custom data를 넘기기 위해
  • state의 변화를 trigger 하기 위해
  • Component의 render메소드 안에서 this.props.*** 로 사용하기 위함

예를 들어, reactProp 을 만들어서 쓴다고 가정해 보자.

1
<Element reactProp={"1"} />

reactProp은 (뭐라고 정의했던 지 간에) React를 사용하여 생성된 component에서 접근이 가능하고, React native props에서 접근하여 사용할 수 있다.

1
props.reactProp;

👆

What is the difference between state and props?

propsstate는 모두 순수 자바스크립트 오브젝트다. 두 객체 모두 render의 output에 영향을 줄 수 있는 정보를 가지고 있지만, 컴포넌트의 기능적인 측면에서는 약간 다르다. props는 함수의 파라미터와 비슷한 방식으로 작동하는 반면, state는 컴포넌트 내에서 선언된 변수와 비슷하다.

👆

Why should we not update the state directly?

state를 아래와 같이 바로 업데이트 하면 렌더링이 일어나지 않는다.

1
this.state.message = "Hello world";

대신에 setState() 메서드를 사용하자.이는 state의 변경이 있을 때 component를 업데이트 해준다. state에 변화가 있을 경우, 컴포넌트는 리렌더링으로 응답한다.

1
2
//Correct
this.setState({ message: "Hello World" });

주의: state를 직접 할당할 수 있는 곳은 constructor 혹은 자바스크립트 클래스의 필드를 선언하는 syntax 뿐이다.

👆

What is the purpose of callback function as an argument of setState()?

콜백함수는 setState가 끝나고 컴포넌트가 렌더링 된 이후에 실행된다.setState는 비동기로 이루어지기 때문에 callback에서는 어떤 액션이든 취할 수 있다.

주의: 콜백함수를 사용하는 것보다 라이프사이클 메서드를 사용하는게 더 좋다.

1
2
3
setState({ name: "John" }, () =>
console.log("The name has updated and component re-rendered")
);

What is the difference between HTML and React event handling?

  1. HTML에서는 이벤트명은 소문자로 작성되어야 한다.

1
<button onclick="activateLasers()"></button>

React는 camelCase를 사용한다.

1
<button onClick="{activateLasers}"></button>

  1. HTML에서는, false를 리턴하면 이후 기본 액션을 막을 수 있다.

1
<a href="#" onclick='console.log("The link was clicked."); return false;' />

하지만 react에서는 preventDefault()를 명시적으로 사용해야 한다.

1
2
3
4
function handleClick(event) {
event.preventDefault();
console.log("The link was clicked.");
}

How to bind methods or event handlers in JSX callbacks?

  1. 생성자에서 바인딩하기: 자바스크립트 클래스에서는, 메소드들이 기본적으로 바인딩 되어 있지 않다. 이는 클래스 메서드로 정의된 리액트 이벤트 핸들러와 마찬가지다. 보통, 생성자에서 바인딩한다.

1
2
3
4
5
6
7
8
9
10
class Component extends React.Componenet {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
// ...
}
}

  1. 퍼블리기 클래스 필드 구문: 생성자에서 바인딩 되기를 원치 않는다면, 퍼블릭 클래스의 필드 구문을 이용하여 callback을 올바르게 바인딩 할 수 있다.

1
2
3
4
5
handleClick = () => {
console.log("this is:", this);
};

<button onClick={this.handleClick}> Click me </button>;

클래스 필드(class field) 클래스 내부의 캡슐화된 변수를 말한다. 데이터 멤버 또는 멤버 변수라고도 부른다. 클래스 필드는 인스턴스의 프로퍼티 또는 정적 프로퍼티가 될 수 있다. 쉽게 말해, 자바스크립트의 생성자 함수에서 this에 추가한 프로퍼티를 클래스 기반 객체지향 언어에서는 클래스 필드라고 부른다.

1
2
3
4
5
class Foo {
name = ""; // SyntaxError

constructor() {}
}

constructor 내부에서 선언한 클래스 필드는 클래스가 생성할 인스턴스를 가리키는 this에 바인딩한다. 이로써 클래스 필드는 클래스가 생성할 인스턴스의 프로퍼티가 되며, 클래스의 인스턴스를 통해 클래스 외부에서 언제나 참조할 수 있다. 즉, 언제나 public이다. ES6의 클래스는 다른 객체지향 언어처럼 private, public, protected 키워드와 같은 접근 제한자(access modifier)를 지원하지 않는다.

  1. 화살표함수: 콜백에 화살표 함수를 사용할 수도 있다.

1
<button onClick={event => this.handleClick(event)}>{"Click me"}</button>

주의: 콜백이 하위 컴포넌트에 prop으로 전달된다면, component가 리렌더링 될 수도 있다. 이러한 경우에는, 성능을 고려해서 1, 2번의 예제를 활용하는 것이 낫다.

👆

How to pass a parameter to an event handler or callback?

이벤트 핸들러와 파라미터 전달을ㅇ 화살표 함수로 감쌀 수 있다.

1
<button onClick={() => this.handleClick(id)} />

이는 .bind와 같다.

1
<button onClick="{this.handleClick.bind(this," id)} />

두 방식 이외에도, 아래와 같은 배열 함수 방식으로 정의해서 전달할 수도 있다.

1
2
3
4
<button onClick={this.handleClick(id)} />;
handleClick = id => () => {
console.log("Hello, your ticket number is", id);
};

👆

What are synthetic events in React?

synthetic event (합성함수) 는 브라우저의 네이티브 이벤트를 위한 크로스 브라우저 래퍼다. 이 api는 브라우저의 네이티브 이벤트와 동일하며, 마찬가지로 stopPropagation() preventDefault()도 포함하고 있지만, 모든 브라우저에서 동일하게 작동한다는 점이 다르다.

👆

What is inline conditional expressions?

조건부 렌더 표현을 위해 javascript의 if문이나 삼항연산자를 사용할 수 있다. 이외에도 중괄호로 묶어서 javascript의 논리식인 &&을 붙여서 jsx에서도 사용할 수 있다.

1
2
3
4
5
6
<h1>Hello!</h1>
; { messages.length > 0 && !isLogin ? (
<h2>You have {messages.length} unread messages.</h2>
) : (
<h2>You don't have unread messages.</h2>
); }

👆

What are "key" props and what is the benefit of using them in arrays of elements?

key는 특별한 string 속성으로, 배열을 사용할 때 이용해야 한다. key는 리액트에서 어떤 item이 변화하고, 추가되고, 삭제되었는지 구별하는데 도움을 준다. 대부분 key로 id를 사용한다.

1
2
3
const todoItems = todos.map(todo =>
<li key="{todo.id}">{todo.text}</li>
);

만약 이런 ID가 없다면, index를 사용할 수 있다.

1
2
3
4
5
const todoItems = todos.map((todo, index) =>
<li key="{index}">
{todo.text}
</li>
)

주의

  1. index를 key로 사용하는 방식은, 아이템의 순서가 바뀌는 경우가 발생할 수 있는 케이스에는 별로 추천할만하지 못하다. 이는 퍼포먼스에 악영향을 미치고, component state에 악영향을 미칠 수 있다.
  2. list를 별도 컴포넌트로 뽑아서 사용하는 경우, key를 리스트 컴포넌트가 아닌 li 태그에 사용해야 한다.
  3. 리스트 아이템에 key가 없으면 콘솔에 경고 메시지가 뜬다.

What is the use of refs?

ref는 element의 참조값을 반환한다. 대부분 이러한 경우는 피해야 하지만, DOM이나 component에 다이렉트로 접근해야할 때 유용하다.

👆

How to create refs?

  1. 최근에 추가된 방식으로, React.createRef() 메소들를 사용하면, React element는 ref를 통해서 접근할 수 있다. ref를 컴포넌트에서 접근하기 위해서는, 생성자 안에 ref를 instance property로 할당하면 된다.

1
2
3
4
5
6
7
8
9
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}

  1. React 버전과 상관없이 ref 콜백을 활용하는 방식이 있다. 예를 들어, SearchBar 컴포넌트의 인풋 요소들은 아래와 같은 방식으로 접근 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class SearchBar extends Component {
constructor(props) {
super(props);
this.txtSearch = null;
this.state = { term: "" };
this.setInputSearchRef = e => {
this.txtSearch = e;
};
}
onInputChange(event) {
this.setState({ term: this.txtSearch.value });
}
render() {
return (
<input
value={this.state.term}
onChange={this.onInputChange.bind(this)}
ref={this.setInputSearchRef}
/>
);
}
}

또한 컴포넌트의 함수 내에서 클로져를 ref를 사용할 수도 있다.

주의: 추천할만한 방법은 아니지만, 인라인 ref callback을 이용하는 방식도 있다.

👆

What are forward refs?

Ref forwarding은 일부 컴포넌트에서 ref를 받아서 자식 컴포넌트에게 전달하는 것을 의미한다.

1
2
3
4
5
6
7
8
9
const ButtonElement = React.forwardRef((props, ref) => (
<button ref={ref} className="CustomButton">
{props.children}
</button>
));

// Create ref to the DOM button:
const ref = React.createRef();
<ButtonElement ref={ref}>{"Forward Ref"}</ButtonElement>;

👆

Which is preferred option with in callback refs and findDOMNode()?

callback ref를 쓰는 것이 더 선호된다. 왜냐하면 findDOMNode()는 향후에 있을 리액트의 개선사항이 반영되지 않기 때문이다.

레거시에서 findDOMNode를 사용하는 방법이 있다.

1
2
3
4
5
6
7
8
9
class MyComponent extends Component {
componentDidMount() {
findDOMNode(this).scrollIntoView();
}

render() {
return <div />;
}
}

그래서 선호하는 방법은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyComponent extends Component {
constructor(props) {
super(props);
this.node = createRef();
}
componentDidMount() {
this.node.current.scrollIntoView();
}

render() {
return <div ref={this.node} />;
}
}

👆

Why are String Refs legacy?

예전에 React를 다뤄보았다면, 옛날 방식인 ref를 string으로 쓰는, ref={'textInput'} 와 같이 ref속성이 string이고, DOM Node인 refs.textInput로 접근하는 방법에 익숙할 것이다. 그러나 이러한 string ref는 하단에서 언급할 문제들 때문에, 레거시로 보는 것이 맞다. 그리고 string ref는 React v16에서 제거 되었다.

  1. String ref는 실행중인 component 요소를 추적하도록 강제한다. 그리고 React Module을 stateful하게 만들기 때문에, 이는 번들시 react module이 중복 되는 경우 이상한 오류를 발생시킨다.
  2. 라이브러리를 추가하여 String ref를 child component에 전달한다면, 사용자는 다른 ref를 추가할 수 없다. 그러나 callback ref를 사용하면 이런 문제를 해결할 수 있다.
  3. Flow와 같은 정적 분석에서는 동작하지 않는다. Flow는 string ref를 this.refs와 같은 형태로 표시하도록 만드는 트릭을 추적할 수 없다. callback ref는 string ref보다 flow에 더 잘맞다.
  4. 대부분이 render callback 패턴으로 동작하기를 기대하지만, 그렇게 동작하지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyComponent extends Component {
renderRow = index => {
// 동작하지 않는다. ref는 MyComponent가 아닌 DataTable에 연결될 것이다.
return <input ref={"input-" + index} />;

// 이거는 동작한다. callback ref가 짱이다.
return <input ref={input => (this["input-" + index] = input)} />;
};

render() {
return <DataTable data={this.props.data} renderRow={this.renderRow} />;
}
}

👆

What is Virtual DOM?

Virtual DOM은 메모리 내에서 표현되는 Real DOM 이다. UI는 메모리 상에서 표현되며, 그리고 real DOM과 동기화 된다. 이는 렌더 함수 호출과 화면에 elements 표시 하는 사이에 일어난다. 이 모든 과정을 reconciliation이라고 한다.

👆

How Virtual DOM works?

  1. 어디서든 데이터가 편하면, Virtual DOM내에서 전체 UI가 다시 렌덜이 된다. virtual-dom-1

  2. 그런 다음 이전 DOM과 새로운 DOM을 비교한다. virtual-dom-2

  3. 계산이 끝나면, Real DOM 중에서 실제로 업데이트가 있었던 부분 만 변경을 가한다. virtual-dom-3

👆

What is the difference between Shadow DOM and Virtual DOM?

Shadow DOM은 web component의 scope및 CSS scope 지정을 위해 설계된 web browser 기술이다. Virtual DOM은 브라우저 API 위에 자바스크립트에서 구현되는 개념이다.

👆

What is React Fiber?

Fiber는 React v16에서 새로운 reconciliation 엔진, 그리고 코어 알고리즘을 새로 작성한 것으로 볼 수 있다. React Fiber의 목적은 애니메이션, 레이아웃, 제스쳐, 작업일시정지 및 중단, 여려 유형의 업데이트 우선순위 조절, 동시성 등 여러가지 기본 사항에 대한 성능을 높이는 것이다.

👆

What is the main goal of React Fiber?

React Fiber 의 목표는 애니메이션, 레이아웃, 제스처등의 성능을 높이는 것이다. 렌더링 작업을 chunk별로 작업하고, 여러 프레임 별로 이를 펼치면서 작업하는 점진적 렌더링을 통해 이를 구현했다.

👆

What are controlled components?

입력요소를 제어하는 component를 controlled components라고 부른다. 모든 상태변경에 연관뢴 handler function이 존재한다.

예를 들어, 모든 이름을 대문자로 쓰기 위해서는, handleChange를 아래와 같이 쓰게 된다.

1
2
3
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()})
}

👆

What are uncontrolled components?

uncontrolled components란 내부적으로 자기 자신의 state를 가지고 있는 component다. 현재 필요한 값을 찾기 위해 ref를 사용하여 DOM query를 할 수 있다. 이는 전통적인 HTML 과 비슷하다.

UserProfile Component를 아래에서 보자면, name input이 ref를 통해서 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}

handleSubmit(event) {
alert("A name was submitted: " + this.input.current.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
{"Name:"}
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

대부분의 경우, 폼에서는 controlled component를 사용하기를 추천한다.

👆

What is the difference between createElement and cloneElement?

JSX는 React.createElement() 함수로 UI에 나타낼 React element를 생성한다. 반면 cloneElement는 element를 props로 보낼 때 사용한다.

👆

What is Lifting State Up in React?

여러 component 들이 동일한 변경 데이터를 공유해야하는 경우 가까운 부모 component 로 state를 올리는 것이 좋다. 즉, 두개의 자식 component가 부모에 있는 동일한 데이터를 공유할 때. 두개의 자식 component 들은 local state를 유지하는 대신, 부모로 state를 올려야 한다.

👆

What are the different phases of component lifecycle?

React lifecycle에는 세 개의 phase가 있다.

  1. mounting: 컴포넌트가 browser DOM에 마운트 될 준비가 된 상태다. 이 phase에는 constructor() getDerivedStateFromProps() render() componentDidMount()가 있다
  2. updating: 이 단계에서는, 컴포넌트가 두가지 방법으로 업데이트 된다. 새로운 props를 보내거나, setState() forceUpdate()를 통해서 state를 업데이트 하는 방법이 있다. 이 단계에서는, getDerivedStateFromProps() shouldComponentUpdate() render() getSnapshotBeforeUpdate() componentDidUpdate() 가 포함된다.
  3. unmounting: 이단계에서는, browser DOM이 더 이 더이상 필요 없어지거나 unmount된다. 여기에는 componentWillUnmount()가 포함된다.

DOM에서의 변경을 적용할 때, 내부에서 어떤 과정을 거치는지 알아볼 필요가 있다. 각 단계는 아래와 같다.

  1. Render 컴포넌트가 어떠한 사이드 이펙트 없이 렌더링 된다. 이는 Pure Component에 적용되며, 이 단계에서는 일시정지, 중단, 렌더 재시작등이 가능하다.

  2. Pre-commit: 컴포넌트가 실제 변화를 DOM에 반영하기 전에, 리액트가 DOM을 getSnapshotBeforeUpdate() 통해서 DOM 을 읽을 수도 있다.

  3. Commit: React는 DOM과 함꼐 작동하며, 각각의 라이프 사이클 마지막에 실행되는 것들이 포함된다. componentDidMount() componentDidUpdate() componentWillUnmount()

    16.3 이후

react-16.3-phases

16.3 이전

before-react-16.3

👆

What are the lifecycle methods of React?

React 16.3+

  • getDerivedStateFromProps: 모든 render()가 실행되기 바로 직전에 호출된다. props의 변화의 결과로 내부 state 변화를 가능하게 해주는 메서드로, 굉장히 드물게 사용된다.
  • componentDidMount: 첫렌더링이 다 끝나고, 모든 ajax 요청이 완료, DOM이나 state 변화, 그리고 이벤트 리스너가 모두 설정된 다음에 호출된다.
  • shouldComponentUpdate: 컴포넌트가 업데이트 될지 말지를 결정한다. default로 true를 리턴한다. 만약 state나 props 업데이트 이후에 컴포넌트가 업데이트 될 필요가 없다고 생각한다면, false를 리턴하면 된다. 컴포넌트가 새로운 props를 받은 후에, 리 렌더링을 방지해서 성능을 향상시키기에 가장 좋은 위치다.
  • getSnapshotBeforeUpdate: 렌더 결과물이 DOM에 커밋되기 직전에 호출된다. 여기서 리턴된 모든 값은 componentDidUpdate()로 넘겨진다. 스크롤 포지션 등, DOM에서 필요한 정보를 사용할 때 유용하다.
  • componentDidUpdate: prop/state의 변화d의 응답으로 DOM을 업데이트 할 때 필요하다. 이 메소드는 만약 shouldComponentUpdate()false를 리턴하면 호출되지 않는다.
  • componentWillUnmount: 네트워크 요청을 취소하거나, 컴포넌트와 관련된 이벤트 리스너를 삭제할 때 쓰인다.

before 16.3은 따로 번역하지 않겠습니다.

  • componentWillMount: Executed before rendering and is used for App level configuration in your root component.
  • componentDidMount: Executed after first rendering and here all AJAX requests, DOM or state updates, and set up event listeners should occur. componentWillReceiveProps: Executed when particular prop updates to trigger state transitions.
  • shouldComponentUpdate: Determines if the component will be updated or not. By default it returns true. If you are sure that the component doesn't need to render after state or props are updated, you can return false value. It is a great place to improve performance as it allows you to prevent a re-render if component receives new prop.
  • componentWillUpdate: Executed before re-rendering the component when there are props & state changes confirmed by shouldComponentUpdate() which returns true.
  • componentDidUpdate: Mostly it is used to update the DOM in response to prop or state changes.
  • componentWillUnmount: It will be used to cancel any outgoing network requests, or remove all event listeners associated with the component.

👆

What are Higher-Order Components?

Higher-order Component (이하 HOC)는 컴포넌트를 받아서 새로운 컴포넌트를 리턴하는 컴포넌트다. 기본적으로, 이러한 패턴은 리액트의 컴포넌트적인 특성에서 유래되었다.

이를 Pure Component라고 부르는데, 동적으로 제공되는 하위 component를 그대로 사용하지만, 입력받은 component를 수정/복사하지 않기 때문이다.

HOC는 아래와 같은 use case에서 사용할 수 있다.

  • 코드 재사용, 로직 추상화
  • render 하이재킹
  • state 추상화 또는 조작
  • props 조작

👆

How to create props proxy for HOC component?

props proxy pattern을 아래와 같이 사용한다면, 컴포넌트에 넘겨진 props를 추가/수정할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function HOC(WrappedComponent) {
return class Test extends Component {
render() {
const newProps = {
title: "New Header",
footer: false,
showFeatureX: false,
showFeatureY: true
};

return <WrappedComponent {...this.props} {...newProps} />;
}
};
}

👆

What is context?

Context는 props을 탑다운으로 주지 않고도, 어느 레벨에서든 데이터를 컴포넌트 트리에 넘기는 방법이다. 예를 들어 인증받은 사용자, 언어 설정, UI theme 등 어플리케이션 단위에서 다양한 컴포넌트가 사용해야 하는 데이터를 context를 통해서 줄 수 있다.

1
const { Provider, Consumer } = React.createContext(defaultValue);

👆

What is children prop?

Children은 prop (this.prop.children) 으로, 다른 컴포넌트에 컴포넌트를 넘길 수 있는 방법으로, 다른 prop를 사용하는 것과 동일하다. 컴포넌트 트리는 이 children을 여닫는 태그 사이에 두며, 이는 컴포넌트를 children prop으로 건내게 된다.

React API에서 이러한 형태로 다양한 prop을 제공하고 있다. React.Children.map React.Children.forEach React.Children.count React.Children.only React.Children.toArray 사용예제는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
const MyDiv = React.createClass({
render: function() {
return <div>{this.props.children}</div>;
}
});

ReactDOM.render(
<MyDiv>
<span>{"Hello"}</span>
<span>{"World"}</span>
</MyDiv>,
node
);

👆

How to write comments in React?

React/JSX의 주석은 자바스크립트의 다중 주석과 비슷하지만, { }에 쌓여있다는 것이 다르다.

한 줄

1
2
3
4
<div>
{/* Single-line comments(In vanilla JavaScript, the single-line comments are
represented by double slash(//)) */} {`Welcome ${user}, let's play React`}
</div>

여러 줄

1
2
3
4
<div>
{/* Multi-line comments for more than one line */} {`Welcome ${user}, let's
play React`}
</div>

👆

What is the purpose of using super constructor with props argument?

자식 클래스 생성자는 super()메소드가 호출되기 전까지 this 레퍼런스를 쓸 수 없다. 이와 동일한것이 es6의 서브 클래스에 구현되어 있다. super() 메소드에 props를 파라미터로 호출하는 주요 이유는 this.props를 자식 생성자에서 쓰기 위해서다.

props 넘기는 경우

1
2
3
4
5
6
7
class MyComponent extends React.Component {
constructor(props) {
super(props);

console.log(this.props); // prints { name: 'John', age: 42 }
}
}

props 안 넘기는 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyComponent extends React.Component {
constructor(props) {
super();

console.log(this.props); // prints undefined

// but props parameter is still available
console.log(props); // prints { name: 'John', age: 42 }
}

render() {
// no difference outside constructor
console.log(this.props); // prints { name: 'John', age: 42 }
}
}

👆

What is reconciliation?

컴포넌트의 props나 state에 변경이 있을때, React는 이전에 렌더링 된 element와 새롭게 렌더링된 것을 비교하여 실제 DOM이 업데이트 되어야 할지를 결정한다. 똑같지 않을때, React는 DOM을 업데이트 한다. 이 과정을 reconciliation이라고 한다.

👆

How to set state with a dynamic key name?

JSX코드 내에서 es6또는 바벨 트랜스파일러를 쓰고 있다면, computed property 명을 쓸 수 있다.

1
2
3
handleInputChange(event) {
this.setState({ [event.target.id]: event.target.value })
}

👆

What would be the common mistake of function being called every time the component renders?

함수를 파라미터로 넘기는 과정에서 함수가 호출되지 않는지 확인해야 한다.

👆

Is lazy function supports named exports?

아니다. 현재 React.lazy함수는 default export만 지원한다. named exports된 모듈을 import 하고 싶을 경우에는, 사이에 디폴트로 reexports 하는 모듈을 만들수 있다. 이는 트리쉐이킹을 도와주고, 사용하지 않는 컴포넌트를 pull하지 않을 수 있다. 밑에서 예를 살펴보자.

1
2
3
// MoreComponents.js
export const SomeComponent = /* ... */;
export const UnusedComponent = /* ... */;

이 컴포넌트 중간에 IntermediateComponent.js를 만들어서 다시 export 한다.

1
2
// IntermediateComponent.js
export { SomeComponent as default } from "./MoreComponents.js";

그리고 lazy 함수를 이용해서 아래와 같이 임포트 할 수 있다.

1
2
import React, { lazy } from "react";
const SomeComponent = lazy(() => import("./IntermediateComponent.js"));

👆

Why React uses className over class attribute?

class는 자바스크립트의 예약어 이고, JSX는 javascript를 확장해 만든 것이다. 따라서 class를 쓰면 충돌이 일어나기 자바스크립트 예약어와 충동리 발생하기 때문에 className을 사용한다. className prop에 string을 넘겨 주면 된다.

1
2
3
render() {
return <span className={'menu navigation-menu'}>{'Menu'}</span>
}

👆

What are fragments?

React에서는 하나의 컴포넌트가 여러개의 elements를 리턴하는 것이 일반적인 패턴이다. Fragments는 추가로 DOM 노드를 사용하지 않더라도 여러개의 노드들을 묶을 수 있게 해준다.

1
2
3
4
5
6
7
8
9
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
)
}

1
2
3
4
5
6
7
8
9
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
)
}

👆

Why fragments are better than container divs?

  1. Fragment는 실제로 추가적인 DOM을 만들지 않기 때문에 더 빠르고 메모리 사용량도 적다. 이는 매우 크고 깊은 트리를 만들 때 상당한 이점으로 작용한다.
  2. CSS Grid나 firefox같은 일부 특수한 CSS 메커니즘은 특별한 부모-자식 관계를 가지고 있는데, div를 중간에 추가하는 것은 원하는 레이아웃을 그리기 어렵게 한다.
  3. DOM Inspector를 사용할 때 덜 혼잡스럽다.

👆

What are portals in React?

portals 은 상위 Component 의 DOM 계층 구조 외부에 존재하는 DOM 노드로, 자식을 render 하는데 권장되는 방법이다.

1
ReactDOM.createPortal(child, container);

첫번째 인자는 React Child에서만 렌더링이 가능하며, 여기에는 element, string, fragment 가 포함된다. 두번째 인자는 DOM 엘리먼트다.

👆

What are stateless components?

컴포넌트의 동작이 state와 독립되어 있다면, 이는 stateless 컴포넌트다. 함수나 클래스를 이용해서 stateless 컴포넌트를 만들 수 있다. 하지만 컴포넌트의 라이프 사이클 훅이 필요하지 않다면, 함수형으로 가는 것이 좋다. 함수형 컴포넌트를 선택한다면 많은 이점을 가져갈 수 있다. 코드 사용 및 이해가 쉽고, 조금더 빠르며, 그리고 this 키워드의 충돌을 막을 수 있다.

👆

What are stateful components?

state의 사용에 종속적인 컴포넌트를 stateful component라고 한다. 이 컴포넌트는 항상 class 컴포넌트로 만들어 져야 하며, constructor를 통해서 초기화 되어야 한다.

1
2
3
4
5
6
7
8
9
10
class App extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

render() {
// ...
}
}

👆

How to apply validation on props in React?

React가 development로 실행한다면, 자동으로 컴포넌트에 있는 props의 타입을 올바르게 체크해 준다. 만약 타입이 올바르지 않다면, React는 콘솔에 경고 메시지를 띄운다. 성능 상의 이슈를 위해 production에서는 이 기능이 꺼져 있다. 필수적인 prop은 isRequired다. 사용할 수 있는 prop type의 종류는 아래와 같다.

  1. PropTypes.number
  2. PropTypes.string
  3. PropTypes.array
  4. PropTypes.object
  5. PropTypes.func
  6. PropTypes.node
  7. PropTypes.element
  8. PropTypes.bool
  9. PropTypes.symbol
  10. PropTypes.any

아래와 같이 쓸수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from "react";
import PropTypes from "prop-types";

class User extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired
};

render() {
return (
<>
<h1>{`Welcome, ${this.props.name}`}</h1>
<h2>{`Age, ${this.props.age}`}</h2>
</>
);
}
}

주의: 리액트 v15.5부터 PropType이 React.PropTypes에서 prop-types로 이동했다.

👆

What are the advantages of React?

  1. Virtual DOM으로 어플리케이션의 성능을 향상시킬 수 있음
  2. JSX를 통해 코들르 쉽게 읽고 쓸수 있음
  3. 클라이언트와 서버사이드 양쪽에서 렌더링 라능
  4. 뷰만 다루는 라이브러리이기 때문에, 다른 프레임워크 (Angular, Backbone) 등과 쉽게 연동 가능
  5. Jest와 같은 툴로 쉽게 유닛/인티그레이션 테스트 가능

👆

What are the limitations of React?

  1. 풀 프레임워크가 아니라, view만 다루고 있음.
  2. 뉴비 웹 개발자들에게 러닝 커브가 존재
  3. 전통적인 MVC 프레임워크와 인터그레이팅을 하기 위해서는 추가적인 설정이 필요
  4. inline 템플릿과 JSX로 인해 코드의 복잡성 증가
  5. 오버엔지니어링/보일러플레이팅을 야기하는 작은 단위의 컴포넌트가 너무 많이 존재

👆

What are error boundaries in React v16?

Error boundaries란 하위 component tree 에서 자바스크립트 에러 를 catch 하고, 기록하고, 에가 발생한 component tree가 아닌 대체 UI를 표현해 주는 component를 말한다.

새롭게 추가된 라이프사이클 메서드인 componentDidCatch(error, info)static getDerivedStateFromError()를 사용한다면, 클래스 컴포넌트는 error boundary가 될 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

componentDidCatch(error, info) {
// 에러 리포틍 서비스를 위해 로그를 기록할 수도 있고
logErrorToMyService(error, info);
}

static getDerivedStateFromError(error) {
// fallback UI를 표현하기 위해여 state를 업데이트 할 수도 있다.
return { hasError: true };
}

render() {
if (this.state.hasError) {
// custom Fallback UI를 그릴 수 있다.
return <h1>{"Something went wrong."}</h1>;
}
return this.props.children;
}
}

그리고 이 컴포넌트는 아래와 같이 사용할 수 있다.

1
2
3
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>

👆

How error boundaries handled in React v15?

unstable_handleError 메서드를 활용한 기본적인 error boundaries만 제공하고 있다. 그리고 v16에서 componentDidCatch로 변경되었다.

👆

보통 PropTypes를 많이 사용한다. 그러나 크기가 큰 어플리케이션의 경우에는, Flow나 타입스크립트같은, 컴파일 단계에서 타입체킹을 제공하고 자동완성을 지원해주는 정적 타입 체커를 사용하는 것이 좋다.

👆

What is the use of react-dom package?

react-dom은 앱 최 상단 레벨에서 사용되는, DOM을 다루는데 필요한 메서드를 제공한다. 대부분의 컴포넌트는 이 모듈을 필요로 하지 않는다. 여기에 있는 메소드를 몇가지 나열하면

  1. render()
  2. hydrate()
  3. unmountComponentAtNode()
  4. findDOMNode()
  5. createPortal()

👆

What is the purpose of render method of react-dom?

render 메서드는 제공된 컨테이너의 DOM에 있는 React element를 render 하고 Component에 대한 참조를 반환하는데 사용된다. React element가 이전에 렌더링 되었다면 update 를 수행하고 최근의 변경사항을 반영하기 위해 필요에 따라 DOM을 변경하기도 한다.

1
ReactDOM.render(element, container[, callback])

옵셔널 콜백이 있따면, 컴포넌트가 렌더링/업데이트 된 이후로 실행된다.

👆

What is ReactDOMServer?

ReactDOMServer는 컴포넌트를 정적 마크업으로 렌더링할 수 있게 해준다. (보통 노드 서버에서 많이 사용 된다) 이 오브젝트는 서버사이드 렌더링을 할 때 사용된다. 아래 메서드들은 서버와 브라우저 환경 모두에서 사용할 수 있다.

  1. renderToString()
  2. renderToStaticMarkup()

예를 들어, 노드 베이스 웹서버인 Express, Hapi, Koa 등에서 서버를 실행한다면, renderToString메서드를 호출하여 이에 대한 응답으로 루트 컴포넌트를 string으로 렌더링할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
// using Express
import { renderToString } from "react-dom/server";
import MyPage from "./MyPage";

app.get("/", (req, res) => {
res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
res.write('<div id="content">');
res.write(renderToString(<MyPage />));
res.write("</div></body></html>");
res.end();
});

👆

How to use innerHTML in React?

browser DOM에서 innerHTML대신 dangerouslySetInnerHTML를 사용할 수 있다. innerHTML과 마찬가지로, 이 속성 또한 크로스 사이트 스크립팅 공격 (XSS)에 취약하다. __html을 키로 하고 HTML text를 값으로 가지는 object를 리턴하면 된다.

1
2
3
4
5
6
7
function createMarkup() {
return { __html: "First &middot; Second" };
}

function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}

👆

How to use styles in React?

style 속성은 css 문자열 대신 camelCased속성이 있는 자바스크립트 오브젝트를 허용한다. 이는 DOM 스타일 자바스크립트 속성과 일치하며, 효율적이고, XSS 보안 허점을 막아준다.

👆

How events are different in React?

React 엘리먼트에서 이벤트를 다루는 것은 문법상 약간의 차이가 있다.

  1. 리액트 이벤트 핸들러는 lowerCase가 아닌 camelCase로 써야한다.
  2. JSX에서는 문자열이 아닌, 함수 이벤트 핸들러를 파라미터로 보낸다.

👆

What will happen if you use setState() in constructor?

setState()를 사용하면, 객체 상태가 할당되고, 자식을 포함한 모든 컴포넌트가 다시 렌더링된다. 그리고 아래와 같은 에러메시지가 나타난다. Can only update a mounted or mounting component. 따라서 this.state를 사용하여 생성자내에서 변수를 초기화 해야 한다.

👆

What is the impact of indexes as keys?

키는 리액트에서 엘리먼트를 추적할 수 있도록 안정적이어야 하고, 예측가능해야 하고, 유니크해야 한다.

아래 코드에서 각 엘리먼트의 키는 데이터를 따르는 것이 아니라 단순히 순서에 따라 결정된다. 이는 React가 하는 최적화를 제한한다.

harmony
1
2
3
{
todos.map((todo, index) => <Todo {...todo} key={index} />);
}

만약 데이터를 유니크 키로 사용한다면 위의 조건을 만족하기 때문에, React는 다시 연산할 필요 없이 재정렬할 수 있다.

harmony
1
2
3
{
todos.map(todo => <Todo {...todo} key={todo.id} />);
}

👆

Is it good to use setState() in componentWillMount() method?

componentWillMount()에서 비동기 초기화를 하는 것은 피하도록 권장한다. componentWillMount()는 마운팅이 일어나기 직전에 바로 실행된다. 이는 render()함수가 불리우기 직전이며, 따라서 여기에서 state를 새로 값을 할당 한다 하더라도 리렌더링을 트리거 하지 않는다. 이 메소드 내에서는 사이드 이펙트나 subscription등은 피해야 한다. 따라서 비동기 초기화는 componentDidMount()에서 하는 것이 좋다.

harmony
1
2
3
4
5
6
7
8
componentDidMount() {
axios.get(`api/todos`)
.then((result) => {
this.setState({
messages: [...result.data]
})
})
}

👆

What will happen if you use props in initial state?

컴포넌트의 새로고칩 없이 props가 변경된다면, 현재 상태의 컴포넌트는 절대로 업데이트 하지 않기 때문에 새로운 prop값이 화면에 표시되지 않을 것이다. props를 통한 state값의 초기화는 컴포넌트가 딱 초기화 되었을 때만 실행된다.

harmony
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyComponent extends React.Component {
constructor(props) {
super(props);

this.state = {
records: [],
inputValue: this.props.inputValue
};
}

render() {
return <div>{this.state.inputValue}</div>;
}
}

props를 render 함수 내에서 쓰면 값을 업데이트 한다.

harmony
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyComponent extends React.Component {
constructor(props) {
super(props);

this.state = {
record: []
};
}

render() {
return <div>{this.props.inputValue}</div>;
}
}

👆

How do you conditionally render components?

때로는 어떤 상태값에 따라서 렌더링을 다르게 해야하는 경우가 발생한다. JSX는 falseundefined는 렌더링하지 않으므로, 특정 조건에 true를 주는 형식으로 조건부 렌더링을 할 수 있다.

harmony
1
2
3
4
5
6
const MyComponent = ({ name, address }) => (
<div>
<h2>{name}</h2>
{address && <p>{address}</p>}
</div>
);

if-else도 삼항연산자를 활용하면 아래와 같이 할 수 있다.

harmony
1
2
3
4
5
6
const MyComponent = ({ name, address }) => (
<div>
<h2>{name}</h2>
{address ? <p>{address}</p> : <p>{"Address is not available"}</p>}
</div>
);

👆

Why we need to be careful when spreading props on DOM elements?

spread prop를 쓴다면, HTML에 알수없는 속성을 추가할 수 있는 위험이 있기 때문에 좋지 못하다. 대신 ...rest 연산자를 쓴다면, 필요한 props만 추가해서 넣을 수 있다.

1
2
3
4
5
6
7
const ComponentA = () => (
<ComponentB isDisplay={true} className={"componentStyle"} />
);

const ComponentB = ({ isDisplay, ...domProps }) => (
<div {...domProps}>{"ComponentB"}</div>
);

👆

How you use decorators in React?

클래스 컴포넌트에 데코레이터를 쓸 수 있으며, 이는 함수에 컴포넌트를 넘기는 것과 동일하다. 데코레이터는 유연하고 읽기 쉬운 방법으로 컴포넌트를 기능적으로 수정할 수 있도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@setTitle("Profile")
class Profile extends React.Component {
//....
}
const setTitle = title => WrappedComponent => {
return class extends React.Component {
componentDidMount() {
document.title = title;
}

render() {
return <WrappedComponent {...this.props} />;
}
};
};

주의: 데코레이터는 es7 문법에 포함되지 못하고 현재 stage2 단계에 있다.

👆

How do you memoize a component?

함수형 컴포넌트를 기반으로한 메모이제이션이 가능한 라이브러리가 있다. 예를 들어, moize라이브러리를 활용하면, 다른 컴포넌트 내에서 컴포넌트를 메모이제이션 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
import moize from "moize";
import Component from "./components/Component"; // this module exports a non-memoized component

const MemoizedFoo = moize.react(Component);

const Consumer = () => {
<div>
{"I will memoize the following entry:"}
<MemoizedFoo />
</div>;
};

👆

How you implement Server Side Rendering or SSR?

React는 이미 노드 서버에서 렌더링을 다룰 수 있도록 지원되고 있다. 클라이언트 사이드와 동일하게 렌더링할 수 있는 특수한 버전의 DOM renderer가 제공되고 있다.

1
2
3
4
import ReactDOMServer from "react-dom/server";
import App from "./App";

ReactDOMServer.renderToString(<App />);

이 메소드는 일반적인 HTML을 string으로 내보내며, 이는 서버의 응답 일부를 페이지 본문 내부에 위치시킬 수 있다. 클라이언트 사이드에서, 리액트는 미리 렌더링된 컨텐츠를 감지하고 나머지를 원활하게 렌더링할 수 있다.

👆

How to enable production mode in React?

Webpack의 DefinePlugin 메서드를 활용하여, NODE_ENVproduction으로 설정해야 propType의 유효성 검사 같은 추가적인 경고를 제거할 수 있다.

production 모드와 별도로, 주석을 제거하고 코드르 압축시키는 uglify의 dead-code 코드를 사용하여 minify하면 번들링 사이즈를 줄일 수 있다.

👆

What is CRA and its benefits?

CRA(create-react-app)는 특별한 설정없이도 빠르고 간편하게 리액트 어플리케이션을 만들수 있도록 해주는 Cli tool이다.

1
2
3
4
5
6
7
8
9
10
11
# Installation
$ npm install -g create-react-app

# Create new project
$ create-react-app todo-app
$ cd todo-app

# Build, test and run
$ npm run build
$ npm run test
$ npm start`

여기에는 리액트 앱을 만드는데 필요한 모든 것이 담겨져 있다.

  1. React, JSX, ES6, 문법 지원을 위한 Flow
  2. spread operator와 같은 es6 문법
  3. auto prefixed css를 통해, -web-kit` 과 같은 접두어를 붙이지 않아도 됨
  4. 빠른 인터렉티브 유닛 테스트 러너와 함께 커버리지 리포팅
  5. 일반적인 실수에 대해 경고하는 라이브 dev 서버
  6. 배포를 위해 소스맵, 해쉬와 함께 제공되는 JS, CSS, 이미지 번들링 해주는 빌드 스크립트

👆

What is the lifecycle methods order in mounting?

컴포넌트가 생성되고, DOM에 들어가는 과정에서 아래와 같은 라이프 사이클 메서드가 순서대로 호출된다.

  1. constructor()
  2. static getDerivedStateFromProps()
  3. render()
  4. componentDidMount()

👆

What are the lifecycle methods going to be deprecated in React v16?

다음 lifecycle메서드는 안전하지 않은 코딩법이 될 수 있고, 비동기 렌더링시 문제가 발생할 수 있다.

  1. componentWillMount()
  2. componentWillReceiveProps()
  3. componentWillUpdate()

v16.3 부터 UNSAFE_ prefix가 붙고, v17에서는 삭제된다.

👆

What is the purpose of getDerivedStateFromProps() lifecycle method?

새로운 라이프 사이클 메서드 getDerivedStateFromProps()는 component가 인스턴스화 된 후, 다시 렌더링 되기전에 호출된다. object를 반환하여 state를 업데이트 하거나, null을 리턴하ㅕㅇ 새로운 props에서 state update가 필요하지 않도록 나타낼 수도 있다.

1
2
3
4
5
class MyComponent extends React.Component {
static getDerivedStateFromProps(props, state) {
// ...
}
}

이 메서드는 componentDidUpdate()와 함께 쓴다면, componentWillReceiveProps()의 모든 유즈케이스에 적용할 수 있다.

👆

What is the purpose of getSnapshotBeforeUpdate() lifecycle method?

새로운 메서드 getSnapshotBeforeUpdate()는 DOM 업데이트 직전에 호출된다. 이 메서드의 반환값은 componentDidUpdate()의 세번째 파라미터로 전달된다.

1
2
3
4
5
class MyComponent extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// ...
}
}

이 메서드는 componentDidUpdate()와 함께 쓴다면, componentWillUpdate()의 모든 유즈케이스에 적용할 수 있다.

👆

Do Hooks replace render props and higher order components?

render props와 HOC 모두 한개의 자식만 렌더링 하지만, 대부분의 경우 Hooks API를 아용하면 트리에 의존성을 줄이면서 간단하게 구현할 수 있다.

👆

displayName을 쓰는 것 보다 컴포넌트에 레퍼런스를 주는 방법이 더 좋다.

displayName을 쓰는 법 보다

1
2
3
4
export default React.createClass({
displayName: 'TodoApp',
// ...
})

이렇게 하는게 더 좋다.

1
2
3
export default class TodoApp extends React.Component {
// ...
}

👆

마운팅에서 렌더링까지 아래와 같은 순서로 나열하길 권장한다.

👆

  1. static 메서드
  2. constructor()
  3. getChildContext()
  4. componentWillMount()
  5. componentDidMount()
  6. componentWillReceiveProps()
  7. shouldComponentUpdate()
  8. componentWillUpdate()
  9. componentDidUpdate()
  10. componentWillUnmount()
  11. 클릭 또는 이벤트 핸들러 onClickSubmit() onChangeDescription()
  12. 렌더를 위한 getter 메서드 getSelectReason() getFooterContent()
  13. 옵셔널 렌더 메서드 renderNavigation() renderProfilePicture()
  14. render()

Comment and share

🚧작성중 🚧

원문-How Browsers Work: Behind the scenes of modern web browsers

이 글을 요약/번역한 더 좋은 글이 네이버 d2에 있습니다. 개인적인 공부 차원에서 이 원문을 fully 한글로 번역하고 있습니다.

소개

웹 브라우저는 가장 널리 쓰이는 소프트웨어다. 이 글에서는, 브라우저가 어떻게 동작하는지 소개할 것이다. 이 글을 읽고 나면, google.com을 타이핑 한 시점 부터 구글페이지가 브라우저에 보이기 까지, 어떤 일련의 과정이 있는지 알게 될 것이다.

목차

  1. 소개
    1. 이 글에서 소개하는 브라우저
    2. 브라우저의 주요 기능
    3. 브라우저의 상위 레벨 구조
  2. 렌더링 엔진
    1. 렌더링 엔진들
    2. 주요 흐름
    3. 주요 흐름 예제
  3. 파싱과 DOM 트리 구축
    1. 파싱: 일반
      1. 문법들
      2. Parser-Lexer 조합
      3. 변환
      4. 파싱 예제
      5. 어휘와 구문에 대한 공식적인 정의
      6. 파서의 종류
      7. 자동으로 파서 생성
    2. HTML 파서
      1. HTML 문법 정의
      2. 문맥으로 부터 자유롭지 못함
      3. HTML DTD
      4. DOM
      5. 파싱 알고리즘
      6. 토큰화 알고리즘
      7. 트리구조 알고리즘
      8. 파싱이 끝났을 경우 액션
      9. 브라우저 에러 처리
    3. CSS 파싱
      1. 웹킷 css 파서
    4. 스크립트와 스타일시트를 프로세싱하는 순서
      1. 스크립트
      2. 예측 파싱
      3. 스타일 시트
  4. 렌더 트리 구축
    1. 돔 트리와 렌더 트리와의 관계
    2. 트리를 구축하는 순서
    3. 스타일 연산
      1. 스타일 데이터 공유
      2. 파이어폭스 규칙 트리
        1. 구조체로 분리
        2. 규칙 트리를 활용하여 스타일 컨택스트 연산
      3. 쉬운 매칭을 위한 규칙 다루기
      4. 다단계 순서에 따라 규칙 적용하기
        1. 스타일 시트 다단계 순서
        2. 특정성
        3. 규칙정렬
      5. 점진적 프로세스
  5. 레이아웃
    1. Dirty bit system
    2. 글로벌 및 incremental 레이아웃
    3. 동기 및 비동기 레이아웃
    4. 최적화
    5. 레이아웃 프로세스
    6. 너비 계산
    7. 라인 브레이킹
  6. 페인팅
    1. 글로벌 및 incremental
    2. 페인팅 순서
    3. 파이어폭스 디스플레이 리스트
    4. 웹킷 사각형 저장소
  7. 다이나믹 변화
  8. 렌더링 엔진의 스레드
    1. 이벤트 루프
  9. CSS2 비쥬얼 모델
    1. 캔버스
    2. CSS box model
    3. 포지셔닝 스킴
    4. 박스 타입
    5. 포지셔닝
      1. relative
      2. floats
      3. absolute & fixed
    6. 층표현
  10. 리소스

1. 소개

1-1. 이 글에서 소개하는 브라우저

오늘날에는 5개의 메인 브라우저가 존재한다: 크롬, 익스플로러, 파이어폭스, 사파리 그리고 오페라. 모바일에서, 안드로이드 브라우저, 아이폰, 오페라 미니, 오페라 모바일, UC 브라우저 등등이 존재하는데, 웹킷을 베이스로 하는 오페라를 제외하고는 대부분이 크롬을 기반으로 하고 있다. 여기에서는 오픈소스 브라우저인 파이어폭스, 크롬, 사파리 (부분적으로 오픈소스)를 예로 들 것이다. stat counter에 따르면 2013년 6월 기준 이 세 브라우저가 차지하는 글로벌 데스크톱 브라우저 비중이 71%에 육박한다. 모바일에서는, 안드로이드 브라우저, 아이폰과 크롬 베이스 프라우저가 54%정도를 차지한다.

2019년 7월 현재 세 브라우저의 시장 점유율은 83% 정도를 차지한다.

1-2. 브라우저의 주요 기능

브라우저의 주요 기능은 사용자가 선택한 웹리소스를 서버에 요청하고, 브라우저 윈도우에 디스플레이하여 표현하는 것이다.일반적으로 리소스는 HTML 문서지만, 여기에는 PDF, 이미지, 혹은 기타 다른 유형이 있을 수도 있다. 이런 리소스의 위치는 사용자가 사용하는 URI(Uniform Resource Identifier)에 의해서 정해진다.

브라우저가 HTML 파일을 해석하고 표시하는 방법은 HTML과 CSS 명세에 따라서 정해진다. 이러한 명세는 웹 표준화 기구인 W3C(World Wide Web Consortium) 에서 정해진다. 수년 간 일부 브라우저는 사양의 일부만을 준수하고 자체 익스텐션을 개발했다. 이로인해 웹 개발자들 사이에서 심각한 호환성 문제가 발생했다. 오늘날 대부분의 브라우저는 이러한 명세를 거의 지킨다.

샤앙을 어긴 브라우저는... ^^

브라우저의 UI는 서로 대부분의 공통점을 가지고 있다. 이러한 공통점들을 예로 들자면

  • URI를 넣을 수 있는 주소창
  • 뒤로가기/앞으로가기 버튼
  • 북마크 옵션
  • 현재 문서를 새로 고치거나 멈출 수 있는 새로고침/멈춤 버튼
  • 사용자의 홈페이지로 가는 홈버튼

이상하게도 이러한 공통점들은, 공식적인 명세로 지정된 것이 아님에도 불구하고 수년 동안 형성된 좋은 관행과 서로를 모방하는 브라우저의 특징에서 비롯된 것이다. HTML5 명세는 브라우저가 가져야하는 UI 요소를 정의하고 있지는 않지만, 일부 공통된 요소들을 나열한다. 그 중에는 주소 표시줄, 상태 표시줄, 도구 표시줄 등이 있다. 물론 파이어폭스나 크롬의 다운로드 관리자와 같은 특정 브라우저에만 있는 기능도 있다.

1-3 브라우저의 상위레벨 구조

  1. 유저 인터페이스: 주소창, 앞/뒤 버튼, 북마크 메뉴 등, 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분을 칭한다.
  2. 브라우저 엔진: 유저 인터페이스와 렌더링 엔진 사이의 동작을 제어
  3. 렌더링 엔진: 요청한 콘텐츠를 표시한다. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시한다.
  4. 네트워킹: HTTP 요청과 같은 네트워크 통신에 사용된다. 여기는 플랫폼 독립적인 인터페이스이고 각 플랫폼 하부에서 실행된다.
  5. UI 백엔드: 콤보 박츠, 창과 같은 기본적인 장치를 그린다. 플랫폼에서 명시하지 않은 일반적인 인터페이스로, OS 사용자 인터페이스 체계를 사용한다.
  6. 자바스크립트 인터프리터: 자바스크립트 코드를 파싱하고 실행을 위해 사용한다.
  7. 데이터 스토리지: 자료를 저장하는 계층이다. 쿠키를 저장하는 것과 같이, 모든 종류의 자원을 하드디스크에 저장할 때 사용한다. HTML5 명세에는 브라우저가 지원해야하는 웹 데이터 베이스가 정의 되어 있다.

브라우저의 주요 구성요소

크롬과 같은 경우에는 각 탭마다 별도의 렌더링 엔진을 사용한다. 각 탭은 다른 프로세스에서 실행된다.

2. 랜더링 엔진

렌더링 엔진의 역할은... 말그대로 렌더링이다. 렌더링은 여기서 요청받은 콘텐츠를 브라우저 화면에 보여주는 역할이다.

기본적으로 렌더링엔진은 HTML, XML, 그리고 이미지를 표시할 수 있다. 플러그인이나 익스텐션을 활용한다면, 다양한 데이터 타입, 예를 들어 PDF 등 도 표시할 수 있다. 그러나, 이번 챕터에서는 일반적인 활용 예제인 CSS로 포맷된 HTML과 이미지를 표시하는 법에 대해서 다룰 것이다.

2-1. 렌더링 엔진들

브라우저 마다 서로다른 렌더링 엔진을 사용하고 있다. 익스프롤러는 Trident를, 파이어폭스는 Gecko를 사용하며 사파리는 Webkit을 사용한다. 그리고 크롬과 15버전 부터 오페라는 Webkit에서 포크된 Blink를 사용한다.

Webkit은 오픈소스 렌더링 엔진으로, 리눅스 플랫폼에서 사용될 엔진으로 만들어졌다가 애플에 의해서 맥과 윈도우도 지원하게 되었다. 자세한 것은 webkit.org를 참조하면 된다.

2-2. 주요 흐름

렌더링 엔진은 통신을 통해 요청한 문서의 내용을 얻는 것부터 시작한다. 보통 문서내용은 8kb 단위로 전송된다. 렌더링 엔진의 기본적인 동작과정은 아래와 같다.

  1. DOM 트리 구축을 위한 HTML 파싱
  2. 렌더 트리 구축
  3. 렌더 트리 배치
  4. 렌더 트리 그리기

렌더링 엔진은 HTML 문서를 파싱하기 시작하며, 콘텐츠 트리 내부에서 태그를 DOM 노드로 변환한다.그리고 엔진은 CSS파일과 스타일 요소를 파싱하기 시작한다. 스타일 정보와 HTML 표시 규칙은 '렌더트리' 라고 부르는 또다른 트리를 생성한다.

렌더 트리는 색상 또는 면적과 같은 시각적 속성이 있는 사각형을 포함하고 잇는데, 정해진 순서대로 화면에 표시된다.

렌더 트리 구축 이후에는, 레이아웃 프로세스로 넘어간다. 이 말은, 각 노드가 화면의 정확한 위치에 표시되는 것을 의미한다. 다음은 UI 백엔드에서 렌더 트리의 각 노드를 가로지르며 모양을 만들어 내는 그리기 과정이다.

2-3. 주요 흐름 예제

웹킷의 주요 흐름

webkit-main-flow

모질라 게코의 렌더링 엔진 주요 흐름

gecko-rendering-engine

그림에서 보다시피, 웹킷과 게코가 약간 다른 용어를 쓰고 있지만 기본적인 흐름은 동일하다.

게코는 시각적으로 처리되는 렌더 트리를 프레임트리 라고 부르고, 각 요소를 프레임이라고 부르는 반면, 웹킷은 렌더 객체로 구성되어 있는 렌더 트리라는 용어를 사용한다. 웹킷은 요소를 배치하는데 레이아웃이라는 용어를 사용하지만, 개코는 리플로우라는 용어를 사용한다. attachment는 웹킷이 렌드 트리를 생성하기 위해 DOM노드와 시각정보를 연결하는 과정을 의미한다. 반면에 게코는 HTML과 DOM트리 사이에 콘텐츠 싱크라고 부르는 과정을 두는데, 이는 DOM 요소를 생성하는 과정으로 웹킷과 비슷하여 큰 의미 있는 차이점으로 보지는 않는다.

3. 파싱과 DOM 트리 구축

3-1. 파싱 일반

파싱은 렌더링 엔진에서 아주 중요한 작업이라서, 파싱에 대해서 아주 깊게 다룰 예쩡이다. 파싱에 대한 짧은 소개와 함께 시작한다.

문서를 파싱한다는 것은 문서 구조를 읽을 수 있는 코드로 변환한다는 것을 의미한다. 파싱의 결과는 보통 노드의 트리로 나타나는데, 이 노드의 트리는 문서의 구조를 나타낸다. 이것을 파스트리 또는 신택스 트리 라고 한다.

예를 들어, 2+3-1은 아래와 같은 트리구조로 나타낼 수 있다.

mathematical-expression-tree-node

3-1-1. 문법들

파싱은 문서에 작성된 언어 또는 형식의 규칙을 따른다. 파싱할 수 있는 모든 형식은 정해진 용어와 구문 규칙에 따라야 한다. 이것을 문맥 자유 문법이라고 한다. 인간의 언어는 이런 모습과는 다르기 때문에 기계적으로 파싱이 불가능하다.

3-1-2. Parser-Lexer 조합

파싱은 두가지 서브 프로세스로 나눌수 있다. 렉시컬 분석과 신택스 분석.

렉시컬 분석은 입력값을 토큰으로 나누는 과정이다. 토큰은 유효하게 구성된 단위의 집합이라고 볼 수 있다. 인간의 언어에서는 사전적으로 뜻이 있는 단어들을 의미한다.

신택스 분석은 언어를 신택스 규칙에 적용하는 것이다.

파서는 보통 두가지 일을 하는데 렉서 (토크나이저라고도 한다)는 입력값을 유효한 토큰 값으로 나누는 일을 하고, 파서는 언어 규칙에 따라 문서구조를 분석하여 파싱트리를 생성한다. 렉서는 공백, 줄바꿈 같은 의미 없는 문자를 제거한다.

D2에서는 렉서를 어휘분석으로, 파서는 구문분석으로 정의했다.

from-source-document-to-parse-tress

파싱과정은 반복된다. 파서는 렉어세 새로운 토큰이 있는지 질의하고, 토큰을 신택스 규칙에 맞추려고 한다. 만약 맞는 규칙이 있다면, 토큰에 해당하는 노드가 파싱트리에 추가되고, 파서는 또다른 토큰을 요청하게 된다.

만약 일치하는 규칙이 없다면, 파서는 토큰을 내부에 저장하고 토큰과 일치하는 규칙이 발견될때 까지 요청한다. 맞는 규칙이 계속해서 없다면 예외처리를 하는데, 이는 문서가 유효하지 않고 신택스 오류가 있다는 것을 의미한다.

3-1-3. 변환

대부분의 경우 파스 트리가 마지막 결과물이 아니다. 파싱은 보통 변환과정에서 사용되는데, 이 과정은 입력된 문서를 다른 형식으로 변환하는 과정을 의미한다. 이와 같은 예로 컴파일이 있다. 소스 코드를 기계 코드로 만드는 컴파일러는, 파싱트리 생성후 기계 코드 문서로 변환한다.

compilation-flow

3-1-4. 파싱 예제

이전 그림에서 수학식을 파스 트리로 만들어 보았다. 간단한 수학 언어를 정의하고, 파싱과정을 살펴보자.

언어: 이 수학언어에는 정수, 더하기, 빼기가 있다.

신택스:

  1. 기본적인 요소로는 표현식, 항, 연산자가 있다
  2. 언어에 포함되는 표현식 갯수에 제한이 없다
  3. 항 뒤에 연산자, 그뒤에 또다른 항이 나오는 형태로 정의한다
  4. 연산자는 더하기 토큰 또는 빼기 토큰이다
  5. 정수 토큰 또는 하나의 표현식은 항이다.

이제 아까 예제인 2+3-1을 분석해보자.

규칙에 맞는 첫 번째 문자열은 2다. 규칙 5에 따르면 이것은 하나의 항으로 볼 수 있다. 두 번째로 맞는 것은 2+3 인데 이것은 항 뒤에 연산자와 또 다른 항이 등장한다는 세 번째 규칙과 일치한다. 입력 값의 마지막 부분까지 진행하면 또 다른 일치를 발견할 수 있다. 2+3은 항과 연산자와 항으로 구성된 하나의 새로운 항이라는 것을 알고 있기 때문에 2+3-1은 하나의 표현식이 된다. 2++은 어떤 규칙과도 맞지 않기 때문에 유효하지 않은 입력이 된다.

3-1-5. 어휘와 구문에 대한 공식적인 정의

어휘는 보통 정규표현식을 활용한다.

예를 들어 이 수학언어는 아래처럼 표현할 수 있을 것이다.

1
2
3
INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -

보시다시피, 정수도 정규표현식으로 정의했다.

신택스는 보통 BNF라고 부르는 형식을 따라서 정의한다.

1
2
3
expression := term operation term
operation := PLUS | MINUS
term := INTEGER | expression

문법이 문맥 자유 문법이라면 언어는 정규 파서로 파싱할 수 있다. 문맥 자유 문법은 완전히 BNF로 표현 가능한 문법으로 보면 된다.

3-1-6. 파서의 종류

파서는 top-down 파서와 bottom up 파서 이렇게 두가지로 나누어진다. 직관적으로 설명하자면, top-down 파서는 위에서 부터 상위구조에서 부터 일치하는 부분을 찾기 시작하지만, bottom-up의 경우에는 밑에서 부터 점차 높은 수준으로 찾는다.

두 종류 파서가 예제를 어떻게 파싱하는지 살펴보자.

top-down 파서는 상위 구조에서 부터 시작한다. 2+3에 해당하는 표현식을 찾는다. 그리고 2+3-1를 찾을 것이다. 표현식을 찾는 과정은, 다른 규칙을 점진적으로 계속해서 찾는 방식인데 가장 높은 수준의 규칙을 먼저 찾는 것을 시작한다.

반면에 bottom-up은 입력값이 규칙에 맞을때 까지 찾아서 맞는 입력값 규칙으로 바꾸는데, 이는 입력값의 끝까지 진행된다. 부분적으로 일치하는 표현식은 파서의 스택에 쌓인다.

Stack input
2 + 3 - 1
+ 3 - 1
항 연산자 3 -1
표현식 -1
표현식 연산자 1
표현식

bottom up 파서는 shift-reduce 파서라고도 불리우는데, 왜냐하면 입력값이 오른쪽으로 이동하면서 신택스 규칙으로 남는 것이 점차 감소하기 때문이다.

3-1-7. 파서 자동 생성

파서를 생성해 줄 수 있는 도구를 파서 생성기라고 한다. 이 생성기에 문법을 제공하고, 어휘와 신택스 규칙을 적용하여 파서를 생성한다. 파서를 작성하려면 파싱에 대한 깊은 이해가 필요하며, 수작업으로 최적화된 파서를 제공하는 것은 쉽지 않으므로 파서 생성기가 유용할 수 있다.

Webkit의 경우에는 flex라고 불리우는 lexer와 bison이라고 불리우는 파서 생성기를 사용한다. Flex는 토큰의 정규 표현식 정의를 포함하는 파일을 입력값으로 받고, bison은 BNF형식의 언어 신택스 규칙을 입력 받는다.

Comment and share

useReducer

1
const [state, dispatch] = useReducer(reducer, initialArg, init);

useState의 대체 함수다. 다수의 하윗 값을 만드는 복잡한 로직, 혹은 다음 state가 이전 state의 의존적인 경우에 쓴다. 뭐가 뭔지 모르겠으니까 예제를 보자.

useState를 쓰기전

1
2
3
4
5
6
7
8
9
10
11
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const initialState = { count: 0 };

function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}

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
function init(initialCount) {
return { count: initialCount };
}

function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return init(action.payload);
default:
throw new Error();
}
}

function Counter({ initialCount }) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({ type: "reset", payload: initialCount })}
>
Reset
</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}

예제를 보니 대충 감이 온다. dispatch를 통해서 state값에 변화를 주지 않고 state값 변화에 트리거를 줄 수 있고, 이 트리거를 이용해 여러개의 state에 변화를 줄 수도 있다.

useCallback

메모이제이션된 콜백을 반환한다.

1
2
3
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);

동일한 콜백을 바라봐야하는 자식 컴포넌트에게 사용할 때 유용하다.

useMemo

1
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

메모이제이션된 값을 반환한다. useMemo는 의존성이 변경되었을 때만, 메모제이션된 값을 다시 계산할 것이다.

useRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const refContainer = useRef(initialValue);

function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

useRef.current에 변경가능한 ref값을 담을 수 있다. 부모 클래스에서 특정 dom객체를 계속 추적해야할 때 유용하다. 다만 .current를 변경한다고 해서 리렌더링이 일어나지는 않는다.

Comment and share

Hooks API

Hook은 react 16.8에서 추가된 개념으로, Hook을 시용하면 class를 갖성하지 않아도 state관리와 같은 react의 기능을 사용할 수 있다.

기본 Hook

useState

1
2
const [state, setState] = useState(initialState);
setState(newState);

상태 유지값, 그리고 그 값을 수정하는 함수를 반환한다. 이전의 state값을 받아다가 수정할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}

동일한 값으로 갱신하는 경우(Object.is) 값이 업데이트 하지 않고 처리를 종료한다.

useEffect

1
useEffect(didUpdate);

화면에 렌더링이 완료된 이후에 수행한다. 또한, 컴포넌트가 화면에서 제거 될 때 정리 해야할 리소스도 선언할 수 있다.

1
2
3
4
5
6
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
});

unsubscribe는 이제 ui에서 컴포넌트를 제거하기 직전에 수행한다. 그리고 만약, 컴포넌트가 여러번 렌더링 된다면 다음 effect가 수행되기 전에 이전 effect가 정리된다.

만약 조건부로 실행하기 위해서는 아래와 같은 방법을 활용할 수도 있다.

1
2
3
4
5
6
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
}, [props.source]);

그럼 이제 props.source값이 변경 될때 만 useEffect가 발생하게 된다.

useContext

Context

context를 이용하면, 매번 일일이 props를 넘겨주지 않아도, 컴포넌트 트리전체에 데이터를 제공할 수 있다. 즉, context는 react 컴포넌트 트리안에서 전역적으로 대이터를 공유할 수 있도록 고안된 방법이다.

1
2
3
4
5
6
7
8
9
<Page user={user} avatarSize={avatarSize} />
<PageLayout user={user} avatarSize={avatarSize} />
<NavigationBar user={user} avatarSize={avatarSize} />
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
</NavigatorBar>
</PageLayout>
</Page>

실제로 useravatarSize를 사용하는 곳은 Link컴포넌트 인데, page 온갖 컴포넌트를 거치면서 값을 내려주는 것을 볼 수 있다. 이게 더 심해지는 경우, 같은 데이터를 트리안의 여러 레벨의 컴포넌트에게 주어야 할 때도 있다. 이렇게 데이터가 변할 때 마다 모든 하위 컴포넌트에게 해당 값을 알려주는 것이 context이다.

1
const MyContext = React.createContext(defaultValue);

Context객체를 만든다. Context 객체를 구독하고, 컴포넌트를 렌더링 할 때 트리 상위에서 가장 가까이 짝이 맞는 Provider로 부터 현재 값을 읽는다. 여기서 선언된 defaultValue는 트리안에서 적절한 Provider를 찾지 못했을 때에만 쓰는 값이다.

1
2
3
<MyContext.provider value="{someValue}">
<SomeComponent />
</MyContext.provider>

Provider는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다. Provider는 value에 있는 prop을 받아서 이 값을 하위 컴포넌트에 전달한다.

1
2
3
<MyContext.Consumer>
{value => /* context 값을 이용한 렌더링 */}
</MyContext.Consumer>

context 변화를 구독하는 React Component다. 반드시 Context.Consumer의 자식은 함수여야만 한다. 이 함수는 context의 현재 값을 받고, React 노드를 반환해야 한다.

1
const value = useContext(MyContext);

를 사용하면, context객체를 받아서, 현재 context의 값을 반환한다.

Comment and share

HAProxy

로드밸런서

로드 밸런싱이란, 부하 분산을 위해서 가상 IP를 통해 여러 서버에 접속하도록 분배하는 기능을 말한다.

로드 밸런싱에서 사용하는 주요 기술은

  • NAT(Network Address Translation): 사설 IP 주소를 공인 IP 주소로 바꾸는 데 사용하는 통신망의 주소 변조기이다.
  • DSR(Dynamic Source Routing protocol): 로드 밸런서 사용 시 서버에서 클라이언트로 되돌아가는 경우 목적지 주소를 스위치의 IP 주소가 아닌 클라이언트의 IP 주소로 전달해서 네트워크 스위치를 거치지 않고 바로 클라이언트를 찾아가는 개념이다.
  • Tunneling: 인터넷상에서 눈에 보이지 않는 통로를 만들어 통신할 수 있게 하는 개념으로, 데이터를 캡슐화해서 연결된 상호 간에만 캡슐화된 패킷을 구별해 캡슐화를 해제할 수 있다.

로드 밸런서는, 네트워크에서 IP 주소와 MAC주소를 바탕으로 목적지 IP주소를 찾아가고, 다시 출발지를 되돌아 오는 구조로 작동된다.

출처: naver d2

HAProxy

ha-proxy

HAProxy는 reserve proxy형태로 작동한다. 흔히 브라우저에서 사용하는 proxy는 클라이언트 앞에서 처리하는데, 이를 forward proxy라고 한다. 반대로 reserve proxy는 실제 서버 요청에 대해 서버 앞단에 존재하면서, 서버로 들어오는 요청을 대신 받아 서버에 전달하고, 요청한 곳에 그 결과를 다시 전달한다.

작동 흐름

  1. 최초 접근 시 서버에 요청 전달
  2. 응답 시 쿠키(cookie)에 서버 정보 추가 후 반환
  3. 재요청 시 proxy에서 쿠키 정보 확인 > 최초 요청 서버로 전달
  4. 다시 접근 시 쿠키 추가 없이 전달 > 클라이언트에 쿠키 정보가 계속 존재함(쿠키 재사용)

haproxy-flow

Comment and share

제법 규모가 있었던 전 회사에서는 특별하게 인프라에 대해 고민을 별로 할 필요가 없었다. 많은 부분이 자동화되어 있었고, 또 적당한 툴로 잘 만들어져 있었기 때문에 개발에 온전히 집중할 수 있었다. 하지만 새롭게 온 스타트업은 (당연하게도) 그런게 없으므로, 회사에서 사용하고 있는 AWS 인프라와 배포 시스템에 대해서 직접 공부할 필요가 있었다. 그 중에서도 처음 접하는 것이 docker 였다.

Docker

예전 부터 이름은 많이 들어봤지만 이런저런 이유로 (귀찮아서) 한번도 써본적이 없었다. docker는 컨테이너 기반의 오픈소스 가상화 플랫폼 이라고 정의할 수 있다. 여기서 중요한 용어 중 하나가 컨테이너라고 생각한다.

컨테이너

docker의 컨테이너에 백엔드 프로그램, DB, 메시지 큐 등 다양한 프로그램을 컨테이너에 집어 넣어 추상화 한 다음, 이를 어디서든 배포할 수 있는 상태로 만들어 준다.

docker-container

docker는 기존의 virtual machine과는 다르게 프로세스를 격리 시키는 방법으로 가상화를 한다. docker가 동작하기 위한 cpu나 메모리만 격리해서 사용하므로, 성능적으로도 손실이 적은 방식이다.

이미지

이미지는 컨테이너의 실행에 필요한 파일 및 설정을 포함하고 있는 것 으로 별도의 상태값을 갖고 변하지 않는다. 컨테이너는 이미지를 실행하는 것이라 볼 수 있고, 추가되거나 변화가 필요한 값은 컨테이너에 저장된다. 말 그대로, 컨테이너를 실행하기 위한 모든 정보를 갖고 있기 때문에 의존성 파일을 컴파일/설치할 필요가 없다. docker를 이용한다면 이제 이미지를 다운받고 컨테이너를 생성면 된다.

Dockerfile을 시작으로 실제해보기

여기를 참고하자. 친절하게 정말 잘 써주셨다.

ECR

아마존 ECR은 docker 컨테이너 이미지를 손쉽게 저장, 관리 배포할 수 있게 도와주는 일종의 레파지토리 같은 개념이다.

AWS_ECR.png

이렇게 생성된 이미지를 푸쉬해서 관리 할 수 있다.

ECS

docker를 사용하다보면 컨테이너를 적절하게 배치하거나 관리할 도구의 필요성을 느끼게 된다. docker에서 만든 swarm도 있지만서도, 가장 유명한건 구글의 쿠버네티스가 아닐까 싶다. 이런 컨테이너 오케스트레이션 도구 중 하나가 바로 aws의 elastic container system (이하 ECS) 다.

Cluster

ecs의 가장 기본적인 단위는 클러스터다. 클러스터는 도커 컨테이너를 실행할 수 있는 가상의 공간, 일종의 논리적인 단위 수준이라고 볼 수 있다.

Task

ECS에서 컨테이너를 실행하는 최소단위를 task라고 부른다. task는 하나이상의 컨테이너로 구성할 수 있다. 같은 task내 있는 컨테이너들은 모두 같은 컨테이너 인스턴스에서 실행되는 것이 보장된다. 이러한 task를 실행하기 위한 것이 task definition 이다. task definition에는 task를 실행하기 위한 다양한 것들을 설정할 수 있다.

task-definition.png

귀찮아서 캡쳐는 안했지만, add container에는 이미지 주소, 컨테이너명, 컨테이너 사양, 환경변수 등을 정할 수 있도록 설정이 준비되어 있다. 그리고 설정 변경 / 이미지 변경이 필요할 때마다, task definition을 새롭게 만듦으로써 (create new revision) 이를 해결할 수 있다.

Service

cluster는 두가지 방식으로 task를 실행할 수 있다. 한가지는 run task definition을 통해서 일회성으로 실행하는 것이다. 이 task는 곧바로 실행되고, 더이상 관리되지 않는다. 두 번째 방법은 서비스를 정의하는 것이다. 서비스는 하나이상의 task definition과 연결된다. 서비스는 크게 리플리카 타입과 데몬타입으로 나누어져 있는데, 우리는 리플리카 타입을 사용해서 n개의 태스크가 실행되도록 관리하고 있다.

어떻게 썼나

먼저 dockerfile을 통해서 이미지를 만들고, 해당 이미지를 ecr에 업로드한다. 그리고 배포가 필요할 때마다 task definition을 새롭게 만들고, 새롭게 만들어진 이미지를 넣었다. 그리고 서비스에서 실행할 이미지를 새롭게 만들어진 k번째 task definition으로 교체하면 끝이다.

써보니까 좋았던 점

의식의 흐름대로 나열해 보았다.

  1. 배포 히스토리를 남길 수 있었다 == 롤백이 쉬웠다. 배포할 때 마다 새롭게 만들어진 이미지를 ecr에 업로드 하였기 때문에 이미지 목록을 히스토리 느낌으로 남길 수 있었다. 그리고 롤백이 필요하면 이전에 잘 동작했던 이미지로 교체해주면 된다. 사실 이 방법보다 쉬운 것은 그냥 예전에 잘 동작했던 task-definition으로 service를 교체해 주는 것이다.

  2. 스테이지 별 로 다른 변수를 task definition - container의 환경변수로 관리할 수 있다. 기존에는 프로젝트에 .development .release 이런 형식으로 소스파일안에 스테이지마다 다른 변수 값을 두어야 했다. 따라서 이 글로벌 변수에 변동이 있을 때마다 배포를 새로 하는 번거로움이 있었는데, 이 방식 대로 하면 단지 task definition을 새롭게 만들고, 환경변수를 추가해서 service에 올려주기만 하면 된다. 추가로 dockerfile 혹은 docker 빌드 과정에 환경변수를 따로따로 주입하지 않는 다면, 어떤 스테이지 든지 간에 동일한 이미지로 대처할 수 있다.

3. 드디어 docker를 써볼 수 있었다.

4. 아마존에 많은 돈을 지출할 수 있다.

Comment and share

Javascript Reduce

멍청이라 그런지 reduce 함수가 잘 이해 되지 않았다.

Reduce

1
2
3
4
5
6
7
8
const list = [1, 2, 3, 4, 5];
const initValue = 10;
const totalSum = list.reduce(
(accumulator, currentValue, currentIndex, array) => {
return accumulator + currentValue;
},
initValue
);

1
25

  • currentValue: 처리할 현재 요소
  • currentIndex (optional): 처리할 요소의 인덱스
  • accumulator: 콜백의 반환값을 계속해서 누적한다. 이 예제에서는 처음엔 1, 그 다음엔 1 + currentValue, 그 다음엔 (1 + currentValue) + currentValue 가 될 것이다.
  • array (optional): reduce를 호출한 배열, 여기서는 list = [1, 2, 3, 4, 5]이 될 것이다.
  • initValue (optional): reduce의 최초 값. 없으면 배열의 0번째 값이 된다. 이 예제에서는 initValue값이 10 이라서, 최종결과는 10 + (1 + 2 ... + 5) 이 될 것이다.
call accumulator currentValue currentIndex array return
1st 10 1 0 [1,2,3,4,5] 11
2nd 11 2 1 [1,2,3,4,5] 13
3rd 13 3 2 [1,2,3,4,5] 16
4th 16 4 3 [1,2,3,4,5] 20
5th 20 5 4 [1,2,3,4,5] 25

중첩 배열 펼치기

1
2
3
4
5
const complicatedList = [[0, 1], [2, 3], [4], [5, 6]];
complicatedList.reduce(
(accumulator, currentValue) => accumulator.concat(currentValue),
[]
);

1
[0, 1, 2, 3, 4, 5, 6]

이보다 더 괴랄한 array의 경우에도 재귀를 사용하여 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const moreComplicatedList = [[0, 1], [[[2, 3]]], [[4, 5]], 6];

const flatten = function(arr, result = []) {
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flatten(value, result);
} else {
result.push(value);
}
}
return result;
};

flatten(moreComplicatedList);

1
[0, 1, 2, 3, 4, 5, 6]

Comment and share

근데 사실.. 컬렉션 필요없지 않을까

자바스크립트에서 일반적인 Object는 key-value쌍을 끊임 없이 추가할 수 있는 형태로 구성되어 있다. 그래서 사실 컬렉션이 필요하지 않은 것 처럼 보일 수도 있다. 그러나 이따금씩 object로 부족할 때가 있다.

  • key 충돌 위험이 존재하는 경우
  • 문자열/심볼 이외의 키 값이 필요한 경우
  • 객체에 얼마나 많은 속성이 있는지 알아낼 수 있는 효과적인 방법이 필요한 경우
  • 객체가 iterable하지 않음. 따라서 for..of...를 사용할 수 없음

es6에 추가된 컬렉션들은 따라서 멤버 데이터를 드러내기 위해 property를 사용하지 않는다. (obj.key, obj[key] 불가능.) 그리고 이들에는 자유롭게 메소드를 추가할 수 있다.

Set

Set은 value로 이루어진 컬렉션이다. 그리고 수정가능하다. 배열과 같을 것 같지만, 다르다.

1
2
3
let faces = new Set("😀 😁 😂 🤣 😃 😄 😅");
faces.size; // 8
faces.add("😂");

  • 일단 (당연하게도) Set에는 같은 value가 중복으로 포함될 수 없다. 기존에 있는걸 추가해도 아무런 변화가 없다.
  • Set은 어떤데이터가 자신의 멤버인지 빠르게 확인하기 위한 목적으로 사용한다
  • 그러나 Set은 index로 값을 조회할 수는 없다.

set 으로 할 수 있는 것

  • new Set(): 비어있는 set 생성

  • set.size: set 데이터 개수 조회

  • set.has(value): valueset에 존재하는지 조회

  • set.add(value)

  • set.delete(value)

  • sets[Symbol.iterator](): set 안의 값을 순회할 수 있는 새로운 이터레이러를 리턴한다. set을 iterable하게 만들어 준다.

    1
    2
    3
    4
    5
    let faces = new Set("😀 😁 😂 🤣 😃 😄 😅");
    iteratorFaces = faces[Symbol.iterator]();
    for (let i of iteratorFaces) {
    console.log(i);
    }

    • set.forEach(f)
    • set.clear
    • set.keys()
    • set.values()
    • set.entries()

Map

Map은 잘 알려진 것처럼, key-value pair로 이루어진 컬렉션이다.

Map으로 할 수 있는 것

  • new Map
  • new Map(pairs)
  • map.has(key)
  • map.size
  • mag.get(key)
  • map.set(key, value)
  • map.delete(key)
  • map.clear()
  • map[Symbol.iterator]() === map.entries()
  • map.forEach(f)
  • map.keys()
  • map.values()

javascript 가 다른점

아래의 코드를 보자.

1
2
3
4
5
6
7
8
let messi = new Set();

const 리오넬메시 = { name: "리오넬메시" };
const 라이오넬멧시 = { name: "리오넬메시" };
messi.add(리오넬메시);
messi.add(라이오넬멧시);

console.log(messi.size); //2 ????

리오넬메시라이오넬멧시는 내부의 값이 같아 보이기 때문에, set에 한개의 값만 추가 될 것 같지만 사실은 그렇지 않다. 자바스크립트에서는 두개의 값을 다르게 본다. 이유는 자바스크립트가 값을 비교할 때 두가지 다른 방법을 사용하기 때문이다.

  • string, number같은 primitive는 값을 비교한다
  • array, date, object 등은 reference를 비교한다. (메모리의 같은 위치를 참조하고 있는가?)

1
2
3
4
5
6
7
const 리오넬메시 = { name: "리오넬메시" };
const 라이오넬멧시 = { name: "리오넬메시" };
const 메석대 = 리오넬메시;

메석대 === 리오넬메시; // true
메석대 == 라이오넬멧시; // false
리오넬메시 === 라이오넬멧시; //false

본질적으로 같은 메모리를 참조하는 값 끼리만 true를 반환하는 것을 볼 수 있다. (두 object를 비교하는 방법은 여기를 참조)

다시 Set으로 돌아와서, javascript는 저 두 값을 제대로 비교하지 못하기 때문에 set에 두개의 값이 들어가게 된다. 물론 해시코드를 사용하면 가능하지만, javascipt에는 그런거 없다

또 하나 다른 점이라고 한다면, mapset에 추가한 순서가 곧 순회하는 순서와 같다는 것이다. 이 역시 다른 언어들과는 다른 점이다.

WeakMap, WeakSet

  • WeakMapnew .has() .get() .set() .delete() 만 지원한다
  • WeakSetnew .has() .add() .delete() 만 지원한다.
  • WeakSetWeakMap의 key는 반드시 object여야 한다.

그렇다. 열거형이 존재하지 않는다. 그 이유는 참조하고 있는 오브젝트가 사라지면 해당 key, value가 사라지는 WeakMap, WeakSet의 특징 때문이다.

1
2
3
4
5
let john = { name: "John" };
// 객체에 접근 가능, 해당 객체는 메모리에서 참조되고 있음.
// 참조를 null로 overwrite
john = null;
// 객체는 메모리에서 이제 삭제됨

1
2
3
4
5
6
7
8
let john = { name: "John" };
let array = [john];
john = null; // 참조를 null로 overwrite

// john은 객체안에 살아 있기 때문에 가비지 컬렉팅이 되지 않음.
// 그래서 array[0]으로 접근 가능
array[0];
// { name: "John" }

이는 기존 Map, Set에서도 동일하다.

1
2
3
4
5
6
7
8
9
let john = { name: "John" };

let map = new Map();
map.set(john, "윅");

john = null; // 참조를 null로 overwrite

// john은 맵안에서 살아있기 때문에
// map.keys() 로 접근 가능

그러나 WeakMap, WeakSet은 다르다

1
2
3
4
5
6
7
8
let john = { name: "John" };

let weakMap = new WeakMap();
weakMap.set(john, "윅");

john = null; // 참조를 null로 overwrite

// john은 메모리에서 사라짐 (가비지 콜렉팅 당함)

그럼 도대체 이것은 언제 쓸까? 객체가 사라지면 자동으로 가비지 콜렉팅 해준다는 특성을 활용해, 아래와 같은 것이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
let john = { name: "John" };
// map: 유저 => 방문횟수
let visitsCountMap = new Map();
visitsCountMap.set(john, 123);

// john이 사라짐
john = null;

// 그러나 Map에서는 계속 남아 있으므로, 따로 처리를 해주어야 함.
// 또한 john은 map에서 key로 사용하고 있으므로 메모리에서도 존재함.
console.log(visitsCountMap.size); // 1

그러나 여기서 WeakMap을 사용하면, 자동으로 가비지 콜렉팅이 되므로 Map에 남아있는 key에 대해서 까지 신경쓰지 않아도 된다.

Comment and share

Javascript Symbol

Javascript Primitive

기존에 자바스크립트는 6가지의 primitive가 있었다.

  • Object
  • string
  • number
  • boolean
  • null
  • undefined

그러나 es6가 들어서면서 symbol이라는 7번째 primitive가 추가되었다.

Symbol

1
2
const helloSymbol = Symbol();
const hiSymbol = Symbol();

새로운 심볼 값을 생성했다. 이 심볼로 생성한 값은 변경할 수 없으므로 const에 할당에도 상관없다. 그리고 이렇게 생성된 심볼 값은 프로그램 내에서 유일함을 보장해 준다.

1
2
3
4
let obj = {};
obj[helloSymbol] = "hello";
obj[hiSymbol] = "hi";
console.log(obj);

1
{Symbol(): "hello", Symbol(): "hi"}

물론 문자열이나 숫자를 key로 사용할 수 있지만, symbol은 유일함을 보장해주기 때문에 이렇게 키값으로 사용할 수 있다.

1
2
const welcomeSymbol = Symbol("환영합니다");
console.log(welcomeSymbol);

1
Symbol(환영합니다)

Symbol안에 있는 문자열은 일종의 주석으로 보면 될 것 같다.

예제

1
2
3
4
5
6
7
const isBlocked = Symbol("is blocked element?");

if (element[isBlocked]) {
openElement(element);
} else {
element[isBlocked] = true;
}

elementisBlocked라는 심볼을 키로 갖는 object다. 문자열이나 숫자가 아닌 심볼을 key로 갖는 속성이다. 이는 유일성을 보장해주기 때문에 다른 키들과의 충돌을 방지할 수 있다. 다만 obj.name과 같이 dot을 이용해서 접근할 수 없다. 반드시 []를 활용해서 접근해야 한다.

한가지 주의 해야할 것은 isBlocked 심볼 값이 스코프 내에 존재 할 때만 이러한 행위가 가능하다는 것이다. 어떤 모듈이 심볼을 스스로 만드는 경우, 해당 모듈은 해당 심볼을 모든 객체에 적용할 수 있다. 즉 다른 속성과의 충돌을 걱정할 필요가 없다.

그리고 심볼 키는 이러한 충돌을 방지하기 위해서 만들어 진 것이므로, 일반적인 javascript 객체 조사는 Symbol을 무시한다. 무슨 소리냐면...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let 메시 = {};
메시["영문명"] = "Lionel Messi";
메시["별명"] = "라이오넬 멧시";
const Nationality = Symbol("선수의 국적");
메시[Nationality] = "칠레";

for (let i in 메시) {
console.log(i);
}

for (let i of Object.keys(메시)) {
console.log(i);
}

for (let i of Object.getOwnPropertyNames(메시)) {
console.log(i);
}

1
2
3
4
5
6
"영문명"
"별명"
"영문명"
"별명"
"영문명"
"별명"

이처럼 심볼 Nationality 키는 일반적인 상황에서 모두 무시 되는 것을 볼 수 있다. 물론 이를 조회하는 방법도 있다.

1
Object.getOwnPropertySymbols(메시);

1
[Symbol(선수의 국적)]

혹은 심볼을 포함해서 모든 키를 조회하고 싶다면

1
Reflect.ownKeys(메시);

1
["영문명", "별명", Symbol(선수의 국적)]

Reflect.ownKeys를 활용하면 된다.

심볼의 특징

  • 일단 생성되면 변경되지 않는다
  • 속성을 부여할 수 없다
  • object의 key 값으로 사용할 수 있다.
  • 모든 심볼은 고유하다. 주석이 동일하다 하더라도 일단 생성되면 다르게 구별된다.
  • 문자열로 자동으로 변환되지 않는다.

1
2
3
4
5
const newSymbol = Symbol(
"this symbol"
)`symbol is ${newSymbol}`//Uncaught TypeError: Cannot convert a Symbol value to a string
`symbol is ${String(newSymbol)} ${newSymbol.toString()}`;
// symbol is Symbol(this symbol) Symbol(this symbol)

심볼을 갖는 방법

  • Symbol()을 호출한다. 이는 호출할 때 마다 새롭고 고유한 심볼을 만들어 준다.
  • Symbol.for(string)을 호출한다. 이 메소드는 Symbol Registry라는 심볼목록을 참조하여 리턴하는데, 앞서와는 다르게 심볼 목록을 공유한다. Symbol.for('호날도')를 계속해서 호출한다면, 매번 같은 심볼을 리턴한다. 이는 심볼이 공유 되어야 하는 상황에서 유용하다.
  • Symbol.length 처럼 표준에 정의된 심볼을 가져오는 법

Comment and share

Author's picture

yceffort

yceffort


programmer


Korea