(준)공식 문서/React

[ React 공식문서 ] 상호작용 추가하기 (0) : 훑어보기

Je-chan 2024. 3. 5. 23:20
  • 화면의 일부 항목은 입력에 따라 업데이트 된다.
  • 이번 챕터에서는 state를 업데이트하고, 시간에 따라 다른 값을 보여주는 컴포넌트를 작성하는 방법을 배울 것

1. 이벤트에 응답하기

  • 이벤트 핸들러는 사용자 상호작용에 반응해서 호출되는 자체 함수다
  • <button> 과 같은 기본 제공 컴포넌트는 onClick 과 같은 기본 제공 브라우저 이벤트만을 지원한다
  • 하지만, 컴포넌트 생성하고 이벤트 핸들러 props 로 원하는 이름을 지정할 수 있
function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Play Movie
      </Button>
      <Button onClick={onUploadImage}>
        Upload Image
      </Button>
    </div>
  );
}

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Playing!')}
      onUploadImage={() => alert('Uploading!')}
    />
  );
}

2. State : 컴포넌트의 메모리

  • 컴포넌트는 상호 작용의 결과로 화면 내용을 변경해야 하는 경우가 많다
  • 이렇게 달라지는 값들은 따로 기억(저장)하고 있어야 하며 이런 컴포넌트별 메모리를 state 라고 부른다
  • 컴포넌트에 state 를 추가하는 방법은 useState 훅을 사용함녀 된다
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
  • 이미지 갤러리를 만들고 이 갤러리를 클릭했을 때 state 를 사용하고 업데이트 하는 방법은 다음의 코드로 구현 가능하다
import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);
  const hasNext = index < sculptureList.length - 1;

  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    } else {
      setIndex(0);
    }
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i>
        by {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
    </>
  );
}

3. 렌더링하고 커밋하기

  • 컴포넌트가 화면에 표시되기 전에 컴포넌트들은 React 에서 렌더링해야 한다.
  • UI 를 요청하고 제공하는 과정은 세 단계로 이뤄진다
  1. Trigger
    • 렌더링 발동
  2. Render
    • 컴포넌트 렌더링
  3. Commit
    • 렌더링된 내용을 DOM 에 커밋

4. 여러 state 업데이트를 큐에 담기

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(score + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Score: {score}</h1>
    </>
  )
}
  • 이 컴포넌트에는 버그가 있다.
  • +3 버튼을 클릭해도 점수가 1씩밖에 증가하지 않는다 (기대하는 바는 +1+1+1 해서 총 +3 이 되길 원했던 것)
  • 이런 현상이 발생하는 이유에 대해서는 해당 챕터에서 다룰 예정
    • 간단히 설명하면 state 를 설정하면 새로운 렌더링을 요청하지만, 이미 실행 중인 코드에서는 변경하지 않는다
    • 즉,
    console.log(score);  // 0
    setScore(score + 1); // setScore(0 + 1);
    console.log(score);  // 0
    setScore(score + 1); // setScore(0 + 1);
    console.log(score);  // 0
    setScore(score + 1); // setScore(0 + 1);
    console.log(score);  // 0
    
    • 위의 상태가 지속되는 것
  • state 를 설정할 때 업데이터 함수를 전달하면 이 문제를 해결할 수 있다
import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(s => s + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Score: {score}</h1>
    </>
  )
}
  • 이렇게 업데이트 함수를 전달하면, 여러 state 업데이트를 큐에 넣는 방식으로 작동한다.

5. 객체 state 업데이트

  • state 는 객체를 포함한 모든 JavaScript 의 값을 보유할 수 있다.
  • 하지만, 객체 state 를 직접 변경해서는 안 된다.
  • 반드시, 새 객체를 생성하거나 기존 객체의 복사본을 만들어 다음의 해당 복사본을 사용하도록 Update 해야 한다
  • 일반적으로는 Spread Syntax(…) 를 사용한다.
import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: '<https://i.imgur.com/Sd1AgUOm.jpg>',
    }
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }

  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }

  return (
    <>
      
        Name:
        
      
      
        Title:
        
      
      
        City:
        
      
      
        Image:
        
      

{person.artwork.title} {' by '} {person.name}
(located in {person.artwork.city})

      
    
  );
}
  • 코드에서 객체를 복사하는 것이 지루하다면 Immer 와 같은 라이브러리의 사용도 추천할 만 하다.
import { useImmer } from 'use-immer';

export default function Form() {
  const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: '<https://i.imgur.com/Sd1AgUOm.jpg>',
    }
  });

  function handleNameChange(e) {
    updatePerson(draft => {
      draft.name = e.target.value;
    });
  }

  function handleTitleChange(e) {
    updatePerson(draft => {
      draft.artwork.title = e.target.value;
    });
  }

  function handleCityChange(e) {
    updatePerson(draft => {
      draft.artwork.city = e.target.value;
    });
  }

  function handleImageChange(e) {
    updatePerson(draft => {
      draft.artwork.image = e.target.value;
    });
  }

  return (
    <>
      
        Name:
        
      
      
        Title:
        
      
      
        City:
        
      
      
        Image:
        
      

{person.artwork.title} {' by '} {person.name}
(located in {person.artwork.city})

      
    
  );
}

6. 배열 state 업데이트

  • 배열은 state 에 저장할 수 있는 또 다른 유형의 Javascript 객체로 위의 객체처럼 읽기 전용으로 취급해야 한다
import { useState } from 'react';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [list, setList] = useState(
    initialList
  );

  function handleToggle(artworkId, nextSeen) {
    setList(list.map(artwork => {
      if (artwork.id === artworkId) {
        return { ...artwork, seen: nextSeen };
      } else {
        return artwork;
      }
    }));
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

 


Reference

https://react.dev/learn/adding-interactivity

 

Adding Interactivity – React

The library for web and native user interfaces

react.dev

https://react-ko.dev/learn/adding-interactivity

 

상호작용 추가하기 – React

The library for web and native user interfaces

react-ko.dev