본문 바로가기
내일배움캠프/Today I Learned

[TIL 2023.06.15] React 프로젝트 GitHub Pages 배포하기 / 컴포넌트 분리하기

by 괴코딩 2023. 6. 16.

💻오늘 배운 내용

React 프로젝트를 GitHub Page로 배포하기

리액트를 쓸 때 CRA(Create-React-App)을 설치해서 사용 하는데 이렇게 하면 깃허브 페이지가 호스팅 되지 않는다.

아마도 페이지 주소를 열면 Readme를 만나는 당혹스러움을 경험했을것..

 

github pages로 web을 deploy했을 때 가장 기본으로 나오는 메인 페이지는 자동으로 repository의 가장 상단에 있는 index.html을 기준으로 하는데

CRA에서 index.html은 public 폴더 안에 있기 때문  >>

 

그래서 React 프로젝트를 GitHub Pages로 배포하기 위해서는 몇가지 작업이 필요하다.

구글에 방법이 여러가지 있었지만 난 간편한게 가장 좋기 때문에..

비교적 제일 덜 복잡하고 빨리 할 수 있는 방법을 알아왔다.

 

4단계로 완료 할 수 있다.

 

1. 프로젝트에 gh-pages 패키지를 설치. 터미널에 입력하면 된다.

npm install gh-pages --save-dev

 

2. 설치가 완료 후 프로젝트에 있는 package.json 파일을 열어 중괄호 안에 "homepage" 주소를 추가

// 형식은 http://{사용자 이름}.github.io/{저장소 이름}
"homepage": "http://HyunseoY.github.io/Todo_list"

 

3. script 부분에 predeploy, deploy 를 추가

"scripts": {
  //...
  "predeploy": "npm run build",
  "deploy": "gh-pages -d build"
}

 

4. 터미널창을 열어 npm run deploy를 실행

npm run deploy

뭐가 주루룩 생기면서 Published 됐다고 한다.

 

그럼 GitHub Repository > Settings > 좌측 사이드의 Pages > Branch를 gh-page로 설정하고 save하면

주소를 만들어준다.

시간이 쪼금 걸린다. 기껏 주소 생겨서 가봤더니 Readme가 나와도 일단은 실망하지 말자. 한 5분만 더 기다려주자..

 

❓발생한 이슈/고민

드디어 두렵고 두려워 미루고 미뤘던 component 분리를 시도해보았다.

컴포넌트 분리를 하면서 코드도 깔끔 & 줄일 수 있는 부분은 줄여 보면서 가독성을 높이려는 시도를 해보았다.

 

💡해결과정

컴포넌트는 총 5개로 분리했다.

return (
    <div className="layout">
      <Header />
      <Form todos={todos} setTodos={setTodos} />
      <List todos={todos} setTodos={setTodos} />
    </div>
  );

App 컴포넌트 내부를 3개의 컴포넌트로 나누고

List 컴포넌트 안에서 다시 자식 컴포넌트로 TodoCard로 분리했다.

 

가장 부모컴포넌트인 App에 todos의 state가 선언되어 있기 때문에

const [todos, setTodos] = useState([]);

투두를 작성하고 렌더링 하는 부분인 Form과 List 컴포넌트에는 todos state는 {todos}로, state를 조절하는 함수인 setTodos는 {setTodos}로 각각 props를 할당해 주었다.

 

Form 컴포넌트에서는 생성될 todo의 초기값을 설정해 주었는데,

const INIT_VALUE = { title: '', content: '', id: 1, isDone: false };

title, contents, id, isDone 키를 가진 객체를 초기값으로 두었다.

대신 이전에 state에 지정해뒀던 값은 삭제했다.

const [todos, setTodos] = useState([
    {
      id: 1,
      title: '리액트 공부하기',
      contents: '리액트 기초를 공부해 봅시다',
      contents: '리액트 기초를 공부해봅시다.',
      isDone: false,
    },
    {
      id: 2,
      title: '리액트 공부하기',
      contents: '리액트 기초를 공부했습니다.',
      isDone: true,
    },
  ]);  // 삭제함

 

List 컴포넌트에서는 삭제버튼과 업데이트버튼, 그리고 투두 입력시 생성될 jsx가 작성되어 있었는데,

<h2>🔥Working</h2>
        {todos.map((item) => {
          if (!item.isDone) {
            return (
              <div key={item.id}>
                <h3>{item.title}</h3>
                <p>{item.contents}</p>
                <button onClick={() => todoDeleteClickBtn(item.id)}>
                  삭제하기
                </button>
                <button onClick={() => todoUpdateClickBtn(item.id)}>
                  완료
                </button>
              </div>
            );
          } else {
            return null;
          }
        })}

