[MGS 3기 - 23일차] React 심화 (2)

박철연·2022년 5월 12일
0

MGS STFE 3기

목록 보기
20/35

프로젝트 후 첫번째로 수강한 강의입니다. 이번 파트에서는 React 공식 문서를 기반으로 기존에 다루지 않은 리액트 심화 개념들을 정리했습니다.

React와 조건부 렌더링

조건부 렌더링의 개념

React에서는 원하는 동작이나 기능을 캡슐화하는 컴포넌트를 만들 수 있습니다.

해당 동작을 컴포넌트 안에 캡슐화하면, 애플리케이션의 상황에 따라서 조건부로 렌더링되는 컴포넌트를 만들 수 있습니다.

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} />,
  document.getElementById('root')
);

위 코드 블럭은 결과적으로 isLoggedIn의 상태에 따라 다른 인사말을 렌더링합니다.

UserGreeting 컴포넌트와 GuestGreeting 컴포넌트가 조건에 따라 선별적으로 렌더링되는 것이죠.

엘리먼트 변수

엘리먼트를 저장하기 위해 변수를 사용할 수 있습니다. 이를 활용해 출력의 다른 부분은 변하지 않은 채로 컴포넌트의 일부를 조건부로 렌더링 할 수 있습니다.

아래와 같이 로그아웃과 로그인 버튼을 나타내는 두 가지 컴포넌트가 있다고 생각해 보겠습니다.

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

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

이 두 가지 컴포넌트를 조건부로 렌더링하기 위해 LoginControl이라는 변수를 만들어 볼 수 있습니다.

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

요약하자면, isLoggedIn의 상태에 따라 다른 종류의 컴포넌트를 렌더링하는 로직을 구현하고, 이를 아예 LoginControl이라는 변수 안에 담아준 코드입니다.

따라서 실제 렌더링을 하는 코드 부분에는 간단하게 LoginControl만 렌더링하게끔 해준 것이 보입니다.

논리 연산자와 조건부 렌더링

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

위 코드는 &&를 활용하여 간략하게 조건부 렌더링을 구현한 코드입니다.

unreadMessages가 없다면, && 연산자의 앞부분이 거짓일 수 밖에 없으므로, 뒤 쪽의 엘리먼트 부분은 당연히 렌더링 되지 않습니다.

이는 자바스크립트의 단축 평가를 활용한 부분으로, if로 조건부 렌더링을 구성하는 것보다 좀 더 효율적입니다.

React & List

React에서 List 활용해보기

리액트에서는 엘리먼트 모음을 만들고 중괄호 {}를 이용하여 이를 JSX에 포함 시킬 수 있습니다. 그 과정에서 List를 활용해 더 효과적인 코드를 작성할 수 있습니다.

아래의 코드 예시는 각 항목에 대해 li 엘리먼트를 반환하고 엘리먼트 배열의 결과를 listItems에 저장합니다.

그런 다음, listItems 배열을 ul 엘리먼트 안에 포함하고 DOM에 이를 렌더링합니다.

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

기본적인 list 컴포넌트는 다음과 같은 형태로 작성하게 됩니다.

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는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 속성입니다.

Key는 리액트가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕습니다. key는 엘리먼트에 안정적인 고유성을 부여하기 위해 반드시 배열 내부의 엘리먼트에 지정해야 합니다.

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

Key를 선택할 때에는 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이 가장 좋습니다. 리스트에 데이터 항목을 넣을 때에는 주로 해당 데이터의 ID를 key로 사용합니다.

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

또한, 렌더링을 하는 항목에 고유한 key 값을 넣는 것이 여의치 않다면, 항목의 인덱스를 key로 사용할 수 있습니다. 다음 코드를 참고해보세요.

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);

다만, key 값을 넣는 것이 쉽지 않을 때만 index를 활용하는 것이 좋고, 또 항목들 끼리의 순서가 바뀔 수 있는 구조라면 코드의 안정성을 위해 인덱스를 사용하지 않는 것이 좋습니다.

주의할 점은, key를 지정하는 것은 어디까지나 내가 추출할 배열의 context 내에서 요구된다는 것입니다.

쉽게 말해서, list라고 해서 반드시 key를 지정하는 것이 아니라 최종적으로 리액트 상에서 추출될 list 컴포넌트에 대해 key를 지정해야 합니다.

또한, key 값은 어디까지나 형제 사이의 엘리먼트들 사이에서만 고유하면 됩니다. 따라서 형제 관계가 아닌 리스트들끼리는 key 값의 고유성을 확보할 필요가 없죠.

Form

React와 Form

일반적으로 Form 엘리먼트는 다른 엘리먼트와 구분되는 특징이 있습니다. 바로 자체적으로 내부 상태를 지니고 있다는 점입니다.

일반적으로 사용자가 폼을 통해 응답을 제출하면 새로운 페이지로 이동하는 기본 HTML 폼 동작이 수행됩니다. (물론 React에서 동일한 동작을 원한다면 이를 그래도 사용할 수 있습니다.)

하지만 대부분의 경우, 자바스크립트 함수로 폼의 제출을 처리하고 사용자가 폼에 입력한 데이터에 접근하도록 하는 것이 편리합니다.

제어 컴포넌트

앞서 언급한 것과 같이, 폼의 기본 동작보다는 자바스크립트 함수로 폼의 제출을 처리하는 것이 더 효과적입니다. 이를 위해 만드는 것이 바로 제어 컴포넌트입니다.

React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트됩니다.

이러한 React state를 통해 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어합니다. 이렇게 React에 의해 값이 제어되는 입력 폼 엘리먼트를 제어 컴포넌트(controlled component)라고 합니다.

예를 들어, 이전 예시가 전송될 때 이름을 기록하길 원한다면 폼을 제어 컴포넌트로 작성할 수 있습니다.

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
profile
Frontend Developer

0개의 댓글