본문 바로가기

React/공식문서(구버전)

[리액트 공식문서] 주요 개념 5 ~ 8 정리

5. State and Lifecycle

- 아래 코드를

 

const root = ReactDOM.createRoot(document.getElementById("root"));

function tick() {
  const element = (
    <div>
      <h1>Hello, world</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  )
  root.render(element);
}

setInterval(tick, 1000);

 

- Clock 컴포넌트로 만들어 재사용하고 캡슐화 해보자 (??? 컴포넌트 캡슐화의 의미 ??? 컴포넌트 내부의 값은 다른 컴포넌트가 바꿀 수 없다? 라는 의미인걸까? 재사용이 가능한 컴포넌트를 만들기 위해서 캡슐화라는 용어를 쓴 것일까??

++ 리액트에서 컴포넌트 캡슐화란 메인 어플리케이션으로부터 독립되어 있는 컴포넌트를 의미한다.
이를 통해 컴포넌트 내부에서 발생하는 업데이트, 테스트 케이스, 재사용에 용이하게 컴포넌트를 설계할 수 있다.)

 

const root = ReactDOM.createRoot(document.getElementById("root"));

function Clock(props) {
  return (
    <div>
      <h1>Hello, World</h1>
      <h2>It is {props.date}</h2>
    </div>
  )
}

function tick() {
  root.render(<Clock date={new Date().toLocaleTimeString()});
}

setInterval(tick ,1000);

 

- 위 예제에서는 Clock 컴포넌트가 타이머를 설정하고 매초 UI를 업데이트 하는 것이 아니다.

- 이상적으로 한 번만 코드를 작성하고 Clock이 스스로 업데이트하도록 만들어야 한다.

- 이 것을 구현하기 위해서 Clock 컴포넌트에 "state"를 추가해아 한다.

 

- State는 props와 유사하지만, 비공개이며 컴포넌트에 의해 완전히 제어된다.

1) 로컬 State 추가하기

import { useState } from "react";

const root = ReactDOM.createRoot(document.getElementById("root"));

function Clock(props) {
  const [date, setDate()] = useState(new Date());
  
  return (
    <div>
      <h1>Hello, World</h1>
      <h2>It is {date.toLocaleTimeString()}</h2>
    </div>
  )
}

root.render(<Clock />);

2) 함수형 컴포넌트에서 생명주기 메서드를 이용한 것 처럼 구현

import { useState, useEffect } from "react";

const root = ReactDOM.createRoot(document.getElementById("root"));

function Clock(props) {
  const [date, setDate] = useState();
  
  function tick() {
    setDate(new Date());
  }
  
  useEffect(() => {
    const timerId = setInterval(() => tick(), 1000)
    
    return () => {
      clearInterval(timerId);
    }
  }, [date]);
  
  return (
    <div>
      <h1>Hello, World</h1>
      <h2>It is {date.toLocaleTimeString()}</h2>
    </div>
  )
}

root.render(<Clock />);

3) State를 올바르게 사용하기

- setState( ) 에 대해서 알아야 할 세 가지가 있다.

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

 

function Clock() {
  const [date, setDate] = useState("");
  ...
  
  // Wrong
  date = new Date();

  // Correct
  setDate(new Date());
}

 

- State 업데이트는 비동기적일 수도 있다.

- React는 성능을 위해 여러 setState( ) 호출을 단일 업데이트로 한꺼번에 처리할 수 있다. props와 state가 비동기적으로 업데이트 될 수 있기 때문에 다음 state를 계산할 때 해당 값에 의존해서는 안된다.

- 객체보다는 함수를 인자로 사용하는 다른 형태의 setState( )를 사용하자. 그 함수는 이전 state를 첫 번째 인자로 받아들일 것이고, 업데이트가 적용된 시점의 props를 두 번째 인자로 받아들일 것이다.

 

function Calculater(props) {
  const [counter, setCounter] = useState("");
  ...
  
  // Wrong
  setCounter(counter + props.increment);

  // Correct
  setCounter((prevState, props) => ({
    prevState + props.increment
  }));
}

 

- State 업데이트는 병합된다.

- setState( ) 를 호출할 때 React는 제공한 객체를 현재 state로 병합한다.