투두가 작성되는 부분이 너무 길고 보기 불편하여 return 되는 부분을 따로 TodoCard로 분리해주었다.

 {todos
            .filter((item) => !item.isDone) // 아직 완료되지 않은 항목 필터링
            .map((item) => (
              <TodoCard
                item={item}
                key={item.id}
                todoDeleteClickBtn={todoDeleteClickBtn}
                todoUpdateClickBtn={todoUpdateClickBtn}
              />
            ))}

또한 if문 대신 filter 메서드를 사용하여 더욱 가독성을 높였다.

 

TodoCard 컴포넌트는 List 컴포넌트의 return부분을 가져왔더니

<button onClick={() => todoUpdateClickBtn(item.id)}>
  완료
</button>

같은 자리에 완료와 취소가 다르게 들어가야 하는데 구분이 되지 않는 문제가 발생했다.

 {item.isDone ? '취소' : '완료'}

그래서 삼항연산자를 이용해 isDone 값이 true라면 '취소' false라면 '완료'를 반환 할 수 있게 설정했다.

 

마지막으로 코드를 좀 더 간결하게 줄이고 가독성을 높이는 방법을 고민하다가

 

Form 컴포넌트의 인풋 입력창 관련 코드를

   const [title, setTitle] = useState('');
   const setTitleHandler = (event) => {
     setTitle(event.target.value);
   };
   const [contents, setContents] = useState('');
   const setContentsHandler = (event) => {
     setContents(event.target.value);
   };

위 코드에서 

  const [todo, setTodo] = useState(INIT_VALUE);

  const onChangeHandler = (event) => {
    const { name, value } = event.target;

    setTodo({ ...todo, [name]: value });
  };

아래처럼 줄였다. 

title과 contents 따로 state를 설정하지말고 todo라고 하나의 state를 둔 뒤, onChangeHandler에서 event.target을

구조분해할당하여 name과 value값을 받는다.

제목
<input
  className="submit-input"
  name="title"
  value={todo.title}
  onChange={onChangeHandler}
/>
내용
<input
  className="submit-input"
  name="content"
  value={todo.content}
  onChange={onChangeHandler}
/>

그래서 input 태그에서 제목의 name은 "title"로 내용의 name은 "content"로 두개의 인풋의 value를 구분했다.

const todoAddClickBtn = (e) => {
  e.preventDefault();
  if (todo.title === '' || todo.content === '') return;
  setTodos([...todos, { ...todo, id: todoID }]);
  setTodo(INIT_VALUE);
};

초기값도 설정해뒀으니 todoAddClickBtn에 선언해 두었던 newTodo 객체도 필요 없어서 삭제해서 줄어들었다.

const todoAddClickBtn = (e) => {
e.preventDefault();
if (title === '' || contents === '') return;

const newTodos = {
  id: number,
  title,
  contents,
};

console.log(number);
setTodos([...todos, newTodos]);
setNumber((prev) => prev + 1);
setTitle('');
setContents('');
};

또한 setTitle(''), setContents('')도 state를 삭제해주었기 때문에 setTodo(INIT_VALUE); 한 줄로 줄어들었다.

 

setNumber((prev) => prev + 1) 부분 또한 

const [number, setNumber] = useState(3);

state로 설정해뒀던 부분을 아예 삼항연산자로 조건을 설정하여

const todoID = todos.length > 0 ? todos[todos.length - 1].id + 1 : 1;

todos.length가 0보다 크면 todos의 마지막 요소의 id 값에 +1을 해주고 아니면 1을 반환한다.

setTodos([...todos, { ...todo, id: todoID }]);

setTodos도 수정해줘야함.

 

🧐궁금점과 부족한 내용

항상 다른 사람들 코드 치는걸 보면서 저사람들은 어떻게 저렇게 속도가 빠르지 의문이었다.

나만 거북이얌...독수리 쪼쪼야...

타수가 빠르다고 전부는 아니니.. 조원분이 유용한 단축키들을 몇가지 알려줘서 메모해뒀다.

 

<유용한 단축키들>

ctrl+enter =다음줄 이동
alt 누르며 방향키 =원하는 방향으로 이동
alt+shist+방향키 =원하는 방향으로 복사
shift+delete =한줄 전체 삭제
ctrl+delete =단어 앞까지 삭제
ctrl+d =하나선택
ctrl+d (반복) =같은 단어 계속 찾아감
ctrl+alt+방향키 =여러줄 동시에 작성
alt+원하는 줄 클릭 =선택한 줄 동시에 작성

 

📋레퍼런스

https://velog.io/@byjihye/react-github-pages

반응형