전통적으로 웹 페이지 UI를 만들 때는 HTML, CSS, JavaScript가 사용됨
웹 페이지의 구조와 Semantic은 HTML로,
디자인은 CSS로,
그리고 사용자가 버튼을 클릭하거나 Form에 정보를 기입하는 등,
User Interaction이 일어날 때는 JavaScript를 사용하여 동적으로 웹 페이지를 변경하는 작업이 이뤄짐
JavaScript의 DOM API를 이용해 동적인 웹 페이지를 만드는 일은 상당히 길고 반복적인 코드를 유발함
2006년, jQuery가 등장하며 DOM API를 보다 간결하게 사용할 수 있게 해주며 대세가 됨
// JavaScript
const textNode = document.querySelector("#textNode");
textNode.style.color = "red";
document.querySelector("#someDiv").appendChild(textNode);
// jQuery
$("#textNode").css("color","red").appendTo("#someDiv");
위 코드를 비교해보면 한눈에 jQuery가 훨씬 간결하고 이해하기 쉽다는 것을 알 수 있음
(단, JavaScript로 변환이 일어나기 때문에 성능은 약간 열세)
2010년, Google에서 발표한 AngularJS가 등장하며 한 때 jQuery급 성장세를 보여주었으나,
성능, 러닝 커브 등의 문제로 React에게 왕좌를 넘겨주게 된다.
(V2부터는 React에 근접한 성능을 보여준다고 하며, 현재도 React, Angular, Vue가 가장 많이 쓰인다.)
Meta(구 Facebook)에서 2013년 발표한 오픈 소스 JS 라이브러리
import React from 'react';
// Functional Component
const Button = () => {
return <button>Click</button>;
}
// Class Component
class Button extends React.Component {
render() {
return <button>Click</button>;
}
}
React App은 수 많은 Component의 조합으로 이루어지며,
각 Component는 JSX Element를 반환하는 보통의 JS 함수
초창기에는 Class Component와 병행하여 사용되었지만
현재는 거의 Functional Component만 사용된다.
JSX는 JavaScript XML의 약자.
XML은 HTML과 구조적으로는 똑같이 생겼지만, 사전에 정의되지 않은 Custom tag의 형태로 사용된다.
// HTML
<html>
<body>HTML Document</body>
</html>
// XML
<custom-tag>
<custom-body>XML</custom-body>
</custom-tag>
// JSX
<html>
<CustomBody>JSX</CustomBody>
</html>
위 예시를 비교해보면, 셋 다 형태는 동일한 것을 알 수 있다.
차이점을 살펴보면,
HTML는 사전에 미리 정의된 html
, body
등의 태그만 사용되었다.
XML은 HTML 태그가 아닌 커스텀 태그를 사용하였다.
JSX는 HTML 태그와 함께 PascalCase 형태의 커스텀 태그를 사용하였다.
JSX는 그저 HTML 태그와 사용자가 만든 커스텀 태그를 JavaScript 파일 내에서 사용하는 것을 말한다.
그러나 JavaScript 인터프리터는 이 문법을 이해할 수 없다.
고로 React로 작성된 코드를 실행하기 전에, JSX 형태의 코드를 아래와 같이 변환하는 과정이 필요하다.
import React from 'react';
// 변환 전
const Button = () => {
return <button>Click</button>;
}
// 변환 후
const Button = () => {
return React.creataeElement('button', null, 'Click');
}
그렇다.
JSX는 그저 개발자가 코드를 읽기 편하게 하기 위해 존재하는 것으로,
내부적으로는 React에서 제공하는 함수에 불과하다.
React에서는 Component들을 여러 개로 쪼개라 한다.
이는 재사용을 가능케 하여 반복적인 작업을 줄일 수 있게 해주고,
관심사를 분리하여 유지보수를 쉽게 해주기 때문이다.
하나의 파일에 모든 html, css, js 코드를 모두 넣는다면
오류가 생겼거나 수정할 부분을 찾기 매우 어렵겠지만,
React로 인해 각각의 단위는 작은 파일, Component로 용도에 따라 분리되어
문제가 생긴 부분이나 수정할 부분을 찾기 쉽게 해주는 것이다.
이는 전혀 새로운 개념이 아닌 그저 함수를 작은 단위로 나누는 것과 비슷한 작업이다.
이렇게 함수를 작은 단위로 쪼개는 이유는 무엇일까
// 간결한 Golang의 Function
func Valid(data []byte) bool {
scan := newScanner() // 1. 새로운 스캐너를 만든다
defer freeScanner(scan) // 2. 함수가 끝난 뒤 스캐너가 할당된 메모리를 비운다.
return checkValid(data, scan) == nil // 3. data가 Valid한지 체크 후 bool 결과 반환
// defer 키워드가 붙은 함수가 실행되는 위치
}
예를 들어, Google에서 개발한 Go언어의 Standard Library를 살펴보면,
대부분의 함수가 매우 짧게 쪼개져 있는 것을 볼 수 있다.
Valid()
함수의 내부에서 함수의 호출이 3번 일어나는데,
newScanner()
함수는 이름 그대로 새로운 Scanner
Value(instance)를 만든다.
freeScanner()
함수는 Valid()
함수가 종료될 때 Scanner를 메모리 상에서 제거한다.
(defer
로 선언된 함수는 return
이후 실행됨)
checkValid()
함수는 data에 문제가 없을 경우 nil
(null
)을 반환한다.
(문제가 있을 경우에는 nil
이 아닌 error
반환)
이렇게 함수를 쪼개서 한가지 작업만 담당하도록 하면,
각 함수의 의미를 이름만 보고 바로 파악할 수 있다.
그리고 여러 곳에서 재사용 할 수 있다는 장점도 있다.
이것이 무슨 뜻이냐면,
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
let res = add(1, 2); // 1 + 2 = 3
let res2 = multiply(res, res); // 3 * 3 = 6
위의 add()
함수는 두 변수의 덧셈이 필요하다면 어떤 곳에서든 사용될 수 있다.
덧셈이라는 한가지 기능에만 집중하였기 때문이다.
하지만 2번의 함수 호출이 필요해진다.
그래서 함수 호출을 1번만 하고 싶어서 아래와 같이 함수를 재작성 한다면?
function addAndMultiply(a, b) {
return (a + b) * (a + b);
}
let res = addAndMultiply(1, 2);
함수 호출 1번으로 add()
, multiply()
를 2번 호출할 것을 단축시켰다.
그러나 이 코드는 두 수를 더한 결과를 다시 곱한다는 특수한 경우가 아니면 사용할 수 없다.
코드와 호출 횟수는 단축 되었지만 첫번째 함수에 비해서 재사용성이 확연히 떨어지게 된 것이다.
게다가 코드가 기능에 따라 분리가 되지 않은 채, 수백~수천 줄로 길어지게 된다면,
오류가 어디에서 발생 했는지 찾기가 매우 힘들어진다.
function addAndManyThings(a, b) {
// ... over 10000 lines
return res;
}
그러나 add()
, multiply()
등의 1가지 기능만 담당하는 작은 단위로 나눈다면,
덧셈 함수에 오류가 있었다면 add()
함수에서 오류가,
곱셈 함수에 오류가 있었다면 multiply()
함수에서 오류가 났다는 것을
쉽게 알 수 있을 것이다.
(물론 논리적인 오류는 예외, 이는 테스트 등을 통해 해결)
이와 비슷한 원리로, 이러한 이점을 얻기 위해 React의 Component를 잘게 쪼개는 것이다.
좋은 도구를 사용하는 것도 중요하지만,
해당 도구가 가진 철학과 이점을 잘 이해하는 것도 중요하다고 생각하여 해당 내용을 정리하였다.
$(".root")
.append("<button class="btn1">Click</button>")
.append("<button class="btn2">Click</button>")
.append("<button class="btn3">Click</button>")
.attr("class", "root");
const App = () => {
return (
<div className="root">
<button className="btn1">Click</button>
<button className="btn2">Click</button>
<button className="btn3">Click</button>
</div>
);
}
React는 Declarative, 선언형 방식을 사용하며
jQuery는 Imperative, 명령형 방식을 사용한다고 한다.
각 방식의 이름만 봐서는 어떤 차이가 있는지 잘 와닿지 않는다.
이해를 돕기 위해 풀어서 이야기 해보면,
jQuery의 명령형 방식은 단계별로 각 절차를 수행하는 것과 같다.
먼저 .root
요소를 선택하고,
그 아래에 button
을 추가하고
그 아래에 button
을 추가하고
그 아래에 button
을 추가하고
.root
의 class를 변경하면 끝이 난다.
이는 마치 그림을 잘 그리는 사람이 무언가를 그리는 것을 옆에서 지켜보는 것과 비슷하다.
처음에는 무엇을 그리는지 파악하기 어렵지만,
부분 부분을 그려나가다 보면 형태가 보이기 시작한다.
React의 선언형 방식은 마치 사진을 찍는 것과 같다.
스마트폰 카메라로 촬영 버튼을 누르면, 그 즉시 내가 보고 있던 장면이 그대로 사진 속에 담긴다.
jQuery의 코드만 봐서는 완성 되었을 때의 HTML 코드를 한눈에 파악하는 것이 어렵지만,
React Component의 코드를 살펴보면 완성된 HTML 코드가 쉽게 예상된다.
const Button = () => {
return (
<button>
<Text txt="Text1"/>
</button>
);
}
const Text = (props) => {
return <h1>{props.txt}</h1>; // "Text1"
}
React의 Component는 내부에 또 다른 Component를 가질 수 있다.
마치 함수 내부에서 다른 함수를 실행시키는 것과 비슷한데,
이런 경우 props라는 속성을 통해 데이터를 전달할 수 있다.
// HTML
<button type="button">Click</button> // type is an attribute of button tag
HTML 태그에서는 각 속성을 attribute라고 부르지만
JSX에서는 props라고 부른다.
// JSX
<Text txt="Text1" /> // txt is a prop of Text Component
const Text = (props) => { // "Text1"은 props.txt로 전달됨
return <h1>{props.txt}</h1>; // "Text1"
}
Text에 txt=”Text1”의 형태로 전달된 속성들은
props라는 객체에 이름 그대로 담기게 되어, props.txt에 접근하여 받을 수 있다.
props를 통한 전달은 상위 Component → 하위 Component 만 가능하며
하위 Component → 상위 Component 로 역전달은 불가능하다.
const Parent = () => {
return <Child>Text</Child>
}
const Child = (props) => { // props.children == "Text"
return <div>{props.children}</div> // <div>Text</div>
}
props.children
은 예약어로,
위 코드에서는 Parent
Component에서 <Child>Text</Child>
을 하위 Component로 포함시켰는데,
Opening Tag, Closing Tag 사이의 Text가 props.children
으로 전달된다.
그래서 Child
Component에서 props.children
을 div 태그 사이에 주면,
실제로 렌더링 되는 결과는 <div>Text</div>
가 되는 것이다.
위에서 봤듯이, Component는 다른 Components를 포함할 수 있다.
그러나 최상위 Component는 단 하나이다.
React의 시작 지점은 보통 index.js 파일이며,
해당 파일에서 ReactDOM.render()
함수에 최상위 Component를 인자로 넘겨준다.
최상위 Component의 이름은 보통 <App />
으로 지정하며,
최상위 Component 밑에 모든 하위 Components가 존재한다.
이로 인해 필연적으로 트리 구조를 갖게 되며, 이 구조를 Component Tree라고 부른다.