React 동작 이해하기 - 1
업데이트:
React
리액트는 컴포넌트 단위로 개발 가능한 SPA(Single Page Application)
으로 부모에서 자식컴포넌트로 전파되는 단방향 방식을 사용합니다. props 를 사용하여 데이터를 전달하고 state 로 상태를 컨트롤 할 수 있습니다. 이때 state를 직접 수정하는것은 공식문서에서도 금지하고있는 부분인데 직접 state 에 접근하여 값을 변경할경우 컴포넌트를 렌더링하지 않게됩니다.
this.state.contents = 'contents' // X
this.setState({ contents: 'contents' }) // O
state 를 수정하려면 setState()
를 사용하는 방법으로 업데이트를 진행해야합니다. this.props
와 this.state
는 비동기적으로 업데이트될 수 있기때문에 state 의 값 변경을 this.props
나 this.state
의 값을 사용할경우 업데이트에 실패할 수도있습니다. 따라서 setState 에 콜백함수를 사용하여 첫번째인자로 state 를 두번쨰로 props 를 받아서 업데이트에 적용할 수 있습니다.
this.setState({ // X
counter: this.state.counter + this.props.increment
})
this.setSTate((state, props) => { // O
counter: state.counter + props.increment
})
리액트는 부모컴포넌트에서 자식컴포넌트로 데이터가 아래로 흐르는 단방향 데이터 흐름을 갖는다고했는데 데이터는 트리구조에 의해 자신의 아래컴포넌트에만 영향을 끼치게됩니다. 즉 부모컴포넌트에 존재하는 자식컴포넌트들은 모두 독립적인 컴포넌트입니다.
React 의 문법 JSX
리액트에서는 JSX 라는 문법을 사용할 수 있는데 html 에서 사용하던 마크업 문법을 리액트의 자바스크립트 파일 내에서 사용할 수 있도록 해줍니다. JSX 는 리액트 엘리먼트를 생성하고 DOM에 렌더링 하게됩니다.
const element = <h1>Hello, world</h1>;
JSX 문법을 사용함으로써 얻을 수 있는 장점은 html 이 아니라 자바스크립트로 해석되기때문에 중괄호를 사용하여 자바스크립트 표현식을 사용할 수 있습니다. 그리고 XSS(cross-site-scripting)
공격을 방지할 수 있습니다. 기본적으로 React DOM 은 JSX 에 삽입된 모든 값을 렌더링하기전에 이스케이프하기때문에 명시적으로 작성되지않은 내용은 주입되지않습니다.
< (<)
> (>)
& (&)
모든 항목은 렌더링되기전에 문자열로 변환됩니다. 이러한 특성으로인해 주입공격을 방지할 수 있습니다.
Babel 을 통해 JSX 를 React.createElement()
호출하여 컴파일을 진행합니다.
const element = <h1 className="hello">Hello, world</h1>;
// =>
const element = React.createElement(
'h1',
{ className: 'hello' },
'Hello, world'
)
// =>
const element = { // 비교를위해 작성된 단순예제
type: 'h1',
props: {
className: 'hello',
children: 'Hello, world'
}
}
단순예제말고 실제로 리액트가 어떤 값들을 받아서 엘리먼트를 생성하는지 궁금해서 찾아봤습니다. 리액트는 페이스북에서 만들어 오픈소스로 제공되고있기때문에 원한다면 찾고자하는 로직을 찾아볼 수 있습니다.
// react / packages / react / src / ReactElement.js
const ReactElement = function (type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
}
type 과 props 이외에도 여러가지 다양한 값들을 받고있는걸 볼 수 있는데 $$typeof: REACT_ELEMENT_TYPE,
이 값이 눈에 띄입니다. 이게뭔가 또 찾아봤습니다.
// react / packages / shared / ReactSymbols.js
export let REACT_ELEMENT_TYPE = 0xeac7; // 60103 16진법 사용
if (typeof Symbol === 'function' && Symbol.for) {
const symbolFor = Symbol.for;
REACT_ELEMENT_TYPE = symbolFor('react.element');
.
.
.
}
기본값을 16진법을 사용한 값이 할당되어있고 조건문에 따라서 Symbol 을 사용하여 값을 할당하고 있습니다 여기서 Symbol 이란 자바스크립트에서 새로 추가된 원시타입으로 기존 자바스크립트 원시타입에는
- String
- Number
- Boolean
- undefined
- null
이렇게 다섯가지 타입만 있었는데 심볼이 이 항목중에 하나로 추가되었습니다.
Symbol
심볼은 유일한 식별자로 만들때 사용합니다. Symbol()
let id = Symbol(); // 새로운 심볼 생성
let id = Symbol("id"); // 심볼에 id 라는 이름을 가진 설명을 붙임 디버깅시 용이함
무슨말이냐면 심볼은 유일성이 보장되는 자료형으로 설명이 동일한 심볼을 여러개만들어도 각 심볼값은 다릅니다
let id1 = Symbol("id");
let id2 = Symbol("id");
console.log(id1 == id2) // false
그리고 심볼은 암묵적 형변환도 발생하지 않습니다. 이런 심볼을 사용하면 숨김 프로퍼티를 만들 수 있습니다. 숨김프로퍼티는 외부코드에서 직접적인 접근이 불가능하고 덮어쓸수도없습니다.
let user = {
name: "Kang"
}
let id = Symbol("id");
user[id] = 1;
console.log(user[id])
이렇게 유일성이 보장되는 키값으로 프로퍼티를 추가하면 누군가 동일한 심볼 또는 아이디를 키로하는 프로퍼티로 생성하거나 덮어씌워져도 내가 추가한 프로퍼티는 유일한 상태로 유지할 수 있습니다. 또한 심볼은 for...in
반복문에서도 배제됩니다.
기본적으로 심볼은 이름이 같더라도 모두 별개로 취급되기때문에 유일성이 보장됩니다. 그러나 항상 예외는 있듯이 이름이 같은 심볼을 가리키길 원하는 경우가 있을수도 있습니다. 여러 애플리케이션에서 동일한 심볼이름을 활용하여 특정 프로퍼티에 접근해야할 필요성이 있을때 Symbol.for(key) 를 사용할 수 있습니다.
// 전역 레지스트리에서 심볼을 읽고 만약 심볼이 없다면 새로운 심볼을 생성
let id = Symbol.for("id");
// 동일한 이름을 사용해 심볼을 다시 읽어옴
let idAgain = Symbol.for("id")
console.log(id === idAgain) // true
모르는건 대충 다 찾아본것같으니 다시 아까 리액트에서 사용하고있던 코드를 다시보겠습니다.
export let REACT_ELEMENT_TYPE = 0xeac7;
if (typeof Symbol === 'function' && Symbol.for) {
const symbolFor = Symbol.for;
REACT_ELEMENT_TYPE = symbolFor('react.element');
.
.
.
}
일단 처음 타입을 정의할때 헥사코드값을 먼저 할당했었는데 우리가 아는 10진법을 사용한것이 아니라 16진법을 사용한건 컴퓨터가 컴파일하는 과정의 연산을 줄이기위한 할당이라고 생각합니다. 10진법일때보다 16진법일때 바이트코드로 컴파일 하는 과정에서 한단계를 줄일 수 있기 때문입니다. 그리고 심볼값을 할당할건데 왜 먼저 다른값을 넣었나 생각해보면 심볼은 자바스크립트가 존재할때부터 있던 원시값이 아닌 새롭게 추가된 타입으로 특정 하위브라우저에서는 아직 지원하지않는 문법일 수도있습니다. 즉 저런 조건문이 없었다면 심볼타입이 없는 브라우저에서는 아마 에러를 출력하게 될겁니다.
그래서 조건문에서 두가지를 검사합니다. 심볼이 함수타입으로 존재하는지 그리고 for 메서드가 심볼에 존재하는지 확인하고 내부 코드가 동작하게됩니다.
REACT_ELEMENT_TYPE = Symbol.for('react.element');
const ReactElement = function (type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
}
다시한번 리액트에서 엘리먼트를 생성할때 받는 값들을 보면 ` $$typeof: REACT_ELEMENT_TYPE, 이 값은 생성된 엘리먼트가 고유한 이름을 가진 프로퍼티를 갖게 만들어주고 이 값으로 인해서 외부에서의 XSS 공격을 차단할 수 있습니다. 엘리먼트의 심볼에 대한 접근은
Symbol.for` 로만 접근이 가능하고 외부에서 생성된 객체와는 서로 다른 엘리먼트라는걸 알 수 있습니다.
댓글남기기