Todo앱 만들기(중단)

jonyChoiGenius·2023년 1월 20일
0

이 글에 나온 내용이 CSS가 안먹는 등 문제가 많아서 리덕스 적용하여 재개하였음!

헤더 만들기

//pages/Header.tsx
const Header: React.FC = () => {
  return (
    <div>
      <h1>개쩌는 투두앱</h1>
    </div>
  );
};

export default Header;

React.FC타입은 리액트 컴포넌트 타입을 의미한다.

_app.tsx

//pages/_app.tsx
import { AppProps } from "next/app";
import GlobalStyle from "../styles/GlobalStyle";
import Header from "./Header";

const app = ({ Component, pageProps }: AppProps) => {
  return (
    <>
      <GlobalStyle />
      <Header />
      <Component {...pageProps} />
    </>
  );
};

export default app;

Next.js는 AppProps이라는 별도의 타입을 지원한다. 프롭스는 :AppProps로 타입을 지정하고, 위와 같이 헤더를 넣는다.

next_js 구동방식 요약

Next js 구동 방식과 getInitailProps를 참조하여 나머지 파일들에 대한 설명을 보자. 요약하자면 Next는 최초로 _app.js, _document.js를 실행한다. 서버환경에서 실행되므로 window객체는 없다는 점에 유의하자. _app.js가 컴포넌트의 레이아웃을 구성하여 HTML의 body를 만든다. 이후 _document.js가 html의 body가 어디에 들어가면 좋을지를 구성한다. _document.js는 어디까지나 회면을 구성하므로 어플리케이션 로직은 _app.js를 활용해야 한다.

getInitialProps은 (9.3이후에는 getStaticProps, getStaticPaths, getServerSideProps으로 분화됨) 간단하게 말하면 FetchData 로직이다. React에서 useEffect(()=>{},[])로 마운트 된 후 실행하거나, Vue에서 onCreate, onMount와 같은 라이프 라이클 훅을 서버사이드에서 처리하여 렌더링한다고 보면 된다. 만일 모든 페이지에서 동일한 정보가 필요하다면 _app.js getInitialProps을 사용하여 pageProps로 넘겨주면 ㄷ된다. 유의할 점은 한 페이지는 하나의 getInitialProps만 동작한다. _app.js에서 getInitialProps을 사용했다면 그 하부 페이지는 별도의 데이터를 Fetch하지 않음을 의미한다.

이슈! next 공식 예제를 다운받음.

아무리 해도 styled-componets가 적용되지 않아서
yarn create next-app --example with-styled-components with-styled-components-app를 실행해서 예제를 받았다.

{
  "private": true,
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "^13.1.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "styled-components": "^5.3.6"
  },
  "devDependencies": {
    "@types/node": "^18.11.18",
    "@types/react": "^18.0.26",
    "@types/react-dom": "18.0.10",
    "@types/styled-components": "^5.1.26",
    "typescript": "4.9.4",
    "eslint": "^8.32.0",
    "eslint-config-airbnb": "^19.0.4",
    "eslint-config-airbnb-typescript": "^17.0.0",
    "eslint-config-next": "13.1.1",
    "eslint-config-prettier": "^8.6.0"
  },
  "eslintConfig": {
    "extends": [
      "next/core-web-vitals",
      "airbnb",
      "airbnb-typescript",
      "prettier"
    ]
  }
}

데이터 만들기

//components\TodoList.tsx
const TodoList: React.FC = () => {
  return (
    <div>
      <h1>TodoList</h1>
    </div>
  );
};

export default TodoList;
//pages\index.tsx
import { NextPage } from "next";
import TodoList from "../components/TodoList";

const index: NextPage = () => {
  return <TodoList />;
};

export default index;
//types\todo.d.ts
export type TodoType = {
  id: number;
  text: string;
  color: "RED" | "ORANGE" | "YELLOW";
  checked: boolean;
};

지정한 타입으로 Todo 데이터를 index.tsx에 만들자

import { NextPage } from "next";
import TodoList from "../components/TodoList";
import { TodoType } from "../types/todo";

