avatar
Ganymedian
#2. npm create vite@latest & first hooks
Jun 25
·
35 min read

#2. npm create vite@latest & first hooks

#챕터 목차

  1. npm create vite@latest

  2. Hello World React

  3. basic hooks : useState

  4. basic hooks : useEffect

  5. Virtual DOM & StrictMode

  6. Components & Props

  7. React Dev-Tools

  8. first project : your_name

npm create vite@latest

이번 챕터의 제목은, CLI 의 커맨드 명령어입니다. 두둥!! 의미심장하죠?

이것은 무려, 리액트 프로젝트를 생성하는 node 명령어입니다.

자 이제 본격적으로 React 프로젝트를 생성해 봅시다.

React, Vue, Astro 등등, 프레임웍 기반 JS 프로젝트는, 빌드 패키지를 포함한, 프로젝트 생성 패키지에 의존해서 시작하는 게 일반적입니다. 백지상태에서 Babel 등 모든 걸 일일이 install 하고 셋팅하려면, 그것만 해도 한숨 나오는 일이고, 또 그 중 하나에서 버전 업 이벤트가 발생한다면 프로젝트 생성 프로세스 전체를 수정해야 하죠. 개발자 세계관에서는 있을 수 없는 일입니다. 그래서 프로젝트 생성 패키지가 나왔고, 지금은 모두가 이를 사용하고 있습니다.

이 글이 작성되고 있는 2024.06 기준으로, 1년 전 까지만 해도 create-react-app 빌드 패키지가 가장 많이 사용되었지만, 그간 관리상 이슈도 있었고, 현재는 많은 사람들이 vite 로 옮겨간 상황입니다. 최근에 CRA 의 관리가 뒤늦게 다시 정상화 되긴 했지만, 이전의 인지도나 점유율을 회복하기에는 어려움이 있어 보입니다. 현재 react 프로젝트 생성 패키지는 vite 와 next 가 대세를 이루고 있는 것 같습니다.

CRA 는, 소스코드의 변경이 있을 때마다 rebuild 가 발생하는데 반해, Vite 는 그렇지 않다고 합니다. CRA 를 사용할 때에는 속도에 대해 민감하게 살펴보지 않았어서 잘 모르겠지만, 확실히, vite 프로젝트에서는, 브라우저로 눈을 돌리기도 전에 변화가 이미 반영되어 있는 것 같습니다.

앞으로 우리의 프로젝트에서는, vite 를 사용하고, 패키지 매니저로는 npm 을 사용하겠습니다. bun, pnpm 등이 익숙하신 분은 익숙한 것을 사용하세요.

참고로, vite 는 [vait] 가 아니라, [vi:t] 로 읽습니다. ‘빠르다’ 는 뜻의 french 라고 해요. 개발자의 공식 언급이 있었다고 합니다. zustand 와는 다르게, vite 는 모두가 비-트 라고 읽고 있습니다. 잘못 발음하면 무식.. 위험합니다.

프로젝트 생성

먼저, 컴퓨터에 작업용 폴더를 만들고 그 안에 우리가 진행할 프로젝트인 react-project 폴더를 생성합니다.

그리고 빈 상태인, react-project 안으로 이동해서,

// cd c:\\Local WorkSpace\\ionian-react-project\\ionian-react-project
// 마지막 점 . 은, 현재 폴더 명을 의미합니다. 
// 점 하나 안찍어주면 프로젝트명을 타이핑해줘야 합니다.
# npm create vite@latest . 
// 혹시 누락된 라이브러리가 있다면 추가 인스톨
# npm i

이렇게 프로젝트를 생성한 후,

// cd c:\\Local WorkSpace\\ionian-react-project\\ionian-react-project
# code .

vscode 를 실행합니다.

이제,

// cd c:\\Local WorkSpace\\ionian-react-project\\ionian-react-project
# npm run dev

를 실행하면 개발 웹서버가 구동되고 로컬호스트의 서버 주소가 포트넘버와 함께 콘솔창에 보여질 것입니다.

http://localhost:3000

아마도 포트번호는 다르지만 로컬 웹서버의 주소는 같은 형태일 것입니다.

해당 주소를 Ctrl + Click 해서 브라우저를 띄우고 개발, 디버깅을 진행할 수 있습니다.