- state는 다양한 독립적인 변수를 포함할 수 있는데 별도의 setState( ) 호출로 이러한 변수를 독립적으로 업데이트 할 수 있다.

4) 데이터는 아래로 흐른다.

- 부모 컴포넌트나 자식 컴포넌트 모두 특정 컴포넌트가 유상태인지 무상태인지 알 수 없고, 그들이 함수나 클래스로 정의되었는지에 대해 관심을 가질 필요가 없다.

- 이 때문에 state는 종종 로컬 또는 캡슐화라고 불린다. state를 소유하고 설정한 컴포넌트 이외에는 어떠한 컴포넌트에서도 접근할 수 없다.

- 컴포넌트는 자신의 state를 자식 컴포넌트에 props로 전달할 수 있다.

- 일반적적으로 이를 "하향식(top-down)" 또는 "단방향식" 데이터 흐름이라고 한다. 모든 state는 항상 특정한 컴포넌트가 소유하고 있으며 그 state로부터 파생된 UI 또는 데이터는 오직 트리구조에서 자신의 "아래"에 있는 컴포넌트에만 영향을 미친다.

6. 이벤트 처리하기 

1) React의 이벤트는 소문자 대신 캐멀 캐이스(camelCase)를 사용한다. (ex. onClick, onChange)

2) JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달한다.

// HTML
<button onclick="activeLasers()">
  Active Lasers
</button>

// React
<button onClick={activeLasers}>
  Active Lasers
</button>

3) React에서는 false를 반환해도 기본 동작을 방지할 수 없다. 

- 반드시 preventDefault를 명시적으로 호출해야 한다.

 

- 예를 들어, 일반 HTML에서 폼을 제출할 때 가지고 있는 기본 동작을 방지하기 위해 다음과 같은 코드를 작성할 수 있다.

 

<form onsubmit="console.log('You clicked submit.'); return false">
  <button type="submit">Submit</button>
</form>

 

- React에서는 다음과 같이 작성할 수 있다.

 

function Form() {
  function handleSubmit(e) {
    e.preventDefault(); // 이렇게 명시적으로 호출해줘야 한다.
    console.log("You clicked submit");
  }
  
  return (
    <form onSubmit={handleSubmit}> // 엘리먼트가 처음 렌더링 될때 리스너를 제공하면 된다.
      <button type="submit">Submit</button>
    </form>
  )
}

 

- React를 사용할 때 DOM 엘리먼트가 생성된 후 리스너를 추가하기 위해 addEventListener 를 호출할 필요가 없다. 대신, 엘리먼트가 처음 렌더링될 때 리스너를 제공하면 된다.

7. 조건부 렌더링

- React에서 조건부 렌더링은 JavaScript에서의 조건 처리와 같이 동작한다. if 나 조건부 연산자와 같은 JavaScript 연산자를 현재 상태를 나타내는 엘리먼트를 만드는데 사용할 수 있다. 그러면 React는 현재 상태에 맞게 UI를 업데이트 할 것이다.

 

function UserGreeting() {
  return <h1>Welcome Back!</h1>
}

function GestGreeting() {
  return <h1>Please Sign up!</h1>
}

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  
  if (isLoggedIn) { // 이렇게 if 연산자를 사용하여 조건부 렌더링을 할 수 있다.
    return <UserGreeting />
  }
  
  return <GuestGreeting />
}

1) 엘리먼트 변수

- 엘리먼트를 저장하기 위해 변수를 사용할 수 있다. 

 

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

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

function LoginControl() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  
  function handleLoginClick() {
    setIsLoggedIn(true);
  }
  
  function handleLogoutClick() {
    setIsLoggedIn(false);
  }
  
  let button;
  
  if (isLoggedIn) {
    button = <LogoutButton onClick={handleLogoutClick} />; // 이렇게 변수에다가 저장할 수 있다.
  } else {
    button = <LoginButton onClick={handleLoginClick} />;
  }
  
  return (
    <div>
      <Greeting isLoggedIn={isLoggedIn} />
      {button}
    </div>
  )
}

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

- JSX 안에는 중괄호를 이용해서 표현식을 포함할 수 있다. 그 안에 JavaScript의 논리 연산자 && 를 사용하면 쉽게 엘리먼트를 조건부로 넣을 수 있다.

 

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", "You Can Do it!!"];

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Mailbox unreadMessages={messages} />);

 

