ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Next.js Learn 문서 번역 #3] From JavaScript to React (React의 Components, Props, State 소개)
    Frontend 2022. 6. 5. 17:47

    https://unsplash.com/photos/KuCGlBXjH_o

     

    Next.js Learn 문서 일부분에 대한 한글 번역입니다.

     

    [원문 링크]

     


     

    From JavaScript to React (JavaScript에서 React까지) #3


    React Core Concepts

    리액트 어플리케이션을 구축하기 위해서 친숙해져야 할 리액트의 3가지 코어 컨셉은 다음과 같다.

    • Components
    • Props
    • State

    다음 섹션에서, 위 컨셉들을 하나씩 살펴보고 더 공부하기 위해서 필요한 자료들을 제공하려고 한다.

     

     

    Building UI with Components

    유저 인터페이스는 컴포넌트라고 불리는 작은 블록들로 쪼개질 수 있다. 컴포넌트는 독립적이고 재사용 가능한 코드 스니펫을 구축할 수 있도록 만들어준다. 컴포넌트를 레고 블록이라고 생각했을 때, 여러분은 개별적인 블록을 가져다가 결합하여 더 큰 구조물을 만들 수 있다. 만약 UI 조각들을 업데이트해야 한다면, 특정 컴포넌트 또는 블록을 업데이트할 수 있다.

     

     

    이러한 모듈성은 점점 커져가는 규모에도 코드를 더욱 유지가능하게 만들어주는데, 왜냐하면 어플리케이션의 나머지 부분을 건드리지 않고 쉽게 컴포넌트를 추가하거나 업데이트, 삭제할 수 있게 해주기 때문이다.

    리액트 컴포넌트의 좋은 점은 이것들이 단지 JavaScript라는 점이다. JavaScript 관점에서 리액트 컴포넌트를 어떻게 만들 수 있는지 살펴보자.

     

     

    Creating components

    리액트에서 컴포넌트는 함수이다. script 태그 안에, header라는 함수를 적어보자:

     

    <script type="text/jsx">
      const app = document.getElementById("app")
    
      function header() {
      }
    
      ReactDOM.render(<h1>Develop. Preview. Ship. 🚀</h1>, app)
    </script>

     

    컴포넌트는 UI 엘리먼트를 리턴하는 함수이다. 함수의 리턴문 안에는 JSX를 작성할 수 있다.

     

    <script type="text/jsx">
      const app = document.getElementById("app")
    
      function header() {
         return (<h1>Develop. Preview. Ship. 🚀</h1>)
       }
    
      ReactDOM.render(, app)
    </script>

     

    이 컴포넌트를 DOM에 렌더하기 위해, ReactDOM.render() 함수의 첫 번째 인자로 전달할 수 있다:

     

    <script type="text/jsx">
    
      const app = document.getElementById("app")
    
      function header() {
         return (<h1>Develop. Preview. Ship. 🚀</h1>)
       }
    
       ReactDOM.render(header, app)
    </script>

     

    그런데 만약 여러분이 위 코드를 브라우저에서 실행하려고 하면 아마 에러를 만날 것이다. 정상 작동되게 하려면 해야하는 작업이 두 가지가 있다:

    첫째, 리액트 컴포넌트를 순수 HTML과 JavaScript로부터 구별하기 위해 첫 글자를 대문자로 해야 한다.

     

    function Header() {
      return <h1>Develop. Preview. Ship. 🚀</h1>;
    }
    
    // Capitalize the React Component
    ReactDOM.render(Header, app);

     

    둘때, 리액트 컴포넌트를 사용할 때 보통의 HTML 태그를 사용하듯이 <> 꺾쇠 괄화를 사용해야 한다.

     

    function Header() {
      return <h1>Develop. Preview. Ship. 🚀</h1>;
    }
    
    ReactDOM.render(<Header />, app);

     

     

    Nesting Components

    어플리케이션은 보통 단일 컴포넌트보다는 더 많은 컨텐츠를 포함하고 있다. 따라서 리액트 컴포넌트 또한 보통의 HTML 엘리먼트와 같이 컴포넌트 안에 끼워 넣을 수 있다.

     

    예를 들어, <HomePage>라는 새 컴포넌트를 만들어보자:

     

    function Header() {
      return <h1>Develop. Preview. Ship. 🚀</h1>;
    }
    function HomePage() {
      return <div></div>;
    }
    
    ReactDOM.render(<Header />, app);

     

    그리고나서 <Header> 컴포넌트를 새 <HomePage> 컴포넌트 안에 끼워넣어보자:

     

    function Header() {
      return <h1>Develop. Preview. Ship. 🚀</h1>;
    }
    
    function HomePage() {
      return (
        <div>
          {/* Nesting the Header component */}
          <Header />
        </div>
      );
    }
    
    ReactDOM.render(<Header />, app);

     

     

    Component Trees

    계속해서 이런 방식으로 리액트 컴포넌트를 nesting하여 컴포넌트 트리를 형성할 수 있다.

     

     

    예를 들어, 상위 레벨의 HomePage 컴포넌트는 Header, Article, Footer 컴포넌트를 가지고 있을 수 있다. 그리고 각 컴포넌트들 또한 그들만의 자식 컴포넌트를 가지고 있을 수 있다. 예를 들어, Header 컴포넌트는 Logo, Title 그리고 Navigation 컴포넌트를 가진다.

    이러한 모듈 형식은 앱 내의 다른 장소에 컴포넌트를 재사용할 수 있게 만들어준다.

     

    이제 프로젝트에서 <HomePage>가 가장 상위 레벨 컴포넌트이고, 이 컴포넌트를 ReactDOM.render() 메소드에 전달할 수 있다.

     

    function Header() {
      return <h1>Develop. Preview. Ship. 🚀</h1>;
    }
    
    function HomePage() {
      return (
        <div>
          <Header />
        </div>
      );
    }
    
    ReactDOM.render(<HomePage />, app);

     

     

    Displaying Data with Props

    지금까지, <Header /> 컴포넌트를 계속 재사용해왔다면, 사용할 때마다 항상 같은 컨텐츠를 화면에 표시했을 것이다.

     

    function Header() {
      return <h1>Develop. Preview. Ship. 🚀</h1>;
    }
    
    function HomePage() {
      return (
        <div>
          <Header />
          <Header />
        </div>
      );
    }

     

    그런데 만약 다른 텍스트를 전달하고 싶었거나 또는 외부 소스로부터 데이터를 불러오기 때문에 어떤 정보가 들어갈 지 미리 알 수 없는 상황이라면 어떻게 해야 할까?

     

    보통의 HTML 엘리먼트는 엘리먼트의 행동을 변경하기 위해 정보 조각들을 전달할 수 있는 attribute들을 가지고 있다. 예를 들어서 <img> 엘리먼트의 src attribute를 변경하면, 화면에 보여지는 이미지를 변경할 수 있다. <a> 태그의 href attribute를 변경하면 링크의 목적지를 변경시킬 수도 있다.

     

    리액트 컴포넌트에도 같은 방법으로 정보 조각들인 property들을 전달할 수 있다. 이것들을 우리는 props라고 부른다.

     

    JavaScript 함수와 비슷하게, 커스텀 인자(또는 props)를 받아서 컴포넌트의 행동 양식을 바꾸거나 화면에 렌더될 때 시각적으로 보여지는 것을 바꾸도록 컴포넌트를 디자인할 수 있다. 그리고나서 이 props를 부모 컴포넌트로부터 자식 컴포넌트로 전달할 수 있다.

     

    노트: 리액트에서 데이터는 컴포넌트 트리의 아래 방향으로 흐른다. 이 것을 다음 섹션에서 얘기 나눌 단방향 데이터 흐름이라고 부르며 데이터가 부모 컴포넌트에서 자식 컴포넌트로 props로 전달될 수 있다.

     

     

    Using props

    HomePage 컴포넌트에서, HTML attribute를 전달하는 것처럼 커스텀 title prop을 Header 컴포넌트에 전달할 수 있다.

     

    // function Header() {
    //   return <h1>Develop. Preview. Ship. 🚀</h1>
    // }
    
    function HomePage() {
      return (
        <div>
          <Header title="React 💙" />
        </div>
      );
    }
    
    // ReactDOM.render(<HomePage />, app)

     

    그리고 자식 컴포넌트인 Header는 그 props를 함수의 첫 번째 파라미터로 받을 수 있다.

     

    function Header(props) {
    //   return <h1>Develop. Preview. Ship. 🚀</h1>
    // }
    
    // function HomePage() {
    //   return (
    //     <div>
    //       <Header title="React 💙" />
    //     </div>
    //   )
    // }
    
    // ReactDOM.render(<HomePage />, app)

     

    만약 console.log()로 props를 출력해보면, title 프로퍼티를 가진 객체인 것을 확인할 수 있을 것이다.

     

    function Header(props) {
        console.log(props) // { title: "React 💙" }
    //   return <h1>React 💙</h1>
    // }
    
    // function HomePage() {
    //   return (
    //     <div>
    //       <Header title="React 💙" />
    //     </div>
    //   )
    // }
    
    // ReactDOM.render(<HomePage />, app)

     

    props는 객체이기 때문에 object destructuring을 사용하여 함수 파라미터 안에서 명시적으로 props의 값들을 네이밍할 수 있다.

     

    function Header({ title }) {
        console.log(title) // "React 💙"
    //  return <h1>React 💙</h1>
    // }
    
    // function HomePage() {
    //   return (
    //     <div>
    //       <Header title="React 💙" />
    //     </div>
    //   )
    // }
    
    // ReactDOM.render(<HomePage />, app)

     

    그리고나서 <h1> 태그의 컨텐츠를 title 변수로 교체할 수 있다.

     

    function Header({ title }) {
      console.log(title);
      return <h1>title</h1>;
    }

     

    브라우저에서 이 프로젝트를 오픈하면, “title”이라는 단어를 디스플레이하고 있는 것을 볼 수 있을 것이다. 왜냐하면 리액트는 DOM에 단순 텍스트 string을 렌더하는 것이 여러분의 의도라고 생각할 것이기 때문이다.

    따라서 리액트에게 이것이 JavaScript 변수라는 것을 나타낼 필요가 있다.

     

     

    Using Variables in JSX

    여러분이 정의한 변수를 사용하기 위해서는 {} 괄호를 사용해야 하는데, 이 괄호는 JSX 마크업 안에 직접적으로 보통의 JavaScript를 작성할 수 있도록 해주는 특별한 JSX 문법이다.

     

    // function Header({title}) {
    //  console.log(title)
    return <h1>{title}</h1>;
    // }

     

    괄호를 “JSX 영토" 안에서 “JavaScript 영토" 안으로 들어가게 해주는 입구라고 생각하면 된다. 괄호 안에는 아무 JavaScript 표현식(단일 value로 계산되는 어떤 것)을 추가할 수 있다. 예를 들어:

     

    앞에서 다뤘듯이, dot notation과 함께 객체 속성을 사용할 수도 있다:

     

    function Header(props) {
      return <h1>{props.title}</h1>;
    }

     

    template literal:

    function Header({ title }) {
      return <h1>{`Cool ${title}`}</h1>;
    }

     

    함수의 리턴 값:

    function createTitle(title) {
      if (title) {
        return title;
      } else {
        return 'Default title';
      }
    }
    
    function Header({ title }) {
      return <h1>{createTitle(title)}</h1>;
    }

     

    그리고 삼항 연산자:

    function Header({ title }) {
      return <h1>{title ? title : 'Default Title'}</h1>;
    }

     

    이제 여러분은 어떤 string이든 title prop으로 전달할 수 있고, 심지어 default case를 삼항연산자로 계획해두었기 때문에 title prop으로 아무것도 전달하지 않아도 된다:

     

    function Header({ title }) {
      return <h1>{title ? title : 'Default title'}</h1>;
    }
    
    function Page() {
      return (
        <div>
          <Header />
        </div>
      );
    }

     

    컴포넌트는 이제 어플리케이션의 다른 부분에 재사용할 수 있도록 포괄적인 title prop을 받을 수 있다. 이제 여러분이 할 일은 단지 title을 변경하는 것 뿐이다.

     

    function Page() {
      return (
        <div>
          <Header title="React 💙" />
          <Header title="A new title" />
        </div>
      );
    }

     

     

    Iterating through lists

    리스트 형태로 보여줘야 하는 데이터를 가지는 것은 흔한 일이다. 배열 메소드로 데이터를 조작하고, 스타일은 통일되지만 다른 정보 조각들을 가지는 UI 엘리먼트를 생성할 수 있다.

     

    노트: 리액트는 데이터 fetching에 있어서는 독선적이지 않다. 무슨 뜻이냐면 여러분에게 맞는 어떠한 솔루션이든 고를 수 있다는 뜻이다. 나중에 Next.js에서의 data fetching 옵션들에 대해서 얘기해볼 것이다. 그러나 지금은 데이터를 보여주기 위한 단순한 배열을 사용할 것이다.

     

    HomePage 컴포넌트에 이름 배열을 추가해보자:

     

    function HomePage() {
      const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
    
      return (
        <div>
          <Header title="Develop. Preview. Ship. 🚀" />
        </div>
      );
    }

     

    이제 array.map() 메소드를 사용하여 배열을 순회하고 화살표 함수를 사용하여 리스트 아이템 이름을 매핑할 것이다.

     

    function HomePage() {
      const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
    
      return (
        <div>
          <Header title="Develop. Preview. Ship. 🚀" />
          <ul>
            {names.map((name) => (
              <li>{name}</li>))}
          </ul>
        </div>
      );
    }

     

    괄호를 사용하여 어떻게 “JavaScript”와 “JSX” 영토 사이를 누비고 지나갔는지 주목해보자.

     

    이 코드를 실행하면, 리액트는 missing key prop에 대한 경고 메시지를 줄 것이다. 왜냐하면 리액트는 배열 아이템을 식별하여 DOM 엘리먼트를 업데이트하기 위해 고유의 식별값을 필요로 하기 때문이다.

     

    지금은 일단 이름이 고유하기 때문에 이름을 그대로 사용할 수 있지만, 아이템 ID와 같이 고유성이 보장되는 값을 사용하는 것을 권장한다.

     

    function HomePage() {
      const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
    
      return (
        <div>
          <Header title="Develop. Preview. Ship. 🚀" />
          <ul>
            {names.map((name) => (
              <li key={name}>{name}</li>))}
          </ul>
        </div>
      );
    }

     

     

    Adding Interactivity with State

    리액트가 어떻게 state 그리고 event handler를 통해 어플리케이션의 상호 작용성을 더해주는지 탐험해보자.

    한가지 예로, 프로젝트에 좋아요 버튼을 만들어보자. 첫째로 코드에 버튼 엘리먼트를 추가한다:

     

    function HomePage() {
      const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
    
      return (
        <div>
          <Header title="Develop. Preview. Ship. 🚀" />
          <ul>
            {names.map((name) => (
              <li key={name}>{name}</li>))}
          </ul>
    
          <button>Like</button>
        </div>
      );
    }

     

     

    Listening to Events

    버튼이 클릭됐을 때 뭔가를 하게 만드려면, onClick 이벤트를 사용하면 된다.

     

    function HomePage() {
      // ...
      return (
        <div>
          {/* ... */}
          <button onClick={}>Like</button>
        </div>
      );
    }

     

    리액트에서 이벤트 이름은 camelCase로 작성된다. onClick 이벤트는 유저 인터랙션에 응답하기 위해 사용 가능한 여러 이벤트 중 하나이다. 예를 들어, input 필드를 위해서 onChange 이벤트를 사용하거나 form을 위해서 onSubmit 이벤트를 사용할 수도 있다.

     

     

    Handling Events

    여러분은 이벤트가 트리거되었을 때 해당 이벤트를 “핸들링"할 함수를 정의할 수도 있다. 컴포넌트의 리턴문 전에 handleClick()이라는 함수를 만들자:

     

    function HomePage() {
      // ...
    
      function handleClick() {
        console.log("increment like count")
      }
    
      return (
        <div>
          {/* ... */}
          <button onClick={}>Like</button>
        </div>
      )
     }

     

    그러면 이제 onClick 이벤트가 트리거될 때 handleClick 함수를 호출할 수 있게 된다:

     

    function HomePage() {
      //    ...
      function handleClick() {
        console.log('increment like count');
      }
    
      return (
        <div>
          {/* ... */}
          <button onClick={handleClick}>Like</button>
        </div>
      );
    }

     

     

    State and Hooks

    리액트는 hook으로 불리는 함수 세트를 가지고 있다. Hook은 컴포넌트에 state와 같은 부가적인 로직을 추가할 수 있도록 해준다. state는 UI상에서 보통 유저 인터랙션에 의해 트리거되는 시간에 따라 변화하는 정보라고 할 수 있다.

     

     

    state를 유저가 좋아요 버튼을 몇 번 클릭한 만큼 숫자를 증가시키고 저장하는데 사용할 수 있다. 사실, 리액트에서 이러한 것들을 관리하는 hook을 useState()라고 한다.

     

    function HomePage() {
      React.useState();
    }

     

    useState()는 배열을 리턴하고 array destructuring을 사용하여 컴포넌트 안에서 배열의 값에 접근할 수 있다.

     

    function HomePage() {
      const [] = React.useState();
    
      // ...
    }

     

    배열의 첫 번째 아이템은 state value이고, 어떤 이름이든 마음대로 네이밍할 수 있다. 어떤 값인지 설명할 수 있는 이름을 사용하는 것을 추천한다.

     

    function HomePage() {
      const [likes] = React.useState();
    
      // ...
    }

     

    배열의 두 번째 아이템은 해당 값을 업데이트할 수 있는 함수이다. 함수 이름 또한 아무거나 정할 수 있지만, set이라는 prefix와 함께 업데이트할 state 이름을 그대로 사용하는 것이 보통이다.

     

    function HomePage() {
      const [likes, setLikes] = React.useState();
    
      // ...
    }

     

    likes state의 초기 값 또한 정할 수 있다: 숫자 0.

     

    function HomePage() {
      const [likes, setLikes] = React.useState(0);
    }

     

    그러면 컴포넌트 안에서 state 변수를 사용하여 초기 state가 잘 작동하는지 확인해볼 수 있다.

     

    function HomePage() {
      // ...
      const [likes, setLikes] = React.useState(0);
    
      return (
        // ...
        <button onClick={handleClick}>Like({likes})</button>
      );
    }

     

    마침내, 이 전에 정의한 handleClick() 함수 안에서 state 업데이트 함수(setLikes)를 호출할 수 있다.

     

    function HomePage() {
      // ...
      const [likes, setLikes] = useState()
    
      function handleClick() {
        setLikes(likes + 1)
      }}
    
      return (
        <div>
          {/* ... */}
          <button onClick={handleClick}>Likes ({likes})</button>
        </div>
      )
    }

     

    버튼을 클릭하면 이제 handleClick 함수가 실행될 것이고, 현재의 좋아요 값에 1을 더한 함수 매개변수와 함께 setLikes라는 state 업데이터 함수가 실행될 것이다.

    노트: 컴포넌트 함수의 첫 번째 파라미터로 전달되는 props와 달리, state는 컴포넌트 안에서 초기화되고 저장된다. state 정보를 자식 컴포넌트에 props로 전달 할 수 있지만, state값을 업데이트하는 로직은 state가 최초 생성된 컴포넌트 안에 위치해야 한다.

     

     

    Managing State

    위 내용은 state의 소개글 정도이고, state를 다루는 것과 리액트 어플리케이션의 데이터 플로우에 대해서 더 공부할 것이 많다. 더 공부하려면 리액트 문서의 Adding Interactivity와 Managing State 섹션을 살펴보는 것을 추천한다.

     

     


    Quick Review

    props와 state의 차이점은 무엇인가?

    더보기

    ⇒ Props은 컴포넌트로 전달되는 read-only 정보이다. State는 주로 유저 인터랙션으로 트리거되는 시간에 따라 변화되는 정보이다.

     

    반응형

    COMMENT