Vite 와 그 번들은 앱 코드의 변화를 실시간 감시하고 있으므로, 소스코드가 바뀔 때마다 매번 re-run 이나 reload 해줄 필요는 없습니다.

Hello World React

이제 감동의 Hello World 를 출력해봅시다.

vsCode 의 폴더 창을 보면,

/src/main.jsx

/src/App.jsx

등의 파일 목록이 보일 것입니다.

이제,

/src

폴더 내에서 main.jsx, App.jsx 를 제외한 나머지 파일과 폴더들을 모두 del 을 눌러서 제거해줍니다.

그리고,

App.jsx 를 다음 처럼 바꿔주고 save 합니다.

// App.jsx
// Hello World React
function App() {
  return (
    <>
      <h1>Hello World React</h1>
    </>
  );
}

export default App;

띄워놓은 브라우저에 Hello World React 가 보여야 정상입니다.

basic hooks : useState,

React의 useState 훅은 함수형 컴포넌트에서 상태 를 관리하기 위해 사용됩니다.

useState상태 와 해당 상태를 갱신할 수 있는 함수 를 반환합니다.

우리의 Hello World React 에, 이제 애플리케이션의 모양을 갖춰지도록 만들어 봅시다.

// App.jsx
import Counter from "./study/components/Counter";

// Hello World React & Counter
function App() {
  return (
    <>
      <h1>Hello World React</h1>
      <Counter />
    </>
  );
}

export default App;

/src/study/components 폴더를 만들어주고,

Counter.jsx 파일을 생성해서 다음과 같이 입력한 후 save 해줍니다.

띄워진 브라우저에서 우리의 웹앱이 정상 표시 되어야 합니다. 버튼도 클릭해 봅시다.

// useState 예제 Counter.jsx
import { useState } from 'react';

function Counter() {
	// useState 훅은, 관리대상 state 와 setState 펑션 튜플을 리턴합니다.
	// 이 구문은 일단 외워두시는 게 좋습니다.
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default Counter;

이 예제에서는 count라는 상태 변수를 정의하고, 버튼을 클릭할 때마다 count를 1씩 증가시킵니다.

  1. useState 의 State

    1. const [state, setState] = useState(defaultValue) 형식을 취합니다.

    2. 리액트에서 ‘상태’ State 는, 원칙적으로 useState 에서만 생성됩니다.(useReducer 와 같은 복합상태를 생성하는 훅도 있지만, 일단 여기서는 이렇게 이해하고 넘어가겠습니다.)

    3. 리액트에서 ‘상태’ State 의 값에 변화가 발생하면 해당 state 가 사용되는 컴포넌트가 re-rendering 되면서 변경된 State 의 값이 출력에 업데이트 됩니다.

  2. Counter 컴포넌트 코드에서, 앱이 작동하는 방식은 다음과 같습니다.

    1. 상태 count 에 ‘0’ 이 할당된 UI DOM 이 생성되고 화면에 출력됩니다.

    2. 사용자가 ‘Click me’ 버튼을 클릭하면 onClick={….} 에 할당된 코드가 실행됩니다.

    3. onClick 에는, setCount(count + 1) 이라는 코드가 할당되어 있으므로 해당 코드가 실행됩니다.

    4. Counter 컴포넌트가 마운팅 되는 시점에, count 에는 0 이 할당되어 있으므로, 첫 버튼 클릭에서는 setCount(0 + 1) 이라는 코드가 실행될 것입니다.

    5. setCount 는 useState 가 리턴한, state 상태 변수를 변경시키는 함수 이므로, count 라는 상태 변수에는 ‘1’ 이 새롭게 할당됩니다.

    6. Counter 컴포넌트에서 사용되는 State, ‘상태’ 에 변화가 발생하였으므로, Counter 컴포넌트는 리렌더링 되면서 업데이트된 상태 변수, count 값 ‘1’ 을 유지하고 화면에 반영합니다.

  3. onClick = {someFunction} & onClick={() => someFunction()} 의 차이점과 주의사항

    1. onClick = {someFunction} 이 코드는 클릭시에 해당 펑션이 즉시 실행되지만,

    2. onClick={() => someFunction()} 이 코드는 중요한 차이점을 갖습니다.

      1. 랜더링 될 때마다 새로운 펑션을 생성합니다.

      2. 아규먼트, 파라메터의 전달이 가능해집니다.

  4. Counter 컴포넌트에서는, setCount(count + 1) 를 사용했는데, 이는 사실 잘못된 사용방식입니다.

    1. setCount(count + 1) 는, count 라는 현재의 ‘상태’ 에 1 을 더하는 코드인데,

    2. 잘못된 setState 와, 맞게 쓴 setState 의 사례…

    setCount(count + 1)
    setCount(count + 1)
    // count : 2 를 기대하지만, count 는 여전히 1 입니다.
    // setCount 는 마지막 변화/ 업데이트 만을 반영합니다.
    // 그러나,
    setCount((c) => c + 1)
    setCount((c) => c + 1)
    // count : 2 이 됩니다.
    // setCount 에게 전달된 업데이트 밸류는, 새롭게 생성된 펑션 코드이기 때문입니다.
    

basic hooks : useEffect

React의 useEffect 훅은 사이드 이펙트를 수행하기 위해 사용됩니다. 컴포넌트가 렌더링될 때마다 특정 작업을 수행할 수 있으며, 이를 통해 데이터 fetching, 구독, DOM 조작 등을 처리할 수 있습니다.

useEffect 훅은 두 가지 인자를 받습니다: 실행할 함수와 의존성 배열. 의존성 배열은 이펙트가 다시 실행될 조건을 지정하며, 배열 안의 값이 변경될 때마다 이펙트가 다시 실행됩니다. 의존성 배열이 비어있으면 이펙트는 컴포넌트가 마운트될 때 한 번만 실행됩니다.

우리의 웹앱 애플리케이션의 기능을 조금 확장해봅시다.

// App.jsx
import Counter from "./study/components/Counter";
import Timer  from "./study/components/Timer";

// Hello World React & Counter & Timer
function App() {
  return (
    <>
      <h1>Hello World React</h1>
      <Counter />
      <Timer />
    </>
  );
}

export default App;

/src/study/components 폴더에 이번엔,

Timer.jsx 파일을 생성해서 다음과 같이 입력한 후 save 해줍니다.

// Timer.jsx
import { useState, useEffect } from 'react';

function Timer() {
  const [count, setCount] = useState(0);

  // useEffect 훅은 컴포넌트가 렌더링될 때마다 실행됩니다.
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    // Clean up 함수는 컴포넌트가 언마운트될 때 호출됩니다.
    return () => clearInterval(timer);
  }, []); // 빈 배열을 전달하면, 이펙트는 컴포넌트가 마운트될 때 한 번만 실행됩니다.

  return (
    <div>
      <p>Timer: {count} seconds</p>
    </div>
  );
}

