React 공식문서 정독하기 - 1. js 압축, props, Lifecycle, State, 조건부 렌더링

kind J·2022년 6월 6일
0

React 공식문서

목록 보기
1/3
post-thumbnail

리액트 공식문서 학습의 중요성을 깨닫고 정독 하기로 맘먹었다. 다른 리액트 강의 보기 이전에 필수로 읽어야하는 정석이랄까. 공식 문서 보면서 몰랐던 점이나 기억해야 할만한 것들을 적어보려고한다.

프로덕션을 위한 javaScript 의 압축

프로덕션을 위해 웹사이트를 배포하기 전에 자바스크립트 파일을 압축하여 웹사이트를 사용하면 성능이 향상된다.

스크립트를 압축하면 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 는 읽기 전용이다

함수 컴포넌트나 클래스 컴포넌트 모두 컴포넌트 자체 props 를 수정해서는 안된다.

function sum(a, b) {
	return a + b
}

이런 함수들을 순수 함수라고 호칭한다. 입력값을 바꾸려 하지 않고 항상 동일한 입력값에 대해 동일한 결과를 반환하기 때문이다.

다음 함수는 자신의 입력값을 바꾸기 때문에 순수 함수가 아니다.

function withdraw(account, amount){
	account.total -= amount;
}

모든 React 컴포넌트는 자신의 props 를 다룰 때 반드시 순수 함수 처럼 동작해야한다.

State and Lifecycle

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')
);
  1. <Clock / > 이 ReactDOM.render() 로 전달 되었을 때 React Clock 컴포넌트의 constructor 를 호출한다. Clock 은 현재 시간이 포함된 객체로 this.state 를 초기화한다.
  2. 리액트는 <Clock / > 컴포넌트의 render 메서드를 호출한다. 화면에 표시될 내용을 알게된다. Clock 의 출력값을 일치시키기 위해 DOM 을 업데이트한다.
  3. clock 출력값이 DOM 에 삽입이되면 componentDidMoun() 생명주기 메서드를 호출한다. tick 메서드를 호출하기 위해서 타이머를 설정하도록 브라우저에 요청한다.
  4. 매초 브라우저가 tick() 메서드를 호출한다. 그 안에서 Clock 컴포넌트는 setState() 에 현재 시간을 포함하는 객체를 호출하면서 UI 업데이트를 진행한다. setState() 호출 덕분에 React 는 state 가 변경된 것을 인지하고 화면에 표시될 내용을 알아내기 위해서 render() 메서드를 다시 호출한다. render() 메서드 안의 this.state.date 가 달라지고 렌더링 출력값은 업데이트 된 시각을 포함한다. React 는 이에 따라 DOM 을 업데이트 한다.
  5. Clock 컴포넌트가 DOM 을 한번이라도 삭제한 적이 있다면 타이머를 멈추기 위해 componentWillUnmount() 생명주기 메서드를 호출한다.

State 를 올바르게 사용하기

1) 직접 State 를 수정하지 않는다.

//wrong
this.state.comment = 'hello'

//Correct
this.setState({comment : 'Hello'})

2) state 업데이트는 비동기 적일 수 있다.

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
    }
  }
}

3) state 는 병합된다.

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은 자신만의 타이머를 설정하고 독립적으로 업데이트를 한다.

조건부 렌더링

1) if 이용

  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 안에서 인라인으로 처리할 방법에 대해 알아보자.

1) 논리 && 연산자로 If 를 인라인으로 표현하기

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>
  );
}

2. 조건부 연산자로 if-else 구문 인라인으로 표현하기

조건부 연산자
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>
  	}
}

3. 컴포넌트가 렌더링 하는 것을 막기

다른 컴포넌트에 의해 랜더링 될 때 컴포넌트 자체를 숨기고 싶을 때가 있을 수 있다. 이때 랜더링 결과를 출력하는 대신 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는 계속해서 호출되게 된다.

profile
프론트앤드 개발자로 일하고 있는 kind J 입니다.

0개의 댓글