Coder Social home page Coder Social logo

nomad-react-beginner's People

Contributors

yeongseoyoon avatar

Watchers

 avatar

nomad-react-beginner's Issues

Feature: 메인 페이지 구현

✨ 개발할 기능

  • 메인 페이지를 구현합니다.

✅ ToDo

  • 메인 페이지에서 api 호출 통해 데이터 불러오기
  • 로딩컴포넌트 구현

📖 기타 사항

[담] 간단 코드리뷰

담님의 간단 코드리뷰

안녕하세요. 영서님.
코드리뷰가 흥했던 2기를 함께하고도 서로 코드리뷰를 해본적이 없었네요.
3기에 같은조가 되어 조활동으로 코드리뷰를 하게되어 영광이에요.

영서님이 리액트를 정말 좋아하시고 잘하시기 때문에 아래의 제 코드리뷰는 취향과도 관련된 내용밖에 없을 것 같습니다.
그냥 재밌게 읽어 주셨으면 좋겠어요.

route path

root path 기준으로 APP 컴포넌트를 렌더하고 중첩라우팅으로(즉, chidldren을 통해)로 Home, Detail을 렌더합니다.
여기서 Detail 페이지로의 path는 root path 중첩라우팅으로 Home 과 동일하게 ‘character/:id’ 작성하셔도 무관합니다.

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
    children: [
      {
        path: "",
        element: <Home />,
      },
      {
        path: "/character/:id", // ptah: "character/:id" 와 동일
        element: <Detail />,
      },
    ],
  },
]);

이에 더해서, 이런 방식으로 route path 를 상수처리하는 것은 어떨까요?

export const ROUTE_PATH = {
  ROOT: "/",
  HOME: "",
  DETAIL: "character/:id",
} as const;

createBrowserRouter에서만 사용될 수도 있는 상수일텐데 굳이 따로 빼놓는 것이 좋을까? 라고 생각 들 수도 있습니다.

  1. 어떤 라우트의 path인지 한 눈에 파악 가능
  2. 어떤 한 페이지에서 다른 페이지로 라우팅할 때 사용이 용이함.

2번의 경우에서는 영서님 프로젝트에 존재하는 헤더 컴포넌트에 사용할수 있을 것 같습니다.
<Link to=”/” > 에서 <Link to=”ROUTE_PATH.HOME” > 으로 대체 가능하게 됩니다. Home 이동하는 역할이 명시적으로 보여지죠.

const Header = () => {
  return (
    <header className="fixed top-0 left-0 right-0 z-10 h-16 bg-white shadow-md">
      <div className="container flex itmes-center">
        <Link to="/" className="my-4 ml-6 text-2xl font-semibold"> 
          Marvelous🚀
        </Link>
      </div>
    </header>
  );
};

export default Header;

API response 타입

사실 이 부분에 대해서는 코드리뷰에 기재를 할까말까 조금 많이 고민했습니다.타입에 대해서는 정확하게 어떤 것이 좋을지 가늠이 되지않더라구요.

그래도 반복되는 코드에 조금 지치지않았을까 싶어서 저는 이런방법으로 구현했다고 공유해드리고 싶었습니다.
제가 타입스크립트에서 타입을 선언할 때 생각하고 있는 방향이라고 생각하시면 될것같아요.

현재 Character 타입에서는 serires와 stories에서의 타입이 중복되는 값이 들어갑니다.
사실, 아래의 제 코드에서는 제가 원하는 사용할 부분만 가져와 타입을 작성한 것이라 조금 상이할 수도있습니다.

지금 저는 items 만 하나의 Items 타입으로 빼내어 series와 stories 에 사용하고있습니다.
이와 같이 동일한 구조의 타입을 가진다면 하나의 공통된 타입을 만들어서 재사용하는 방식은 어떨까요?
(어쩌면 series와 stories도 동일하게 사용할 수도 있어 보입니다.)

export interface Character {
  id: number;
  name: string;
  thumbnail: {
    extension: string;
    path: string;
  };
  series: {
    available: number;
    items: Array<Items>;
  };
  stories: {
    available: number;
    items: Array<Items>;
  };
  urls: Array<Url>;
}
export type Items = { name: string; resourceURI: string };
export type Url = { type: string; url: string };

“동일한 구조였지만 여기서, 추가되거나 제외될 수도 있을텐데 유지보수관점에서 별로지 않을까요?” 라는 의문이 들지도 모릅니다.

그럴 때 타입을 extends 확장 시키거나 원하는 부분만을 뽑아오거나 Pick, 제외하거나 Omit / Exclude하는 방향도 생각해볼 수 있지않을까 싶어요.
(참고로 저는 동일한 타입을 뽑아두고 extends 시키는 방법을 자주 사용합니다.)

타입이 존재하는 언어를 사용한지 얼마되지 않았기 때문에 이 방법이 좋을지는 가늠이 되지않지만, 한 번쯤은 공유 하고싶었습니다.

타입 단언 보다는 타입 선언

이라는 이펙티브 타입스크립트 아이템9 소제목이 있습니다.