export default Timer;

이 예제에서는 useEffect를 사용하여 1초마다 count를 증가시키는 타이머를 구현합니다. 컴포넌트가 언마운트될 때 clearInterval을 호출하여 타이머를 정리합니다.

  1. useEffect의 기본 구조와 작동 방식

    1. useEffect(() => { ... }, [dependencies]) 형식을 취합니다.

    2. 첫 번째 인자는 실행할 함수입니다.

    3. 두 번째 인자는 의존성 배열로, 배열 안의 값이 변경될 때마다 이펙트가 다시 실행됩니다.

    4. 의존성 배열을 생략하면, 이펙트는 매번 렌더링될 때마다 실행됩니다.

    5. 의존성 배열이 빈 배열이면, 이펙트는 컴포넌트가 마운트될 때 한 번만 실행됩니다.

  2. Timer 컴포넌트 코드에서, 앱이 작동하는 방식은 다음과 같습니다.

    1. 상태 count0이 할당된 UI DOM이 생성되고 화면에 출력됩니다.

    2. useEffect 훅이 실행되어 1초마다 count를 증가시키는 타이머가 설정됩니다.

    3. 1초마다 setCount가 호출되어 count가 증가합니다.

    4. count 상태가 변경되면 컴포넌트가 리렌더링되어 업데이트된 count 값이 화면에 반영됩니다.

    5. 컴포넌트가 언마운트될 때 clearInterval이 호출되어 타이머가 정리됩니다.

useEffect 의 Cleanup Function

이 주제는 다소 어려울 수 있습니다. 지금 혼란스러우시면 이 파트는 스킵하시고, 나중에 내공이 다져진 후 다시 돌아와서 읽어도 좋습니다.

