React 21.05.06
React : 프론트엔드 라이브러리(프레임워크). *컴포넌트 단위로 개발 가능.
*컴포넌트 : 하나의 의미를 가진 독립적인 모듈. 재사용성이 높아지고 코드를 직관적으로 사용 가능
React는 처음부터 점진적으로 도입할 수 있게 설계. 그래서 React는 필요한 만큼만 사용 가능. 아마 기존 페이지에 “상호작용”이라는 조미료를 첨가하기만 하고 싶을 수도 있다. 이럴 때 React 컴포넌트를 쓰면 좋다.
npx create-react-app [리액트 설치할 폴더명]
cd [리액트 설치 한 폴더 명]
npm start
https://create-react-app.dev/docs/getting-started/
[웹 사이트 개발 방법] 1.HTML+CSS+JS : 관리해야 할 DOM이 많아질 수 록 상태관리가 어렵다.
<div class='tweet'>
<span class='userId'> @Terry </span>
<div class='msg'> Hello, React! </div>
<div class='time'> 7h ago </div>
</div>
2.React : 컴포넌트 단위로 개발 가능. 기능 개발에 집중 할 수 있게 해준다.
// 1번 코드를 React로 작성
<Tweet userId='terry' time='7h'>
Hello, React!
</Tweet>
// 직관성과 재사용성이 올라간다!
React를 시작하기전에 알아야 할 것
ES6
Destructuring(구조 분해)
Spread Operator & Rest parameters(…)
default parameters
template literals
arrow function (=>)
for-of loop (Array loop)
JSX
React에서는 이벤트가 처리되는 방식, 시간에 따라 state가 변하는 방식, 화면에 표시하기 위해 데이터가 준비되는 방식 등 렌더링 로직이 본질적으로 다른 UI 로직과 연결된다.
JSX는 자바스크립트 코드 안에서 UI 관련 작업을 할 때 시각적으로 더 도움이 된다. 또한 에러 및 경고 메시지를 표시할 수 있게 해준다.
자바스크립트 확장 문법
복잡도를 낮추고 코드의 가독성을 높인다.
JSX는 React “엘리먼트(element)” 를 생성
[JSX 규칙]
1.반드시 하나의 엘리먼트로 감싼다.
- 틀린 예시
<div>
<h1> Hello </h1>
</div>
<div>
<h2> JSX </h2>
</div>
// 엘리먼트 블럭 2개
- 옳바른 예시
<div>
<div>
<h1> Hello </h1>
</div>
<div>
<h2> JSX </h2>
</div>
</div>
// 엘리먼트 블록 1개
2.자바스크립트 코드를 적용할 땐 { } 중괄호 안에서 작성
class App extends Component {
render() {
const name = 'React'
return (
<div>
hello {name} !
</div>
)
}
}
3.JSX 내부 ({} JSX의 중괄호 내부) 에서는 if문을 사용 할 수 없다. 삼항연산자를 사용해라!
<div>
{
(1 + 1 ===2 )?(<h1>정답</h1>):(<h1>오답</h1>)
}
</div>
4.엘리먼트의 클래스 이름을 적용할 때 class가 아닌 className 사용 (ES6 문법 중 class가 있기 때문에 구분하기 위해 className을 사용)
- 틀린 예시
<div class='app-container'> hello </div>
- 옳은 예시
<div className='app-container'> hello </div>
JSX x React : Ex code
JSX 문법을 도입하게 된 이유가 무엇일까요? 어떤 장점이 있나요? 코드의 복잡도를 낮추고 가독성을 높인다.
JSX를 사용하지 않고 React를 사용할 수도 있나요? (advanced) Yes
JSX에 JavaScript 표현식을 쓰려면 어떤 방법으로 써야 하나요? {} 안에 자바스크립트 표현식 입력
JSX에 표현식 포함하기
const name = 'Josh Perez';
// JSX의 중괄호 안에는 자바스크립트 코드를 넣을 수 있다.
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
// 자동 세미콜론을 피하고 가독성을 위해 괄호로 묶는 것을 권장.
const element = (
<h1>
// 함수의 실행 값을 넣을 수 있다.
Hello, {formatName(user)}!
</h1>
);
ReactDOM.render(
element,
document.getElementById('root')
);
function hi(user) {
if(user) {
return <h1>Hi, {formatName(user)}!</h1>
}
return <h1>Hi, Stranger.</h1>
}
JSX 속성(Property) 정의
// 문자열로 속성 지정
const element = <div tabIndex="0"></div>
// 중괄호를 사용하여 어트리뷰트에 JS 표현식 삽입 가능
const element = <img src={user.avatarUrl}></img>
*[주의 사항]
따옴표(문자열 값에 사용) 또는 중괄호(표현식에 사용) 중 하나만 사용하고, 동일한 어트리뷰트에 두 가지를 동시에 사용하면 안됨.
camelCase 사용
JSX로 자식 정의
// 태그가 비어있다면 XML처럼 /> 를 이용해 바로 닫아주기
const element = <img src={user.avatarUrl} />;
// JSX 태그는 자식을 포함 가능
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
JSX 주입 공격(XSS) 방지
기본적으로 React DOM은 JSX에 삽입된 모든 값을 렌더링하기 전에 이스케이프 하므로, 애플리케이션에서 명시적으로 작성되지 않은 내용은 주입되지 않습니다. 모든 항목은 렌더링 되기 전에 문자열로 변환됩니다. 이런 특성으로 인해 XSS (cross-site-scripting) 공격을 방지 가능
const title = response.potentiallyMaliciousInput;
// 이것은 안전합니다.
const element = <h1>{title}</h1>;
JSX는 객체 표현
Babel은 JSX를 React.createElement() 호출로 컴파일
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
// 위 코드를 아래와 같이도 표현 간으
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement()는 버그가 없는 코드를 작성하는 데 도움이 되도록
몇 가지 검사를 수행하며, 기본적으로 다음과 같은 객체를 생성합니다.
// 주의: 다음 구조는 단순화되었습니다
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
이러한 객체를 “React 엘리먼트”라고 하며, 화면에서 보고 싶은 것을 나타내는 표현이라 생각하면 됩니다.
React는 이 객체를 읽어서, DOM을 구성하고 최신 상태로 유지하는 데 사용합니다.
엘리먼트 렌더링
엘리먼트 : React 앱의 가장 작은 단위로 화면에 표시할 내용을 작성
브라우저 DOM 엘리먼트와 달리 React 엘리먼트는 일반 객체이며(plain object) 쉽게 생성할 수 있다. React DOM은 React 엘리먼트와 일치하도록 DOM을 업데이트합니다.
const element = <h1>Hello, React!</h1>
DOM에 엘리먼트 렌더링
- index.html
<div id="root"></div>
html 파일에 들어가는 모든 엘리먼트를 React DOM에서 관리하기 때문에
이것을 “루트(root)” DOM 노드라고 부른다.
React로 구현된 애플리케이션은 일반적으로 하나의 루트 DOM 노드가 있다.
React를 기존 앱에 통합하려는 경우 원하는 만큼 많은 수의 독립된 루트 DOM 노드가 있을 수 있다.
React 엘리먼트를 루트 DOM 노드에 렌더링하려면 둘 다 ReactDOM.render()로 전달하면 된다.
// 리액트 엘리먼트 생성
const element = <h1>Hello, world</h1>;
// ReactDOM.render()로 전달 : 엘리먼트를 root DOM 노드에 렌더링
ReactDOM.render(element, document.getElmentById('root'))
렌더링 된 엘리먼트 업데이트하기
React 엘리먼트는 불변객체입니다. 엘리먼트를 생성한 이후에는 해당 엘리먼트의 자식이나 속성을 변경할 수 없다. 엘리먼트는 영화에서 하나의 프레임과 같이 특정 시점의 UI를 보여준다.
현재까지 배운 내용에서 UI를 업데이트하는 유일한 방법은 새로운 엘리먼트를 생성하고 이를 ReactDOM.render()로 전달하는 것
주의 : 실제로 대부분의 React 앱은 ReactDOM.render()를 한 번만 호출. 유상태 컴포넌트에 캡슐화 필요
function tick() {
// 엘리먼트 생성
const elemnet =(
<div>
<h1>Hello, React!</h1>
<h2>It is {new Date().toLocaleTimeString()} </h2>
</div>
);
// ReactDOM.render()로 전달
ReactDOM.render(element, document.getElementById('root'));
}
// tick 함수 1초 마다 실행 : 1초마다 화명 렌더링
setInterval(tick, 1000);
// 위 코드는 <h2> 부분(변경된 부분)만 업데이트한다.
변경된 부분만 업데이트 : React DOM은 해당 엘리먼트와 그 자식 엘리먼트를 이전의 엘리먼트와 비교하고 DOM을 원하는 상태로 만드는데 필요한 경우에만 DOM을 업데이트.
위 코드는 <h2> 부분만 업데이트한다.
Components & Props
Component : 컴포넌트를 통해 UI를 재사용 가능한 개별적인 여러 조각으로 나누어 사용 가능.
개념적으로 컴포넌트는 자바스크립트의 함수와 유사. props라고 하는 임의의 입력을 받은 후 화면에 리액트 엘리먼트를 리턴.
props : 속성을 나타내는 데이터
함수 컴포넌트 & 클래스 컴포넌트
- 함수 컴포넌트 : 컴포넌트를 정의하는 가장 간단한 방법은 JS 함수를 작성하는 것
// props를 객체 인자로 받은 후
// 주의: 컴포넌트의 이름은 항상 대문자로 시작
function Welcome(props) {
// 리액트 엘리먼트 반환
return <h1>Hi, {props.name}</h1>
}
- 클래스 컴포넌트 : ES6 class 사용.
// React.Component를 상속 받은 클래스 Welcome
// 주의: 컴포넌트의 이름은 항상 대문자로 시작
class Welcome extends React.Component {
// 렌더링
render() {
return <h1>Hi, {props.name}</h1>
}
}
컴포넌트 렌더링
React 엘리먼트는 사용자 정의 컴포넌트로도 표현 가능
// 함수 컴포넌트
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 클래스 컴포넌트 : React.Component를 상속 받은 Welcome
// class Welcome extends React.Component {
// render() {
// class 이므로 this를 사용
// return <h1>Hi, {this.props.name}</h1>
// }
// }
// 위에서 만든 Welcome 컴포넌트 사용
const element = <Welcome name ="Terry" />;
// 실제 렌더링
ReactDOM.render (
element,
// html 파일에 있는 root 선택
document.getElementById('root')
)
위 예제 코드 실행 과정
1.
컴포넌트 합성
컴포넌트는 자신의 출력에 다른 컴포넌트를 참조 가능. 이는 모든 세부 단계에서 동일한 추상 컴포넌트를 사용할 수 있음을 의미.
일반적으로 새 React 앱은 최상위에 단일 App 컴포넌트를 가지고 있다. 하지만 기존 앱에 React를 통합하는 경우 Button과 같은 작은 컴포넌트부터 시작해서 뷰 계층의 상단으로 올라가면서 점진적으로 작업해야 한다.
// 함수 컴포넌트 : props는 데이터 인자를 의미
function Welcome(props) {
return <h1>Hi, {props.name}</h1>
}
// 함수 컴포넌트 : 렌더링 목적
function App() {
return (
<div>
<Welcome name = "CREAMer"/>
<Welcome name = "DREAMer"/>
<Welcome name = "Terry"/>
</div>
)
}
// 렌더링
ReactDOM.render(
// APP 컴포넌트 사용
<App />,
// 위치
document.getElementById('root')
);
컴포넌트 추출
컴포넌트를 여러 개의 작은 컴포넌트로 나눠라!
// 함수형 컴포넌트
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
function formatDate(date) {
return date.toLocaleDateString();
}
const comment = {
date: new Date(),
text: 'I hope you enjoy learning React!',
author: {
name: 'Hello Kitty',
avatarUrl: 'https://placekitten.com/g/64/64',
},
};
ReactDOM.render(
<Comment
date={comment.date}
text={comment.text}
author={comment.author}
/>,
document.getElementById('root')
);
위 컴포넌트를 작은 컴포넌트로 나누기
// Avartar 컴포넌트 추출
function Avatar(props) {
return (
<img className='Avatar'
src={props.user.avatarUrl}
alt={props.user.name}
/>
// Avatar 는 자신이 Comment 내에서 렌더링 된다는 것을 알 필요가 없습니다.
// 따라서 props의 이름을 author에서 더욱 일반화된 user로 변경하였습니다.
// props의 이름은 사용될 context가 아닌 컴포넌트 자체의 관점에서 짓는 것을 권장
);
}
// UserInfo 추출
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}
// 결과
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
props 읽기 전용
함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트의 자체 props를 수정해서는 안 된다.
// 순수 함수 : 입력 값을 바꾸지 않고 항상 동일한 입력값에 대해 동일한 결과
function sum(a, b) {
return a + b;
}
// 비 순수 함수 : 자신의 입력값을 변경하기 때문에 순수함수가 아니다.
function withdraw(account, amount) {
account.total -= amount;
// === account.total = account.total -amount;
}
React의 한 가지 엄격한 규칙 : 모든 React 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야 한다
State
조건부 렌더링
React에서는 컴포넌트 중 몇 개만을 렌더링하여 원하는 동작을 캡슐화하는 컴포넌트 생성 가능
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
// 위 함수 컴포넌트를 캡슐화하여 원하는 동작을 하는 컴포넌트 생성
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
// Try changing to isLoggedIn={true}:
<Greeting isLoggedIn={false} />,
// 화면에 Please sign up. 렌더링
// <Greeting isLoggedIn={true} />,
// 화면에 Welcome back! 렌더링
document.getElementById('root')
);
논리 && 연산자로 If를 인라인으로 표현하기
JSX 안에는 중괄호를 이용해서 표현식을 포함 할 수 있다. 그 안에 JS 논리 연산자 &&를 사용하면 쉽게 엘리먼트를 조건부로 넣을 수 있다.
JavaScript에서 true && expression은 항상 expression으로 평가되고 false && expression은 항상 false로 평가된다.
따라서 && 뒤의 엘리먼트는 조건이 true일때 출력이 됩니다. 조건이 false라면 React는 무시
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
// true && expression은 항상 expression으로 평가 : expression 출력
return (
<div>
<h1>Hello!</h1>
true &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
// 렌더링 화면
Hello!
You have 3 unread messages.
// false && expression은 항상 expression으로 평가 : false로 평가.
return (
<div>
<h1>Hello!</h1>
false &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
// 렌더링 화면
Hello!
// false이므로 xpression 엘리먼트 리액트 작동 X
주의 : false로 평가될 수 있는 표현식을 반환하면 && 뒤에 있는 표현식은 건너뛰지만 false로 평가될 수 있는 표현식이 리턴
render() {
const count = 0;
return (
<div>
{ count && <h1>Messages: {count}</h1>}
// Boolean(count)은 false 이므로 && 전인 count 부분만 반환
// Boolean(count)가 true면 && <h1>Messages: {count}</h1> 부분만 반환
</div>
);
}
// render 메소드는 <div>0</div>를 return
조건부 연산자로 If-Else구문 인라인으로 표현하기
엘리먼트를 조건부로 렌더링하는 다른 방법 : 조건부 연산자인 condition ? true: false 사용 (삼항 연산자)
// ex code 1
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
// ex code 2
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
// true면 LogoutButton 컴포넌트에 온클릭 이벤트 할당
? <LogoutButton onClick={this.handleLogoutClick} />
// true면 LoginButton 컴포넌트에 온클릭 이벤트 할당
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}
// JavaScript와 마찬가지로, 가독성이 좋다고 생각하는 방식을 선택하면 된다.
// 또한 조건이 너무 복잡하다면 컴포넌트를 분리하기 좋을 때 일 수도 있다.
List & Key
여러개의 컴포넌트 렌더링 하기
엘리먼트 모음을 만들고 중괄호 {}를 이용하여 JSX에 포함 가능
const numbers = [1,2,3,4,5];
const listItems = numbers.map(number => <li>{number}</li>);
// listItems
// [ <li>1<li>, <li>2<li>, <li>3<li>, <li>4<li>, <li>5<li>]
// listItems 배열을 <ul>엘리먼트 안에 포함하고 DOM에 렌더링합니다.
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
State & LifeCycle
Props와 State의 차이
- State : 컴포넌트 사용 중 컴포넌트 내부에서 변할 수 있는 값 (ex : 나이, 사는 곳, 취업 여부)
- props : 외부로 부터 전달 받은 값 (ex : 이름, 성별)
this.setState 사용법 & 사용하는 이유
이벤트 처리 방법 & 이벤트 처리시 바인드 사용 이유
State 컴포넌트를 만들기 위해서는 class 컴포넌트 필요
- ex code : on / off 버튼
import React from "react";
import "./styles.css";
function App() {
return (
<div className="App">
<h2>talk about States</h2>
<ToggleSwitch />
</div>
);
}
class ToggleSwitch extends React.Component {
constructor(props) {
super(props);
this.state = { isOn: true };
// class 인스턴스에 이벤트 연결을 위해?
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log("이벤트 연결?");
// state 변경시 setState 사용
this.setState((state) => ({
isOn: !state.isOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isOn ? "ON" : "OFF"}
</button>
);
}
}
export default App;
함수 컴포넌트 -> 클래스 컴포넌트 + state + Life Cycle
- ex code : 함수 컴포넌트
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
위 함수 컴포넌트가 사용 된 코드를 클래스 컴포넌트를 사용한 코드로 변경 (+state 사용)
1.React.Component를 확장(상속)하는 ES6 class 생성 2.render() 메소드 추가 3.함수의 내용을 render() 메소드 안에 추가 4.render() 내용 안에 있는 props를 this.props로 변경(class로 생성한 인스턴스에 적용하기 위해) 5.render() 메서드 안에 있는 this.props.date를 this.state.date로 변경 6.초기 this.state를 지정하는 class constructor를 추가
[생명주기 메서드를 클래스에 추가]
많은 컴포넌트가 있는 애플리케이션에서 컴포넌트가 삭제될 때 해당 컴포넌트가 사용 중이던 리소스를 확보하는 것이 중요
생명주기 메서드 componentDidMount() : 컴포넌트 출력물이 DOM에 렌더링 된 후에 실행 (ex 타이머 설정) componentWillUnmount() :
ex code : 클래스 컴포넌트
// 1.React.Component를 확장하는 ES6 class 생성
class Clock extends React.Component {
// 6.초기 this.state를 지정하기 위해 class constructor를 추가
construcrot (props) {
// 상속 : 클래스 컴포넌트는 항상 props로 기본 constructor를 호출
super(props);
// state 추가
this.state = {date: new Date()};
}
// [Life Cycle Method]
// componentDidMount : 컴포넌트 출력물이 DOM에 렌더링 된 이후 실행
componentDidMount() {
this.timerID = setInterval( () => this.tick(), 1000);
}
// componentWillUnmount
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({date: new Date()})
}
// 2.render() 메소드 추가
render() {
// 3.함수의 내용을 render() 메소드 안에 추가
// 4.render() 내용 안에 있는 props를 this.props로 변경
// - class로 생성한 인스턴스에 적용하기 위해
// 5.render() 메서드 안에 있는 this.props.date를 this.state.date로 변경
return (
<div>
<h1>Hello, State!</h1>
<h2>Curren Time {this.state.date.toLocaleTimeString()} </h2>
</div>
);
}
}
ReactDOM.render(
// <Clock /> 요소에서 date prop을 삭제
<Clock />,
document.getElementById('root')
}
setInterval(tick, 1000);
위 코드의 작동 로직
-
가 ReactDOM.render()로 전달되었을 때 React는 Clock 컴포넌트의 constructor를 호출합니다. Clock이 현재 시각을 표시해야 하기 때문에 현재 시각이 포함된 객체로 this.state를 초기화합니다. 나중에 이 state를 업데이트할 것입니다.
React는 Clock 컴포넌트의 render() 메서드를 호출합니다. 이를 통해 React는 화면에 표시되어야 할 내용을 알게 됩니다. 그 다음 React는 Clock의 렌더링 출력값을 일치시키기 위해 DOM을 업데이트합니다.
Clock 출력값이 DOM에 삽입되면, React는 (렌더링이 되었으므로) componentDidMount() 생명주기 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 매초 컴포넌트의 tick() 메서드를 호출하기 위한 타이머를 설정하도록 브라우저에 요청합니다.
매초 브라우저가 tick() 메서드를 호출합니다. 그 안에서 Clock 컴포넌트는 setState()에 현재 시각을 포함하는 객체를 호출하면서 UI 업데이트를 진행합니다. setState() 호출 덕분에 React는 state가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기 위해 render() 메서드를 다시 호출합니다. 이 때 render() 메서드 안의 this.state.date가 달라지고 렌더링 출력값은 업데이트된 시각을 포함합니다. React는 이에 따라 DOM을 업데이트합니다.
- Clock 컴포넌트가 DOM으로부터 한 번이라도 삭제된 적이 있다면 React는 타이머를 멈추기 위해 componentWillUnmount() 생명주기 메서드를 호출합니다.
State 올바르게 사용하기
직접 State를 수정하지마라!
// 잘못된 예시 : 직접 수정
this.state.comment = 'Hello';
// 옳은 예시 : setState 사용
this.setState({comment: 'Hello'});
// this.state를 지정할 수 있는 유일한 공간은 constructor
State 업데이트는 비동기적일 수도 있다.
React는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한꺼번에 처리할 수 있다.
this.props와 this.state가 비동기적으로 업데이트될 수 있기 때문에 다음 state를 계산할 때 해당 값에 의존해서는 안된다.
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct : 함수 사용
// 이전 state를 첫 번째 인자, 업데이트가 적용된 시점의 props를 두 번째 인자로 받아들일 것
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
State 업데이트 병합 가능
setState()를 호출할 때 React는 제공한 객체를 현재 state로 병합
// state는 다양한 독립적인 변수 포함 가능
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
// 별도의 setState() 호출로 이러한 변수를 독립적으로 업데이트
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
// 병합은 얕게 이루어지기 때문에 this.setState({comments})는
// this.state.posts에 영향을 주진 않지만 this.state.comments는 완전히 대체.
데이터는 아래로 흐른다.
state가 소유하고 설정한 컴포넌트 이외에는 어떠한 컴포넌트에도 접근할 수 없다.
// 컴포넌트는 자신의 state를 자식 컴포넌트에 props로 전달 가능
<FormattedDate date={this.state.date} />
기본 리스트 컴포넌트
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
위 코드를 실행하면 리스트의 각 항목에 key를 넣어야한다는 경고가 나온다. 위 문제를 해결하기 위해 key를 할당해야한다.
key : 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// key를 할당하여 key 문제 해결.
<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')
);
key
Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다. key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 한다.
const numbers = [1,2,3,4,5];
const listItems = numbers.map(num =>
// num을 문자열로 변환해서 키 값에 저장
<li key={num.toString()}>
{num}
</li>
);
Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다. 대부분의 경우 데이터의 ID를 key로 사용
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
렌더링 한 항목에 대한 ID가 없다면 최후의 수단으로 항목의 인덱스를 key로 사용할 수 있다.
최후의 수단인 이유는 항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것은 권장하지 않는다. 이로 인해 성능이 저하되거나 컴포넌트의 state와 관련된 문제가 발생할 수 있다.
// map의 두번째 파리미터로 index를 준다.
const todoItems = todos.map((todo, index) =>
// 만약 ID가 없는 경우 index를 사용.
<li key={index}>
{todo.text}
</li>
);
Key로 컴포넌트 추출하기
키는 주변 배열의 context에서만 의미가 있다. ex: ListItem 컴포넌트를 추출 한 경우 ListItem 안에 있는 <li> 엘리먼트가 아니라 배열의
경험상 map() 함수 내부에 있는 엘리먼트에 key를 넣어 주는 게 좋다.
function ListItem(props) {
const value = props.value;
return (
// <li> 엘리먼트에 key를 작성하면 안된다.
<li>{value}</li>
)
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map(num =>
// key를 배열에 작성해야한다.
<ListItem key={value.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
)
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Key는 형제 사이에서만 고유한 값이어야 한다.
Key는 배열 안에서 형제 사이에서 고유해야 하고 전체 범위에서 고유할 필요는 없다. 두 개의 다른 배열을 만들 때 동일한 key를 사용할 수 있다.
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map(post =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map(post =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
ReactDOM.render(
// Blog : 함수 컴포넌트
// posts : props
<Blog posts={posts} />,
document.getElementById('root')
)
JSX에 map() 포함
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map(num =>
<ListItem key={num.toString()}
value={num} />
);
return (
<ul>
{listItems}
</ul>
);
}
위 코드를 JSX를 사용하면 중괄호 안에 모든 표현식을 포함 시킬 수 있다.
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map(num =>
<ListItem key={num.toString()}
value={num} />
)}
</ul>
)
}
위 방식을 사용하면 코드가 더 깔끔해 지지만, 이 방식을 남발하는 것은 좋지 않다. 가독성을 위해 변수로 추출해야 할지 아니면 인라인으로 넣을지는 개발자가 직접 판단해야 함. map() 함수가 너무 중첩된다면 컴포넌트로 추출 하는 것이 좋다.
Fragments
React에서 컴포넌트가 여러 엘리먼트를 반환하는 것은 흔한 패턴. Fragments는 DOM에 별도의 노드를 추가하지 않고 여러 자식을 그룹화 가능
render() {
return (
// 의미 없는 프래그 먼트를 넣어서 그룹화
// <>는 <React.Fragment>의 축약형
<>
<ChildA />
<ChildB />
<ChildC />
</>
)
}
key가 있는 Fragments
Fragments에 key가 있다면
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// React는 `key`가 없으면 key warning이 발생하므로
// map에 key를 넣어줘라.
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
key는 Fragment에 전달할 수 있는 유일한 어트리뷰트입니다. 추후 이벤트 핸들러와 같은 추가적인 어트리뷰트를 지원할 수도 있습니다.