들어가기 전에

리액트 공식문서로 배워보자 #7, 조건부 렌더링!

리액트에서는, 필요한 동작을 캡슐화하는 독립적인 컴포넌트를 만들 수 있습니다. 또, 어플리케이션의 state에 따라 그들 중 오직 몇몇개만 렌더링 할 수도 있습니다.

리액트에서 조건부 렌더링은 자바스크립트 내부의 조건문과 똑같이 동작합니다. 현재 상태를 나타내는 엘리먼트를 만들기 위해서 자바스크립트 if와 같은 연산자들 또는 조건부 연산자를 사용하세요. 그리고 리액트가 그 상태에 맞춰 UI를 업데이트하게 두면 됩니다.

다음 두개의 컴포넌트를 생각해봅시다.

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

우리는 유저가 로그인했는지 아닌지에 따라 위의 2가지 상태 중 1가지를 보여주는 Greeting 컴포넌트를 만들 것입니다.

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

코드펜에서 직접 해보기

이 예제는 isLoggedIn prop의 값에 따라 다른 인사를 렌더링합니다.

엘리먼트 변수

엘리먼트를 저장하기 위해서 변수를 사용할 수 있습니다. 이 기능은 나머지 출력 결과물이 변경되지 않는 동안, 컴포넌트의 일부를 렌더링하는 것을 돕습니다.

로그인과 로그아웃을 보여주는 다음 두개의 버튼을 생각해봅시다.

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}

아래의 예제에서, 우리는 LoginControl이라 불리는 상태가 있는 컴포넌트를 만들 것입니다.

이 컴포넌트는 <LoginButton /> 또는 <LogoutButton />을 현재 상태에 따라 렌더링할 것입니다. 또 이전 예제로부터 <Greeting />도 렌더링할 것입니다.

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;

    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

코드펜에서 직접 해보기

인라인(inline), 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')
);

코드펜에서 직접 해보기

위의 소스는 정상적으로 동작합니다. 왜냐하면 자바스크립트에서 true && expression은 언제나 expression이고 false && expression은 언제나 false이기 때문입니다.

그러므로 조건이 true일 때는, && 오른쪽 옆에 있는 엘리먼트는 출력됩니다. 만일 조건이 false라면 리액트는 무시하고 넘어갑니다.

인라인(inline), 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>
  );
}

자바스크립트처럼 단순히 여러분과 여러분의 팀이 생각하기에 무엇이 더 가독성이 좋은지 판단하여 적절한 스타일을 사용하시면 됩니다. 또한 조건이 너무 복잡해질 때는, 컴포넌트 추출을 하는 것이 바람직하다는 것을 기억해두세요.

컴포넌트 렌더링으로부터 보호하기

아주 드물게, 어떤 컴포넌트가 다른 컴포넌트에 의해 렌더링됐지만 그걸 숨기고 싶을 때가 있습니다. 이렇게 하기 위해, 렌더링을 하는 대신에 그냥 null을 리턴하세요.

아래 예제에서, <WarningBanner />warn이라는 prop 값에 의해 렌더링이 결정됩니다. 만일 prop의 값이 false라면, 컴포넌트는 렌더링되지 않습니다.

function WarningBanner(props) {
  if (!props.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 메소드는 여전히 잘 실행될 것입니다.