useEffect에서 반환하는 함수는 cleanup 함수로, 컴포넌트가 언마운트되거나 이펙트가 다시 실행될 때 호출됩니다. 이를 통해 불필요한 리소스를 해제하거나 정리 작업을 수행할 수 있습니다.

Cleanup function 은, 특히 외부 API 등 Async 작업도중 사용자의 재클릭 등 이벤트 처리에 매우 유용합니다.

Fetch 요청 등, 외부 API 와 통신이 발생될 때, 네트워크 사정에 임시적 문제가 발생하면, 사용자는 무심결에 반복 클릭을 누르기도 하죠. 이 때, 클린업 펑션이 없다면, 사용자가 누른 fetch API 요청이 무한히 반복해서 쌓일 수 있겠죠.

아래는 AbortController 와 클린업 펑션을 사용한, 요청취소 처리의 예제 코드입니다.

// FetchData
import { useState, useEffect } from 'react';

function FetchData() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    fetch('<https://api.example.com/data>', { signal })
      .then(response => response.json())
      .then(data => setData(data))
      .catch(error => {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          console.error('Fetch error:', error);
        }
      });

    // Cleanup 함수에서 fetch 요청을 중단합니다.
    return () => controller.abort();
  }, []); // 빈 배열을 전달하면, 이펙트는 컴포넌트가 마운트될 때 한 번만 실행됩니다.

  return (
    <div>
      {data ? <p>Data: {JSON.stringify(data)}</p> : <p>Loading...</p>}
    </div>
  );
}

export default FetchData;

이 예제에서는 fetch 요청을 수행하고, 컴포넌트가 언마운트될 때 요청을 중단하는 cleanup 함수를 정의합니다.

useEffect가 반환하는 함수가 cleanup 함수로 작동하여 fetch 요청이 중단되도록 하여, 사용자가 fetch 요청을 클릭할 때마다 AbortController 가 작동해서 직전의 fetch 요청을 취소 Abort 하도록 하였습니다.

  1. Cleanup 함수의 역할과 필요성

    1. useEffect에서 반환되는 함수는 cleanup 함수입니다.

    2. 컴포넌트가 언마운트되거나 이펙트가 재실행되기 전에 cleanup 함수가 호출됩니다.

    3. cleanup 함수는 타이머를 정리하거나, 구독을 해제하거나, 네트워크 요청을 중단하는 등 리소스를 정리하는 데 사용됩니다.

Virtual DOM & StrictMode

Virtual DOM

React는 Virtual DOM을 사용하여 실제 DOM 조작을 최소화하고 성능을 향상시킵니다.

Virtual DOM은 가상으로 메모리에 존재하는 DOM 트리로, 상태가 변경될 때마다 새로운 Virtual DOM을 생성하고 이전 Virtual DOM과 비교하여 최소한의 실제 DOM 변경을 수행합니다.

StrictMode

React의 StrictMode는 잠재적인 문제를 감지하고 경고를 출력하는 개발 도구입니다.

vite 로 프로젝트를 생성하면, 시작 스크립트 격인, /src/main.jsx 가 생성되는데

// /src/main.jsx
import React from "react";
import ReactDOM from "react-dom/client";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

이렇게 디폴트로 StrictMode 가 적용되어 있습니다.

StrictMode는 렌더링을 한 번 더 추가로 발생시킴으로서, 사이드 이펙트 코드가 제대로 동작하는지 한번 더 확인하도록 해줍니다. StrictMode 는 오직 개발단계에서만 그 역할을 수행하고, Build 시점에는 제거 됩니다.

간혹 3rd 파티 ORM 등과의 연계 코드에서 StrictMode 가 예기치 않은 문제를 일으키는 경우가 발생하기도 합니다. 이를테면 IndexedDB 에서 DBConn 이 두번의 연속 렌더링으로 인해 두번 연속 DBConn 요청이 발생하면서 DB 가 Lock 되어 버리는 경우가 발생하는데, 이런 경우, DBConn 을 관리하는 전역 변수를 컴포넌트 바깥에 두고 DBConn 의 추가 컨넥션을 방지하는 등의 방법을 사용할 수 있습니다.

이렇게 다소간의 불편을 감수하게 되더라도, StrictMode 는 되도록 끄지 않는 게 좋습니다.(건들지 마시오)

IndexedDB 중복 컨넥션 문제와 전역 변수를 통한 해결 예제