타입 단언시에는 타입시스템이 안전성 체크를 무시하기 때문에, 런타임에러가 발생할 경우가 많아진다는 내용입니다. 즉, 타입스크립트가 추론한 타입이 있더라도 타입 단언한 타입으로 간주하겠다는 의미라고 합니다.

타입선언을 사용해서 타입스크립트가 타입 추론 하게끔 만드는 건 어떨까요?

타입단언

getCharacterList = async () => {
    const response = await this.httpClient.get(
      "/v1/public/characters?limit=50&orderBy=modified&series=24229,1058,2023"
    );
    return (await response.json()).data as PaginatedCharacter;
  };

타입선언

getCharacterList:()=>Promise<PaginatedCharacter> = async () => {
    const response = await this.httpClient.get(
      "/v1/public/characters?limit=50&orderBy=modified&series=24229,1058,2023"
    );
    return (await response.json()).data;
  };

멋진 영서님

이전에도 말씀드렸지만, 배운 것을 바로 적용하여 프로젝트를 꾸려낸 영서님이 너무 멋있어요.

사실 저는 횡단관심사에 대해서 배웠었지만, 실습만 진행해보고 제 코드에 녹여내기에는 어려워서 멀리 뒀었거든요. 그래서 영서의 과제를 보자마자 감탄과 동시에 많은 반성을 했습니다.
서로 항상 좋은 자극을 주며 재밌게 공부해나가는 이 관계와 일상이 너무 행복합니다.

위의 코드리뷰내용이 잘못된 내용도 있을 수도 있고 제가 잘못 이해하고 작성된 내용도 있을 것이니, 의문이 드는 부분은 꼭 말씀해주세요.

코드리뷰 너무 재밌었습니다.

[빡준] 코드리뷰

🐛 빡준의 조그마한 코드리뷰입니다

ㅎㅇ입니다 영서님
HttpClient를 상속받는것이아닌 의존성주입으로 DIP(Dependency Inversion Principle)를 준수한점은 저도 배우고 훌륭한 코드라고 보게되네요

좋은 코드 공유 감사합니다 (__) 77

http Client 추상화와 Service Layer의 선언적 타입활용에 대해서 조금 전파해보고싶어서 글을 남깁니다.

기존의 영서님의 코드에요

export class HttpClient {
  constructor(private baseURL: string) {
    this.baseURL;
  }

  public get = async <T>(endPoint: string, options?: RequestOptions):Promise<T> => {
    const response = await fetch(this.baseURL + endPoint, {
      method: "get",
      ...options,
    });

    const data = await response.json() as T

    if(!response.ok) throw response
    
    return data;
  };
}


export class MarvelService {
  constructor(private httpClient: HttpClient) {
    this.httpClient;
  }

  getCharacterList = async () => {
    const response = await this.httpClient.get<PaginatedCharacter>(
      "/v1/public/characters?limit=50&orderBy=modified&series=24229,1058,2023"
    );

    return (await response.json()).data as PaginatedCharacter;
  };
}

Service는 저수준 모듈이고, HttpClient는 고수준 모듈이겠죠? 저수준 모듈에서 타입을 단언해버리게되면 매번 개발자는 타입단언을 통해 타입을 제어하게됩니다.

그런데 이 과정을 HttpClient에서 타입제어를 한다면 어떨까요? 타입스크립트에서는 이러한 피로감을 줄여줄수있는 좋은 기능이있습니다. 바로 제너릭을 활용하여 타입좁히기인데요!

아래의 코드 스크린샷과 함께 보시죠!

image

우선 Service에서 리스폰스의 데이터를 까보여주는 await response.json()if(!response.ok) throw response는 이제 HttpClient에서 하게되었습니다. 기존에도 Service가 해야할 역할은 데이터를 외부 저장소로부터 가져온다 라는 역할을 했었습니다. 하지만 에러처리와 같은 일련의 작업도 함께했었죠?

기존의 Service 의 역할은 두가지의 역할이었습니다.

  • fetch
  • exception

이제 이 exception의 역할을 httpClient로 즉 고수준 모듈로 끌어올려봅시다.

이제서야 데이터페칭이라는 역할 하나만을 수행하게되었습니다. SRP를 충족한 계층이 되지않았을까합니다

  • 타입단언도 고수준모듈에서!
    타입단언도 고수준모듈인 httpClien로 위임을 해봅시다. 이는 제너릭을 통해 타입좁히기를 사용하여 했습니다. 호출처인 Service에서는 페칭과 동시에 타입을 보장받을수있게된것같아요

우선 리액트를 배운지 4달정도된 사람이 맞을까 하는 의심이 들정도의 코드였네요 ㅋㅋㅋ 영서님의 코드를 보고 많은 배움을 느끼게됩니다. 저도 더 공부를 해야할것같네요

항상 고생하시고있고 잘하고있습니다. 존경합니다

Feature: detail 페이지 구현

✨ 개발할 기능

  • detail 페이지를 구현합니다.

✅ ToDo

  • detail 페이지 구현 및 스타일링
  • detail 페이지 데이터 호출 훅 구현

📖 기타 사항

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.