프론트 엔드의 꽃이라고도 불리는 React
React 에 대해 알아보기로 하자
먼저 엘리먼트는 React 앱의 가장 작은 단위로 화면에 표시할 내용을 기술한다.
DOM에 엘리먼트 렌더링 하기 위해서는 HTML 파일 어딘가에 <div>가 있다고 가정해 보면
<div id="root"></div> 로
이 안에 들어가는 모든 엘리먼트를 React DOM에서 관리하기 때문에 이것을 “루트(root)” DOM 노드라고 부른다.
React로 구현된 애플리케이션은 일반적으로 하나의 루트 DOM 노드가 있다. React를 기존 앱에 통합하려는 경우 원하는 만큼 많은 수의 독립된 루트 DOM 노드가 있을 수 있다.
React 엘리먼트를 루트 DOM 노드에 렌더링 하려면 둘 다 ReactDOM.render()로 전달하면 된다.
React에서는 JSX라는 문법을 사용하게 된다.
우선 JSX 문법을 도입하게 된 이유가 무엇이고 어떤 장점이 있을까?
이 질문의 답변은 JSX를 사용하게 되면 코딩의 복잡도가 줄어들고 가독성이 좋아지게 된다.
그렇다면 JSX에 JavaScript 표현식을 쓰려면 어떤 방법으로 써야 할까?
아래와 같이 중괄호로 감싸 JSX 안에 사용하는 방법이 있다.
1
2
|
const name = 'ikseung';
const element = <h1>Hello, {name}</h1>;
|
cs |
jsx에서는 몇 가지 주의사항이 있는데 if/else 문이 우리가 생각하던 js의 문법과는 약간의 차이가 있다는 것이다.
그렇기에 조건부 렌더링을 구현할 때에는 if / else 구문은 지양하고, ternary operator 혹은 %% 구문, 삼항 연사자를 사용하는 것이 좋다.
그리고 es6문법의 애로우 펑션 사용 시 return 생략 관련 문제라던지 (중괄호 사용 시에는 return 사용), map을 사용할 때는 고유한 키를 부여하여야 한다는 것(디폴트 값으로 고유키를 부여해주는 기능이 있기는 하지만 부여해주어야 한다는 것을 잊지 말 것)이다. 비슷한 forEach를 사용하지 못하는 것은 리턴을 하지 못한다는 차이점으로 이해하면 될 것이다.
import / export 구문은 어떤 식으로 사용할 수 있을까?
import는 말 그대로 수입할 부분, export는 수출할 부분이라고 사전적인 의미로 이해하면 될 것이다.
1
2
3
4
5
6
7
8
9
10
|
import React from "react";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
|
cs |
React 환경에서 개발하기 위해서는 제일 먼저
$ npx create-react-app my-app 명령어를 통해 react를 설치해준 후
해당 디렉터리에 접속에 npm start를 해주면 local:3000 포트의 react가 구동되는 것을 확인할 수 있다.
앞서 말한 엘리먼트와는 혼동을 하지 말아야 할 컴포넌트라는 것이 있는데
컴포넌트는 하나의 작은 태그로 커스텀 태그라고도 불린다.
컴포넌트의 종류는 함수 컴포넌트와 클래스 컴포넌트가 있다.
함수 컴포넌트
기존의 js에서 함수를 작성하는 것처럼 하면 된다.
물론 return에 jsx 엘리먼트는 넣어주어야 한다. 아래에서 보는 것 그대로다.
1
2
3
4
|
function Tweet(props) {
return <div>tweet component</div>;
}
|
cs |
그렇게 하고 <Tweet></Tweet> 태그를 사용하면 하면 리턴해주었던 엘리먼트인 tweet component가 렌더 되는 것을 볼 수 있다. 아 그리고 유의할 것이 하나 있는데 함수 명을 정할 때는 앞에 대문자를 꼭 사용해야 한다는 것이다. 이 것은 꼭 잊지 말자. 우리는 이 <Tweet> 태그에 name이나 content 등의 데이터를 담아 둘 수도 있다.
1
|
const element = <Tweet name="Sara" />;
|
cs |
여기서 props라는 것(속성을 나타내는 데이터)이(가) 존재하는데 이는 쉽게 변수라고 생각하면 되고 위의 Tweet 태그에서는 name, content props라고 보면 된다.
그리고 중괄호로 jsx 표현식을 이용하여 props를 아래와 같이 가져와 사용하여 렌더 해줄 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function Tweet(props) {
return (
<div>
<div>작성자 : {props.name}</div>
<div>트윗내용 : {props.content}</div>
</div>
) }
//ES6 문법으로 바꿔 준다면 아래 코드는 위와 결과가 동일하다.
function Tweet({name,content}) {
return (
<div>
<div>작성자 : {name}</div>
<div>트윗내용 : {content}</div>
</div>
) }
|
cs |
클래스 컴포넌트
1
2
3
4
5
6
|
class Tweet extends React.Component {
render() {
return <div>tweet component</div>;
}
}
|
cs |
함수 컴포넌트와의 차이점은 class를 사용한다는 것과 render를 사용해주어야 한다는 점인 것 같다.
앞서 언급되었던 props는 읽기 전용이라는 특성을 가지고 있다.
그렇기에 함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트의 자체 props를 수정해서는 안 되는 것이다.
그리고 모든 React 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야 하는데 여기서 말하는 순수 함수란 아래와 같이 입력값을 바꾸려 하지 않고 항상 동일한 입력값에 대해 동일한 결과를 반환하는 것을 뜻한다.
1
2
3
|
function sum(a, b) {
return a + b; // account.total -= amount;는 순수 함수가 아니다.
}
|
cs |
또한 컴포넌트들은 조건부 렌더링을 통해 주어진 조건에 맞는 컴포넌트들을 렌더링 할 수 있게도 구현 가능하다. 기존의 js처럼 if / else 문을 사용하거나(사용하는 것은 문제없으나 우리가 생각하는 결과를 가지고 오지 못할 수도 있다.) 삼항 연산자(condition? true: false)로도 구현 가능하며 논리 연산자 또한 가능하다. 아까 위에서 조건부 렌더링에서 언급한 부분과 같다.
그렇다면 이러한 컴포넌트 단위로 개발할 때의 장점에는 어떠한 것들이 있을까?
컴포넌트의 개념을 자세히 짚어보면 답은 나온다.
UI를 구성하는 개별적인 view 단위로 전체적인 앱은 각 컴포넌트들의 조합을 통해 만들어지기에 이는 모듈화를 의미한다고 생각하고 그렇기에 유지보수 적인 측면의 장점이 클 것이라고 생각한다.
React에서는 리스트를 어떻게 반환할 수 있을까?
앞서 위에서 언급하였지만 map을 사용하면 된다. 배열과 map을 이용하여 리스트를 반환하는 것은 같으나 key를 사용한다는 것이 다른 점이다. “key”는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트이며 map에서 리스트의 각 항목에 key를 할당해주면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key = {number.toString()}> {number} </li> //키 할당
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
|
cs |
또한 Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다. key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 한다.
Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이며 대부분의 경우 데이터의 ID를 key로 사용한다.
1
2
3
4
5
|
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
|
cs |
그리고 key는 map()map() 함수 내부에 있는 엘리먼트에 key를 넣어 주는 게 좋다. Key는 배열 안에서 형제 사이에서 고유해야 하고 전체 범위에서 고유할 필요는 없다. 두 개의 다른 배열을 만들 때 동일한 key를 사용할 수 있다.
앞서 언급되었었던 props와 state를 간단히 알아보자면
props는 외부에서 전달받는 값이고
state는 내부(컴포넌트 안)에서 변화하는 값이다.
state를 가지기 위해서는 함수 컴포넌트가 아닌 클래스 컴포넌트를 사용하여야 한다.
state는 props와 유사하지만, 비공개이며 컴포넌트에 의해 완전히 제어된다.
아래는 전반적인 react를 사용한 토글스위치 구현을 한 코드이다.
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
|
import React from 'react';
import './styles.css';
function App() {
return (
<div className="App">
<h2>talk about State</h2>
<ToggleSwitch /> //클래스 컴포넌트가 렌더링 되는지 확인
</div>
)
}
class ToggleSwitch extends React.Component { //ES6 문법
constructor(props) { //컨스트럭터 생성(상속받는 부분 props)
super(props) //상속받기위해 super 키워드 사용 ->초기화
this.state = { isOn: false }; //기본값(객체) this.state를 지정할 수 있는 유일한 공간은 바로 constructor
this.handleClick = this.handleClick.bind(this);//클릭 이벤트시 입력해줘야함(constructor안에)
} //<-- 콜백에서 `this`가 작동하려면 바인딩 해주어야 다.
handleClick() { //this.state가 false면 true로 바꿔주고 true이면 false
this.setState(state => ({ //위와 같은 경우처럼 상태를 변경하기 위해선 setState사용(이전 state를 반전)
isOn: !state.isOn
}))
}
render() { //여기서 handleClick은 위의 메소드
return <h1><button onClick={this.handleClick}>{this.state.isOn ? 'ON' : 'OFF'}</button></h1> //버튼구현
} //클릭시 바뀌는 것이기에 이벤트사용(onClick) //위의 state상태를 반영... isOn:false이기 때문에 현재 OFF 렌더 ... true 일시 ON렌더...
}
|
cs |
state 업데이트는 비동기적일 수도 있다.
그렇기에 this.props와 this.state가 비동기적으로 업데이트될 수 있기 때문에 다음 state를 계산할 때 해당 값에 의존해서는 안 된다. 객체보다는 함수를 인자로 사용하는 다른 형태의 setState()를 사용한다.
그리고 state는 다양한 독립적인 변수를 포함할 수 있다.
1
2
3
4
5
6
7
|
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
|
cs |
또한 앞서 말한 별도의 setState() 호출로 이러한 변수를 독립적으로 업데이트할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
|
cs |
react는 단방향 데이터 흐름이며 이는 데이터는 위에서 아래로 단방향으로 흐른다는 것을 의미한다.
이렇게 아래로 흐르는 데이터를 전달하는 방식은 props를 이용한다.
같은 레벨의(형제) 컴포넌트끼리는 상호작용을 할 수 없다.
그렇기에 같은 레벨의(형제) 컴포넌트끼리 상태를 공유하기 위해서는 공통의 부모에서 상태를 다루어야 한다.
컴포넌트 클래스에서 특별한 메서드를 선언하여 컴포넌트가 마운트 되거나 언마운트 될 때 일부 코드를 작동할 수 있다. 이러한 메서드들은 “생명주기 메서드”라고 불리며
componentDidMount() 메서드는 컴포넌트 출력물이 DOM에 렌더링 된 후에 실행
componentWillUnmount() 메서드는 화면에서 사라지기 (unmount) 후 (제거) => componentWillUnmount 실행된다.
이 두 가지 메서드 말고도 존재하는데 이는 추후에 알아보도록 하자
'개념정리' 카테고리의 다른 글
1/6[TIL]React(3) (0) | 2021.01.06 |
---|---|
1/5[TIL]React(2) (0) | 2021.01.05 |
12/30[TIL] Server & Node (0) | 2020.12.30 |
12/15[TIL]Deploy (0) | 2020.12.15 |
12/11[TIL]Authentication(3) (0) | 2020.12.11 |