이 주제도 다소 난해하거나 불필요하다고 느끼실 수 있습니다. 필요에 따라 스킵하셔도 무방합니다.

IndexedDB를 사용할 때 StrictMode로 인해 두 번 렌더링이 발생하면 중복 컨넥션 문제가 발생할 수 있습니다.

이를 해결하기 위해 전역 변수를 사용하여 DB 연결을 한 번만 설정하는 방법을 사용할 수 있습니다.

// IndexedDBComponent & openIndexedDB
import React, { useEffect, useState } from 'react';

// IndexedDB 연결을 위한 전역 변수
let dbConnection = null;

function openIndexedDB() {
  return new Promise((resolve, reject) => {
    if (dbConnection) {
      resolve(dbConnection);
      return;
    }

    const request = indexedDB.open('myDatabase', 1);

    request.onupgradeneeded = (event) => {
      const db = event.target.result;
      db.createObjectStore('myStore', { keyPath: 'id' });
    };

    request.onsuccess = (event) => {
      dbConnection = event.target.result;
      resolve(dbConnection);
    };

    request.onerror = (event) => {
      reject(event.target.error);
    };
  });
}

function IndexedDBComponent() {
  const [data, setData] = useState([]);

  useEffect(() => {
    let isMounted = true;

    openIndexedDB().then((db) => {
      const transaction = db.transaction('myStore', 'readonly');
      const store = transaction.objectStore('myStore');
      const request = store.getAll();

      request.onsuccess = (event) => {
        if (isMounted) {
          setData(event.target.result);
        }
      };

      request.onerror = (event) => {
        console.error('Failed to retrieve data:', event.target.error);
      };
    });

    return () => {
      isMounted = false;
    };
  }, []);

  return (
    <div>
      <h1>IndexedDB Data</h1>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.value}</li>
        ))}
      </ul>
    </div>
  );
}

export default function App() {
  return (
    <React.StrictMode>
      <IndexedDBComponent />
    </React.StrictMode>
  );
}

이 예제에서 dbConnection 변수를 전역으로 선언하고, openIndexedDB 함수에서 이를 재사용하여 중복 연결을 방지합니다. useEffect 훅에서 컴포넌트가 마운트될 때 IndexedDB에 연결하고 데이터를 가져옵니다. isMounted 변수는 컴포넌트가 언마운트된 후에 비동기 작업이 완료되더라도 상태 업데이트를 방지하기 위해 사용됩니다.

이렇게 하면 StrictMode가 활성화된 상태에서도 중복 연결 문제를 방지하면서 안전하게 IndexedDB를 사용할 수 있습니다.

Components & Props

React 프로젝트에서 컴포넌트는 기능을 담당하는, 재사용 가능한 UI 의최소 구성요소입니다.

모든 컴포넌트의 이름은 대문자로 시작합니다.

React 의 State 는 해당 State 가 생성된 컴포넌트 내에서만 유효하고, 소속 컴포넌트 바깥에서는 사용할 수 없습니다.

하지만 펑션에서의 파라메터 처럼, 컴포넌트간 state 와 변수, 그리고 펑션의 전달도 가능한데, 이를 Properties 줄여서 Props라고 부릅니다.

컴포넌트는 props를 통해 부모 컴포넌트로부터 데이터를 받을 수 있습니다.

// App
function App() {
  return (
    <div>
      <Greeting name="Alice" />
      <Greeting name="Bob" />
    </div>
  );
}

// Greeting
function Greeting(props) {
  return <h1>Hello, {props.name}</h1>;
}

export default App;

이 예제에서는 Greeting 컴포넌트가 name이라는 props를 받아서 화면에 출력합니다.

propsstate 일 수도 있고 일반 변수일 수도 있고, function 일 수도 있습니다.

Prop Drilling

컴포넌트 App 에서 App2 컴포넌트로, 또다시 App3 컴포넌트로… 무한히 프롭을 전달하면서 겪게 되는 혼란을 프롭-드릴링 이라고 부릅니다.

중간에 해당 프롭을 필요로 하지도 않는 컴포넌트를 거치게 되면, 코드도 난삽해지고, 집중력도 흐트러지죠.

그런데 더 큰 문제는, 프롭으로 상태 변화를 발생시키는 펑션이 전달되어야 할 때와,

  • AAA — AAB — AAC,

  • AAA — ABB —- ABC

