나에게는 아주 안 좋은 습관이 있다.
덧셈 뺄셈을 배우던 초등학생 시절부터 거의 병적으로 손으로 필기하기를 싫어했다.
필기가 필수인 대학 수업 때도 PPT 제공 없는 교수님의 수업까지도 필사적으로 필기 없이 공부하기 위해 노력했다.
정말 피치 못해 필기를 해야 할 때에도 단어 위주로, 심지어 단축말... 까지 만들어가며 적어야 하는 양을 최소한으로 줄였다.
십여 년간 쌓아온 나름의 공부 노하우와 이해 방법이 있었기에 공부의 목적이 시험뿐이었던 지금까지는 이 방법이 잘 먹혔으나, 취업 시장에 들어서고 난 후로는 내가 공부한 내용을 남에게 !!!보여줄!!! 방법이 필요함을 절실히 깨닫게 되었다.
특히나 개발 공부를 시작하고 난 뒤로 여기저기서 개발자에게는 블로그와 깃허브가 전부라는 말을 많이 들었기 때문에 큰 맘먹고 블로그를 시작한다!
일주일 안에 리액트 정복하기
나는 현재 제로베이스의 프런트엔드 과정을 수강 중이고, 2주 전 옥소폴리틱스 사의 인턴십에 합격하여 활동 중이다. (여유가 있다면 이 경험에 관련하여 짧게라도 글을 써보고 싶지만 가능할지는 모르겠다.)
문제는 인턴십 활동에 필요한 최소 기술 스택이 React였으나, 난 이제야 수강 2달 차에 접어들었기 때문에 HTML, CSS, Vanila JS가 내 기술 스택의 전부였다는 것이다. 인턴십 활동 중인 옥소폴리틱스(이하 옥소)에서는 나와 팀원들에게 1 주일 동안 React 공부 및 클론 코딩 미션을 주었다. 팀원들은 나보다 한 기수 앞서 프런트엔드를 수강 중이었기에 어느 정도 React 지식이 있었으나, 나는 말 그대로 Zero 베이스로 리액트 공부를 시작하게 되었다.
일주일이면 React를 정복할 수 있을까
리액트 공부는 제로베이스의 리액트 과정을 수강하였다. 강의 과정 자체가 React의 공식 문서(document)를 베이스로 진행하였기 때문에 공부 내용 정리 역시 해당 문서의 목차를 참고하였다.😁
React란 무엇인가?
React란 사용자 인터페이스를 구축하기 위한 선언적이고 효율적이며 유연한 Javascript 라이브러리입니다. - React 공식 문서
React는 jQuery를 통한 직접적인 DOM 요소를 다루던 기존의 웹개발 방식에 컴포넌트 기반 개발이라는 새로운 방법론을 제시했다. 숙련된 개발자라면 jQuery로도 선언적인 프로그래밍이 가능했으나 비숙련자들은 조금만 코드가 길어지더라도 명령형으로 코드를 짜게 되었고, 이는 유지보수 및 에러 관리를 어렵게 만들었다. 또한 빠르게 변화하는 다양한 데이터를 받아 반응형 UI로 바로 랜더링해 보여주는 현대적인 홈페이지를 제작하기 위해 jQuery는 DOM요소를 직접 찾아 조작해야 했기에 코드가 길어지는 것은 물론, 이로 인한 불필요한 시간 낭비가 많았다. (사실 반응형 UI 기반의 홈페이지가 React, Angular 등 프레임워크들의 등장 덕분에 대세가 된 것이기도 하다.)
페이스북(현 메타)사에서 아주 주도적으로 개발 및 유지보수하고 있어 스폰서는 빵빵하다고 볼 수 있으며, 자바스크립트 언어를 기초로 동작하기 때문에 자바스크립트 커뮤니티를 거의 그대로 이어받을 수 있다. 또한 React Native를 이용한 모바일 앱 개발 등 수많은 추가 라이브러리가 이용 가능하다.
Virtual DOM
Virtual DOM (VDOM)은 UI의 이상적인 또는 “가상”적인 표현을 메모리에 저장하고 ReactDOM과 같은 라이브러리에 의해 “실제” DOM과 동기화하는 프로그래밍 개념입니다. 이 과정을 재조정이라고 합니다.
이 접근방식이 React의 선언적 API를 가능하게 합니다. React에게 원하는 UI의 상태를 알려주면 DOM이 그 상태와 일치하도록 합니다. 이러한 방식은 앱 구축에 사용해야 하는 어트리뷰트 조작, 이벤트 처리, 수동 DOM 업데이트를 추상화합니다.
“Virtual DOM”은 특정 기술이라기보다는 패턴에 가깝기 때문에 사람들마다 의미하는 바가 다릅니다. React의 세계에서 “Virtual DOM”이라는 용어는 보통 사용자 인터페이스를 나타내는 객체이기 때문에 React elements와 연관됩니다. 그러나 React는 컴포넌트 트리에 대한 추가 정보를 포함하기 위해 “fibers”라는 내부 객체를 사용합니다. 또한 React에서 “virtual DOM” 구현의 일부로 간주할 수 있습니다. - React Document
위 문서의 내용을 조금 더 내가 이해한 대로 풀어보자면, Virtual DOM은 브라우저 위에서 동작되는 어떤 기술이 구현된 것이 아니다. 리액트 사용자가 어떤 UI를 업데이트하여 랜더링 하겠다고 리액트에 선언하면 버추얼 돔은 해당 UI를 분석하고 노드(텍스트 노드, 엘리먼트 노드 등)를 잘게 쪼개어 변화하는 노드만을 분리하고, 변화된 부분만 선택하여 해당 부분만 업데이트한다. 이는 효율적인 랜더링을 가능하게 하고 선언적 프로그래밍을 가능하게 한다.
JSX
const element = <h1>Hello, World!</h1>;
리액트에서는 JSX라는 문법을 사용해 자바스크립트에서 HTML 태그를 사용할 수 있다. 과거 XML이라는 템플릿 언어에서 비슷한 방식이 가능했다고 하는데 이에 대해서는 나중에 교양 목적으로 공부해볼 생각이다.
JSX는 표현식처럼 작동한다. 즉 JSX를 if 구문 및 for 루프 등에 변수나 인자로서 받아들이고 return할 수 있다.
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
JSX 내부에서 문자열 등 JS 표현식을 삽입하기 위해서는 중괄호{}를 사용해야 하며, 중괄호 안에서는 표현식만 사용이 가능하다. 즉, 삼항 연산자를 사용할 수 있다! 또한 HTML 태그와 거의 유사하게 어트리뷰트를 정의하고 삽입할 수도 있다. JSX는 JavaScript에 가깝기 때문에 camelCase 명명 규칙을 사용해야 한다. class는 className이 되고, tabindex는 tabIndex가 된다.
JSX의 작동 원리는 다음과 같다.
//React with JSX
return <div className='shopping-list'>{/* ... h1 children ... */}</div>
//React without JSX
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* ... h1 children ... */)
);
간혹 JSX가 리액트의 기능이라고 생각하는 개발자들이 있다. 하지만 JSX는 기본적으로 React의 createElement 함수를 대체하여 사용이 가능한 문법적 설탕(Syntactic sugar) 일뿐이다. 이 JSX 덕분에 컴포넌트 기반의 프로그래밍을 아주 편리하게 구현할 수 있기 때문에 리액트 공식문서에서는 JSX를 사용할 것을 권장하고 있다.
함수 컴포넌트
아래와 같이 함수를 만들면 간단하게 컴포넌트를 정의하고 사용할 수 있다.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
//아래와 같이 바로 랜더링이 가능하다!
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
* 컴포넌트의 이름은 항상 대문자로 시작해야 한다!
컴포넌트는 HTML 태그처럼 자식 요소를 얼마든지 가질 수 있으며, 선언적 프로그래밍을 지향하기 때문에 기능의 최소 단위로 컴포넌트를 잘게 쪼개기를 적극 권장하고 있다.
리액트에서는 상속 등을 사용할 수 있는 class 컴포넌트 역시 지원하고 있지만 최신 React 문법에서는 이를 권장하지 않고 있다.
Props
JSX에서 어트리뷰트 형태 또는 자식 요소의 형태로 전달된 값들은 'props'라는 객체에 담겨 전달된다. 어트리뷰트 형태로 전달된 value는 해당 어트리뷰트의 key 값이 그대로 사용되고 자식요소의 경우에는 props.children을 사용하여 접근이 가능하다. props는 읽기 전용이다! 즉, 순수 함수처럼 동작해야 한다.
*순수 함수: 입력값을 직접 변경하지 않는 함수. 같은 입력값을 받는다면 항상 같은 출력값만 나와야 한다.
State와 생명주기
만약 시계같이 시간에 따라 변화하는 UI를 만들고 싶다면, 변화하는 데이터에 맞춰 UI를 업데이트하여 렌더링 해주어야 한다. 이를 위해 React에서는 props와 유사한 'state'라는 객체를 사용한다.
아래의 내용은 class 기반의 React 프로그래밍에 관련한 내용이다. 최신 React에서는 함수형 프로그래밍을 지원하는 강력한 Hooks를 제공하고 있다.
생명주기
수많은 컴포넌트가 있는 애플리케이션에서 컴포넌트가 삭제될 때(사용되지 않을 때) 해당 컴포넌트가 사용 중인 리소스를 확보하는 것이 중요하다. 리액트에서는 컴포넌트가 처음 DOM에 랜더링 되어 작동을 시작하는 것을 "마운팅"이라고 하며, 해당 DOM이 삭제되어 작동을 중지하는 것을 "언마운팅"이라고 부른다. 이를 리액트 컴포넌트 클래스에서는 컴포넌트가 "마운트" 되거나 "언마운트" 되었을 때 특별한 매서드를 선언하여 사용할 수 있다.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
}
componentWillUnmount() {
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
이러한 매서드들을 생명주기 매서드라고 부른다.
componentDidMount() 메서드는 컴포넌트 출력물이 DOM에 렌더링 된 후에 실행된다. 즉 마운트 되었을 때 1번만 실행되는 함수이다.
componentDidMount() {
this.timerID = setInterval( //타이머 설정
() => this.tick(),
1000
);
}
componentWillUnmount() 메서드는 언마운트 될 때 실행되는 함수이다.
componentWillUnmount() {
clearInterval(this.timerID); //타이머 해제
}
setState() 매서드는 state를 변경할 때 사용하며, React에 state가 변경되었음을 알려준다. 이 호출 덕분에 React는 자동으로 render() 메서드를 다시 호출한다. 이때 render() 메서드는 변경된 사항을 인식하여 해당 부분만 부분적으로 렌더링 한다.
tick() {
this.setState({
date: new Date()
});
}
주의사항
- state를 직접 수정해서는 안 된다. setState()를 호출해야만 컴포넌트가 rerendering 된다.
- state 객체이다! state는 얕은 참조만 하기 때문에 반드시 새로운 객체를 선언하여 전달해주어야 한다!!
* 얕은 참조: 배열이나 객체 등 포인터 값을 value로 갖는 인자를 참조할 때 배열 등의 내부의 값이 아닌 해당 포인터 값만을 확인하는 것 - state 업데이트는 비동기적일 수 있다. 리액트는 성능을 위해 여러 setState() 업데이트를 병합하여 한번에 처리할 수 있다. 때문에 다음 state를 계산할 때 현재의 state를 참조하면 안 된다.
const [number, setNumber] = useSate(1);
const add = () => setNumber(number + 1);
const multiplyBy2 = () => setNumber(number * 2);
const multiplyBy2AndAddBy1 = () => {
multiplyBy2();
add();
}
add(); //return 1
multiplyBy2(); //return 2
multiplyBy2AndAddBy1(); //return 3(띠용??? 2*2+1=5가 출력되지 않음!)
위와 같은 상황에서 multiplyBy2AndAddBy1 함수는 2*2+1=5를 정상적으로 출력하지 못하고 2+1=3을 출력한다. 이는 state 업데이트가 병합되기 때문에 발생하는 일이다. multiplyBy2() 함수는 add() 함수의 setState와 병합되어 처리되기 때문에 number는 {number, number: number * 2, number: number + 1}의 연산이 동시에 작동되고 이는 단순히 덮어쓰기가 되기 때문에 최종적으로 number는 (number + 1)의 값만 갖게 된다.
이럴 경우 setState에 콜백 함수를 전달하여 해결이 가능하다!
이벤트 처리하기
React 엘리먼트에서 이벤트를 처리하는 방식은 DOM 엘리먼트에서 이벤트를 처리하는 방식과 매우 유사하다! 차이점은 HTML처럼 대시('-')와 소문자를 이용하는 대신 캐멀 케이스(camelCase)를 사용하고, JSX에서 문자열이 아닌 함수로 Event Handler를 전달한다는 점이다. 또한 React에서는 addEventListener를 따로 호출할 필요가 없다.
<!-- HTML DOM -->
<button onclick="activateLasers()">
Activate Lasers
</button>
//JSX + React
<button onClick={activateLasers}>
Activate Lasers
</button>
폼 엘리먼트 등의 기본 동작을 방지하기 위해서는 반드시 이벤트 핸들러 함수 내부에 preventDefault()를 명시적으로 호출해야 한다.
조건부 렌더링
삼항 연산자를 이용하면 if 구문 없이도 JSX 내부적으로 조건부 렌더링을 구현할 수 있다.
render() {
return (
<div>
<div>The user is <b>{isLoggedIn ? "currently" : "not"}</b> logged in.</div>
<div>{isLoading ? <Loading /> : null}</div>
</div>
);
}
map을 이용한 렌더링
배열의 map() 함수를 이용하여 배열을 엘리먼트로 만들 수 있다. 이는 표현식이므로 JSX의 중괄호{} 안에서 즉시 실행이 가능하다!
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
map을 사용할 경우 고유한 값인 key를 설정해주어야 한다. 이때 key는 prop으로 전달되지 않는다.
인라인 방식이 깔끔하기는 하지만 가독성을 위해 어떤 방식이 더 좋을지는 개발자가 판단해야 한다. 일반적으로 map을 너무 남발하는 것은 좋지 않고 map() 함수가 너무 중첩된다면 별도의 컴포넌트로 추출하는 것이 좋다.
아직 일주일이 지나지 않았다! (2)에서 계속된다!
'Dev > JS Family, HTML, CSS' 카테고리의 다른 글
[TypeScript] 타입스크립트 입문 (4) - 제네릭, 고급 타입 다루기 (0) | 2022.09.14 |
---|---|
[TypeScript] 타입스크립트 입문(3) - 인터페이스, 타입 가드, 열거형, 유니온 타입 (0) | 2022.09.12 |
[TypeScript] 타입스크립트 입문 (2) - 기본 타입, 클래스 (0) | 2022.09.09 |
[TypeScript] 타입스크립트 입문(1) (2) | 2022.09.04 |
[React] 일주일만에 리액트 정복하기! (2) (0) | 2022.08.03 |