리액트 공식문서 학습의 중요성을 깨닫고 정독 하기로 맘먹었다. 다른 리액트 강의 보기 이전에 필수로 읽어야하는 정석이랄까. 공식 문서 보면서 몰랐던 점이나 기억해야 할만한 것들을 적어보려고한다.
프로덕션을 위해 웹사이트를 배포하기 전에 자바스크립트 파일을 압축하여 웹사이트를 사용하면 성능이 향상된다.
스크립트를 압축하면 production.min.js 형태가 된다.
압축하는 절차를 알아보자.
like_button.js 라는 js 파일이 있다고 치자.
1. Install Node.js
2. npm init -y 실행
3. npm install terser
4. npx terser -c -m -o like_button.min.js -- like_button.js
함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트 자체 props 를 수정해서는 안된다.
function sum(a, b) {
return a + b
}
이런 함수들을 순수 함수라고 호칭한다. 입력값을 바꾸려 하지 않고 항상 동일한 입력값에 대해 동일한 결과를 반환하기 때문이다.
다음 함수는 자신의 입력값을 바꾸기 때문에 순수 함수가 아니다.
function withdraw(account, amount){
account.total -= amount;
}
모든 React 컴포넌트는 자신의 props 를 다룰 때 반드시 순수 함수 처럼 동작해야한다.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
//wrong
this.state.comment = 'hello'
//Correct
this.setState({comment : 'Hello'})
React 는 성능을 위해 여러 setState() 호출을 단일 업데이트로 한번에 처리한다.
this.props 와 this.state 가 비동기적으로 업데이트 될 수 있어서 state 를 계산할 때 해당 값에 의존해서는 안된다.
다음 코드는 카운터 업데이트에 실패할 수 있다.
// wrong
this.setState({
counter : this.state.counter + this.props.increment,
})
이를 수정하려면 객체보다는 함수를 인자로 사용하는 다른 형태의 setState를 사용한다.
그 함수는 이전 state 를 첫번째 인자로 받아들일 것이고 업데이트가 적용된 시점의 props 를 두번째 인자로 받아들일것이다.
this.setState((state, props) =>
counter : state.counter + props.increment
}
화살표 함수 대신 일반 함수에서도 정상작동함
this.setState(function(state, props){
return {
counter : state.counter + props.increment
}
}
}
setState()를 호출할 때 React는 제공한 객체를 현재 state 로 병합한다.
state 는 다양한 독립적인 변수를 포함할 수 있다.
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
}
}
별도의 setState() 호출로 변수를 독립적으로 업데이트 할 수 있다.
componenentDidMount(){
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를 소유하고 설정한 컴포넌트가 아닌 다른 컴포넌트에서는 접근할 수 없다.
컴포넌트는 자신의 state 를 자식 컴포넌트에 props 로 전달할 수 있다.
<FormattedDate date={this.state.date} />
FormattedDate 컴포넌트는 date 를 props 로 받고 이것이 Clock 의 state 로 부터 왔는지 Clock 의 props 에서 왔는지 수동으로 입력한 것인지 알지 못한다.
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
이를 '하향식', '단방향식' 데이터 흐름이라고 한다.
모든 state 는 항상 특정한 컴포넌트가 소유하고 있고 그 state 로 부터 파생된 UI 또는 데이터는 오직 트리구조에서 자신의 '아래' 컴포넌트에만 영향을 미친다.
모든 컴포넌트가 완전히 독립적이라는 것을 보여주기 위해 App 렌더링 하는 세개의 을 만들 수 있다.
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
각 Clock은 자신만의 타이머를 설정하고 독립적으로 업데이트를 한다.
function LoginControl extends React.Component {
constructor(props){
super(props);
this.handleLoginClock = this.handleLoginClick.bind(this);
this.handleLogoutClock = this.handleLogoutClock.bind(this);
this.state = {isLoggedIn : false};
}
handleLoginClick() {
this.setState({isLoggedIn : true});
}
handleLogoutClick() {
this.setState({isLoggedIn : false});
}
render() {
const isLoggedIn = this.sate.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />
} else {
button = <LogInButton onClick={this.handleLoginClick} />
}
return (
<div>
<Greeting is LoggedIn={isLoggedIn} />
{button}
</div>
)
}
}
더 짧은 구문을 사용하고 싶을때 여러조건을 JSX 안에서 인라인으로 처리할 방법에 대해 알아보자.
JSX 안에는 중괄호를 이용해서 표현식을 포함할 수 있다. 자바스크립트 논리연산자 && 를 사용하면 쉽게 엘리먼트를 조건부로 넣을 수 있다.
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')
);
JavaScript에서 true && expression은 항상 expression으로 평가되고 false && expression은 항상 false로 평가된다.
따라서 && 뒤의 엘리먼트는 조건이 true일때 출력이 됩니다. 조건이 false라면 React는 무시하고 건너뜁니다.
falsy 표현식을 반환하면 여전히 && 뒤에 있는 표현식은 건너뛰지만 falsy 표현식이 반환된다. 아래 예시에서,
<div>0</div>
이 render 메서드에서 반환됩니다.
render() {
const count = 0;
return (
<div>
{count && <h1>Messages: {count}</h1>}
</div>
);
}
조건부 연산자
condition ? true : false
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is<b>{isLoggedIn ? 'currently' : 'not'}</b> logged in
</div>
)
}
가독성은 떨어지지만 아래와 같은 더 큰 표현식에도 이 구문을 사용할 수 있다.
render() {
const isLoggedIn = this.state.isLoggedIn;
return() {
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick}>
: <LogInButton onClick={this.handleLogInClick}>
}
</div>
}
}
다른 컴포넌트에 의해 랜더링 될 때 컴포넌트 자체를 숨기고 싶을 때가 있을 수 있다. 이때 랜더링 결과를 출력하는 대신 null 을 반환하면 해결할 수 있다.
아래 예시는 가 warn prop 의 값에 의해서 렌더링 된다. prop 이 false 라면 컴포넌트는 렌더링 하지 않게 된다.
function WarningBanner(props){
if(!prop.warn){
return null
}
return (
<div className="warning">
Warning!
</div>
);
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true};
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(state => ({
showWarning: !state.showWarning
}));
}
render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}
ReactDOM.render(
<Page />,
document.getElementById('root')
);
컴포넌트의 render 메서드로부터 null을 반환하는 것은 생명주기 메서드 호출에 영향을 주지 않는다. 그 예로 componentDidUpdate는 계속해서 호출되게 된다.