이렇게 여러 갈래로 프롭이 뻗어져 나가야 하는 상황에서 더 큰 혼란이 옵니다.

만약에 ABC 컴포넌트에서 상태변화가 발생할 때, AAC 에서 그 변화된 상태값을 I/O 처리해야 한다면요?

이런 문제를 겪어온 리액트 생태계에서는 context, redux, zustand, recoil 등의 내부 훅이나 3rd 파티 라이브러리가 사용되고 있습니다.

이 문제에 대해서는 다음 강 #3. more hooks & custom hooks 의 한 챕터를 통해 자세히 알아보기로 하고 여기서는 이쯤 해서 마무리 짓겠습니다.

React Dev-Tools

크롬, FF 의 React Developer Tools 는 다양한 방법으로 개발자의 리액트 개발을 돕습니다.

React Developer Tools 는 브라우저에서 F11 키를 누르고 개발자툴 을 열어서 사용할 수 있는데,

설치하게 되면, 개발자툴 환경에 Components 와 Profiler 탭이 추가됩니다.

Components 탭에서 현재 개발중인 컴포넌트의 요소를 선택하면 해당 컴포넌트의 프롭, state, 렌더링 현황 등 자세한 정보를 모니터링 할 수 있습니다.

또한 프롭, 상태의 값을 강제지정할 수도 있어서 상태변화화 프롭 변화에 따른 브라우저의 출력 변화도 미리 확인할 수 있죠.

또한, 개발자 툴 창이 활성화 된 환경에서는 브라우저에서 리렌더링이 표현됩니다.

테두리가 표시되었다가 사라지는 방식으로 각각의 컴포넌트가 렌더링 되는 이벤트가 표현되는데, 이 간단한 표시로 렌더링 문제를 미리 감지할 수 있습니다.

다운로드for Chrome :

https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi

현재 400만 다운로드 수를 기록하고 있습니다. 세계의 모든 리액트 디벨로퍼들이 사용하고 있다고 해도 과장은 아니겠죠.. 꼭 설치해 두세요.

first project : your_name

이제, 배운 것들을 토대로, 과제를 해봅시다.

되도록 직접 해보시는 걸 강력히 권장하고, GPT, Claude 등의 도움은 받으셔도 좋습니다. 다만, 되도록이면 직접 충분히 시도해 본 후에 디테일한 영역이나 코드가 막히는 부분만 도움을 받으세요.

개고생 한 만큼만 근육이 붙기 마련입니다.

자, 첫번째 과제 입니다.

  1. ChildForExercise1 컴포넌트가 re-render 될 때마다 console.log(”rerendering Component”) 가 실행되도록 하십시오.

  2. ChildForExercise1 컴포넌트가 마운트될 때, console.log(”Hi at mounting..”) 가 실행되도록 하십시오.

  3. name 과 age 가 변경 될 때마다 화면의 출력에 반영되도록 하십시오.

  4. document.title 에 name 과 age 가 반영되도록 name, age 의 변화에 연동시키십시오.

심화 과제

  • 디폴트 코드에서는, name 필드의 값이 변경될 때마다, 즉시 컴포넌트의 리-렌더링이 발생합니다. 하지만, 이 작동방식이 외부의 API 에게로 이어진다면 심각한 문제로 이어질 수도 있습니다.

  • name 필드의 값이 변경된 직후라도, 1초의 인터벌을 두고 컴포넌트의 리-렌더링이 일어나도록 관리할 수 있을 것입니다. 외부 API 에게 ‘자동완성’ 과 같은 질의를 해야 하는 경우, 이런 알고리즘은 꼭 필요할 것입니다.

  • 만약, 사용자가 0.1초 간격으로 타이핑 했을때, 모든 타이핑 입력 이벤트를, 비용이 소모되는 API 요청으로 이어줘서는 안될 것입니다.

  • name 필드에서 입력값에 변화가 발생했을 때, 최소 1초의 인터벌을 두고 상태변화로 이어지도록 코딩하십시오. useEffect 의 cleanup 펑션 사례에서 힌트를 찾을 수 있을 것입니다.

컴포넌트 구조:

/App.jsx — 이 과제에서는 아무런 영향력을 끼치지 않습니다. Exercise1 을 출력 영역으로 불러오는 역할만 합니다.

/exercises/Exercise1.jsx —

/exercises/components/ChildForExercise1.jsx —