const todos: TodoType[] = [
  { id: 1, text: "마트 가서 장보기", color: "RED", checked: false },
  { id: 2, text: "수학 숙제하기", color: "ORANGE", checked: true },
  { id: 3, text: "투두리스트 만들기", color: "YELLOW", checked: false },
  {
    id: 4,
    text: "마트가서 투두리스트 만드는 숙제하기",
    color: "RED",
    checked: false,
  },
];

const index: NextPage = () => {
  return <TodoList todos={todos} />;
};

export default index;

이때 '정의된 어트리뷰트가 아니다'라며 타입스크립트가 에러를 띄운다.

타입스크립트 인터페이스를 만들자.

//components\TodoList.tsx
import { TodoType } from "../types/todo";

interface IProps {
  todos: TodoType[];
}

const TodoList: React.FC<IProps> = () => {
  return (
    <div>
      <h1>TodoList</h1>
    </div>
  );
};

export default TodoList;

TodoType을 import하여 이를 IProps라는 인터페스로 만들고, React.FC의 제너릭에 IProps라는 타입을 넣어주었다.

이제 index.tsx에서도 IProps라는 어트리뷰트임을 잘 인식한다.

리스트 만들기

리스트의 갯수를 세자.
아래는 switch-case와 플래그를 이용하여 total을 구하는 패턴이다.

//components\TodoList.tsx
  const todoNums = useCallback(() => {
    const data = {
      total: 0,
      RED: 0,
      ORANGE: 0,
      YELLOW: 0,
    };
    todos.forEach((todo) => {
      let hasColor = true;
      switch (todo.color) {
        case "RED":
          data.RED += 1;
          break;
        case "ORANGE":
          data.ORANGE += 1;
          break;
        case "YELLOW":
          data.YELLOW += 1;
          break;
        default:
          hasColor = false;
      }
      if (hasColor) data.total += 1;
    });
    return data;
  }, [todos]);

아래는 책에서 소개된 새로운 colors가 발견되면 문자열을 키로하는 새로운 값을 선언하는 패턴이다. 대괄호 표기법을 사용하면 표현식을 키값으로 가진 프로퍼티를 조회할 수 있다.

  const todoNums = useMemo(() => {
    const data: ObjectIndexType = { total: 0 };
    todos.forEach((todo)=>{
      const value = data[todo.color];
      if(!value){
        data[todo.color] = 1;
      } else {
        data[todo.color] += 1;
      }
      data.total += 1;
    })
    return data
  }, [todos]);

console.log를 찍으면 { total: 4, RED: 2, ORANGE: 1, YELLOW: 1 }로 정상 출력 된다. (참고로 콘솔은 터미널에 출력된다.)

Object.keys()로 객체의 키값을 배열로 얻어 다양하게 써보자

import { useMemo } from "react";
import { TodoType } from "../types/todo";

interface IProps {
  todos: TodoType[];
}

type ObjectIndexType = {
  [key: string]: number | undefined;
};

const TodoList: React.FC<IProps> = ({ todos }) => {
  const todoNums = useMemo(() => {
    const data: ObjectIndexType = {};
    todos.forEach((todo) => {
      const value = data[todo.color];
      if (!value) {
        data[todo.color] = 1;
      } else {
        data[todo.color] += 1;
      }
    });
    return data;
  }, [todos]);

  return (
    <div>
      <h1>TodoList</h1>
      <p>남은 TODO {todos.length}</p>
      <div>
        {Object.keys(todoNums).map((el, index) => (
          <p style={{ color: `${el}` }} key={index}>
            {todoNums[el]}</p>
        ))}
      </div>
    </div>
  );
};

export default TodoList;

리액트에서 여러개의 태그를 순회하며 렌더링 할 때에는 map메서드를 사용한다.(for문은 사용이 안된다 ㅠㅠ)

Object.keys(object)는 키값을 배열로 반환한다. 이를 이용하여 key값을 인라인 스타일로도 넣고, 갯수를 넣는 용도로도 썼다.

profile
천재가 되어버린 박제를 아시오?

0개의 댓글