- JavaScript 에서 true && expression 은 항상 expression 으로 평가되고 false && expression 은 항상 false로 평가된다.

- 따라서 && 뒤의 엘리먼트는 조건이 true 일때 출력된다. 조건이 false라면 React는 무시하고 건너뛴다.

 

⚠️ falsy 표현식을 반환하면 여전히 && 뒤에 있는 표현식은 건너뛰지만 falsy 표현식이 반환된다. 아래 예시에서, <div>0</div>이 반환됨을 확인할 수 있다.

 

const count = 0;
return (
  <div>
    {count && 
      <h1>Messages: {count}</h1> // Messages: 0 이 아니라 0만 출력된다.
  </div>
)

3) 조건부 연산자로 If-Else 구문 인라인으로 표현하기

const [isLoggedIn, setIsLoggedIn] = useState(false);

return (
  <div>
    {isLoggedIn
      ? <LogoutButton onClick={handleLogoutClick} />
      : <LoginButton onClick={handleLoginClick} />
    }
  </div>
)

4) 컴포넌트가 렌더링하는 것을 막기

- 가끔 다른 컴포넌트에 의해 렌더링 될 때 컴포넌트 자체를 숨기고 싶을 때가 있을 수 있다. 이때는 렌더링 결과를 출력하는 대신 null을 반환하며 해결할 수 있다.

 

function WarningBanner(props) {
  if (!props.warn) {
    return null; // 이렇게 null 을 반환하면 WarningBanner 컴포넌트는 렌더링 되지 않는다.
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

function Page() {
  const [showWarning, setShowWarning] = useState(true);

  function handleToggleClick() {
    setShowWarning((prevState) => !prevState);
  }

  return (
    <div>
      <WarningBanner warn={showWarning} />
      <button onClick={handleToggleClick}>
        {showWarning ? 'Hide' : 'Show'}
      </button>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root')); 
root.render(<Page />);

8. 리스트와 key

1) 여러개의 컴포넌트 렌더링 하기

- 엘리먼트 모음을 만들고 중괄호 { } 를 이용하여 JSX에 포함시킬 수 있다.

 

const number = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => <li>{number}</li>); // 엘리먼트 모음을 만들고

const root = ReactDOM.createRoot(document.getElementById('root')); 
root.render(<ul>{listItems}</ul>); // 중괄호를 이용하여 넣을 수 있다.

2) 기본 리스트 컴포넌트

- 일반적으로 컴포넌트 안에서 리스트를 렌더링한다

 

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];
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<NumberList numbers={numbers} />);

3) Key

- "key"는 엘리먼트 리스트를 만들 때 포함해야 하는 특수한 문자열 어트리뷰트이다.

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

 

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}> // 이렇게 배열 내부의 엘리먼트에 key를 지정해야 한다.
    {number}
  </li>
);

 

- Key를 선택하는 가장 좋은 방법은 리스트의 다른 항목들 사이에서 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다. 대부분의 경우 데이터의 ID를 key로 사용한다.

 

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

 

- 렌더링 한 항목에 대한 안정적인 ID가 없다면 최후의 수단으로 항목의 인덱스를 key로 사용할 수 있다.

 

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

 

- 리스트 항목의 순서가 바뀔 수 있는 경우 key에 인덱스를 사용하는 것을 권장하지 않는다.

4) Key는 형제 사이에서만 고유한 값이어야 한다.

- Key는 배열 안에서 형제 사이에서 고유해야 하고 전체 범위에서 고유할 필요는 없다. 두 개의 다른 배열을 만들 때 동일한 key를 사용할 수 있다.

- React에서 key는 컴포넌트로 전달하지 않는다. 컴포넌트에서 key와 동일한 값이 필요하면 다른 이림의 prop으로 명시적으로 전달한다.

 

const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);

 

- 위 예시에서 Post 컴포넌트는 props.id를 읽을 수 있지만 props.key는 읽을 수 없다.

5) JSX에 map( ) 포함시키기

- 아래 예제를

 

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) => // 요 부분을 아래 코드와 같이 인라인으로 처리할 수 있다.
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

 

- 다음과 같이 바꿀 수 있다

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) => 
        <ListItem key={number.toString()}
                  value={number}
        />
      )}
    </ul>
  )
}