과제 준비…

src/App.jsx 를 이렇게 바꿔줍니다.

// App.jsx
import Exercise1 from "./src/exercises/Exercise1";

// UseEffectExercise
function App() {
  return (
    <>
      <h1>UseEffectExercise</h1>
      <Exercise1 />
    </>
  );
}

export default App;

그리고,

/src/exercises/Exercise1.jsx

파일을 생성하고 다음처럼 저장합니다.

// UseEffectExercise
import ChildForExercise1 from "./components/ChildForExercise1"

const Exercise1 = () => {
  const [show, setShow] = useState(true);

  const childComponent = show ? <ChildForExercise1 /> : null;

  return (
    <div>
      <button onClick={() => setShow((currentShow) => !currentShow)}>
        Show/Hide
      </button>
      {childComponent}
    </div>
  );
};

export default Exercise1;

/src/exercises/components/ChildForExercise1.jsx 를 만들어줍니다.

import React from "react";
import { useState, useEffect } from "react";

export function ChildForExercise1() {

  // 1. ChildForExercise1  컴포넌트가 re-render 될 때마다 
  //  console.log(”rerendering Component”)  가 실행되도록 하십시오.
  
  
  // 2. ChildForExercise1  컴포넌트가 마운트될 때,
  //  console.log(”Hi at mounting..”)  가 실행되도록 하십시오.
  
	// 3. name  과  age  가 변경 될 때마다  화면의 출력에 반영되도록 하십시오.
  const [name, setName] = useState("John");
  const [age, setAge] = useState(24);
  
  // 4. document.title  에  name  과 age 가 반영되도록  name, age 의 변화에 연동시키십시오.
  useEffect(() => {
    // console.log("Render");
    // document.title = name;
  });

	// **. name 필드에서 입력값에 변화가 발생했을 때, 
	//  최소 1초의 인터벌을 두고 상태변화로 이어지도록 코딩하십시오. 
	//  useEffect 의 cleanup 펑션 사례에서 힌트를 찾을 수 있을 것입니다.

  return (
    <div>
      <input
        type="text"
        name="name"
        value={name}
      />
      <br />
      <br />
      <button onClick={() => setAge((a) => a - 1)}>-</button>
      {age}
      <button onClick={() => setAge((a) => a + 1)}>+</button>
      <br />
      <br />Your name is {name}, you're {age} years old. You'll never get old in my app.
    </div>
  );
}

export default ChildForExercise1;

npm run dev 띄워진 브라우저에서 디버깅 해가면서 천천히 풀어보세요.

코드에 정답은 존재하지 않습니다.

주어진 태스크가 해결되었다면, 모두가 정답이 아닐까요.

그러나, 문제를 해결한 뒤에도 우리는 모두 목마름을 경험하게 됩니다. 더 나은 방법은 없었을까? 다른 사람들 이라면 어떻게 코딩했을까?

당신의 코드를 공유해 주세요.

당신의 코드가 우리 모두에게 도움이 될 수 있습니다. 누군가에게는 생각하지도 못했던 새로움일 수도 있겠죠. 코드 리뷰는 가장 빠르게 코딩 실력을 향상시키는 길이라는 설문도 있었고요. 활발한 코드 공유와, 공유된 코드에 적극적인 리뷰 참여가 이루어지길 소망해 봅니다.

모두의 코드가 똑같이 소중합니다.

배려 까지도 필요 없겠죠. 상대방의 수고와 고생을 알고있는 우리들 사이에서 존중은 기본값이어야 하겠죠.

코드 공유방법 :

방법 1.

CodeSandbox 에서 sandbox 를 생성하고, public 으로 전환합니다. *devbox 에서는 공개전환에 제약이 있습니다.

방법 2.

StackBlitz 에서 프로젝트를 생성하고, public 으로 전환 합니다.

방법3.

그 외, 모든 코드 하이라이팅이 지원되는 플랫폼에서의 공개된 코드

gist.github.com 도 좋습니다.

  • 코드 하이라이팅이 지원되지 않는 플랫폼에서의 코드 소통은, 서로가 고역이 되어요. 이게 가장 큰 문제입니다.

모두의 Code 와 Review 를 포함한 Workthrough 는,

1~2 주 후에 Chapter #3 more hooks & custom hooks 와 함께 업로드 될 예정입니다.







....