GithubHelp home page GithubHelp logo

wanted-pre-onboarding-11th-team3 / pre-onboarding-11th-3-3 Goto Github PK

View Code? Open in Web Editor NEW
0.0 0.0 4.0 1 MB

원티드 프리온보딩 프론트엔드 인턴십 3주차 3팀 과제

License: MIT License

Shell 0.57% JavaScript 0.42% HTML 8.48% TypeScript 90.53%

pre-onboarding-11th-3-3's Introduction

👆🏻 제목을 클릭하면 배포된 사이트를 확인하실 수 있습니다.


🗓️ 기간

  • 2023년 7월 11일 ~ 2023년 07월 14일

🧭 목적

  • 동료학습 및 협업능력과 새로운 인사이트
  • GitHub REST API로 GitHub Issue 페이지 구현하기

✅ Task

❗필수 요구 사항

  • 이슈 목록 및 상세 화면 기능 구현
  • Context API를 활용한 API 연동
  • 데이터 요청 중 로딩 표시
  • 에러 화면 구현
  • 지정된 조건(open 상태, 코멘트 많은 순)에 맞게 데이터 요청 및 표시

❗범위

  1. 이슈 목록 화면

    • 이슈 목록 가져오기 API 활용
    • open 상태의 이슈 중 코멘트가 많은 순으로 정렬
    • 각 행에는 ‘이슈번호, 이슈제목, 작성자, 작성일, 코멘트수’를 표시
    • 다섯번째 셀마다 광고 이미지 출력
    • 화면을 아래로 스크롤 할 시 이슈 목록 추가 로딩(인피니티 스크롤)
  2. 이슈 상세 화면

    • 이슈의 상세 내용 표시
    • ‘이슈번호, 이슈제목, 작성자, 작성일, 코멘트 수, 작성자 프로필 이미지, 본문' 표시
  3. 공통 헤더

    • 두 페이지는 공통 헤더를 공유합니다.
    • 헤더에는 Organization Name / Repository Name이 표시됩니다.

💡 진행방식

  1. 팀 컨벤션을 지정하여 위키에 정리해 두었습니다.
  2. 팀 노션을 활용하여 각자의 사전과제를 보고 Best Practice라고 생각되는 과제에 투표를 합니다.
  3. 팀원들의 원티드 인턴십 사전과제 중, 각각 Best Practice로 선발된 코드를 팀 프로젝트에 반영하고 VScode의 "Live Share" 확장프로그램을 사용하여 서로의 코드 작성법을 실시간으로 보며 함께 리팩토링을 진행하였습니다.

🎖️ Members

팀장 FE.

팀원 FE.
팀원 FE.
팀원 FE.

🛠️ Stacks

react eslint prettier typescript styledcomponents


👑 Best Practice

Best Practice란 모범사례라는 말로서, 특정 문제를 효과적으로 해결하기 위한 가장 성공적인 해결책 또는 방법론을 의미합니다.

📍 기능

  • 이슈 목록
  • 이슈 상세
  • 이슈 목록 인피니티 스크롤
GitHub issue Page
GitHub issue Page

🌳 File Tree

📦src
 ┣ 📂apis
 ┃ ┗ 📜issueApi.ts
 ┣ 📂components
 ┃ ┣ 📂common
 ┃ ┃ ┗ 📜Loading.tsx
 ┃ ┣ 📜Header.tsx
 ┃ ┣ 📜IssueDetail.tsx
 ┃ ┣ 📜IssueItem.tsx
 ┃ ┣ 📜IssueList.tsx
 ┃ ┗ 📜LocalNavBar.tsx
 ┣ 📂constants
 ┃ ┗ 📜color.ts
 ┣ 📂context
 ┃ ┗ 📜IssueContext.tsx
 ┣ 📂httpClient
 ┃ ┗ 📜httpClient.ts
 ┣ 📂interface
 ┃ ┗ 📜issue.ts
 ┣ 📂lotties
 ┃ ┗ 📜loading.json
 ┣ 📂pages
 ┃ ┣ 📜Issue.tsx
 ┃ ┗ 📜NotFound.tsx
 ┣ 📂routes
 ┃ ┗ 📜router.tsx
 ┣ 📂utils
 ┃ ┗ 📜InfiniteScroll.ts
 ┣ 📜App.tsx
 ┣ 📜GlobalStyle.ts
 ┣ 📜index.tsx
 ┗ 📜react-app-env.d.ts

✨ GitHub Issues Web Page

❓ 선정이유

  • 과제의 핵심 기능인 API연동이 깔끔하고 Context API와 HTTP통신의 관심사 분리가 잘 되어 있다고 생각합니다.저번 과제에서 피드백 받았던 부분을 class로 관심사 분리했습니다.

💥트러블 슈팅

  • Case 01.

    • 이슈

      • base url 다음에 임의의 값을 넣어주면 /notFound페이지로 바로 넘어가지 않고, 에러창이 뜹니다. 새로고침해야 /notFound페이지가 렌더됩니다.
      const router = createBrowserRouter([
        {
          path: '/',
          element: <App />,
          children: [
            {
              path: '/',
              element: <Issue />,
              children: [
                {
                  path: '/:id',
                  element: <IssueDetail />,
                  errorElement: <Navigate to="/notFound" />,
                },
              ],
            },
            { path: '/notFound', element: <NotFound /> },
            { path: '*', element: <Navigate to="/notFound" /> },
          ],
        },
      ]);
    • 해결

      • 비정상적으로 접근을 하면 바로 데이터 요청 없이 바로 "404 Not found"페이지로 이동을 해야하는데 데이터 요청을 하니까 GitHub API에서는 documentation_url: "https://docs.github.com/rest/issues/issues#get-an-issue" message: "Not Found" 에러가 출력됩니다.
      • 디테일 페이지를 불러올 때, 디테일 페이지를 불러오는 id값 앞에 /issues 경로를 추가합니다.
      • base url 다음에 임의의 값을 넣어주면 "404 Not found"페이지로 이동하고, base url/issues/ 다음 임의의 값을 넣어주면 '존재하지 않는 이슈'가 화면에 출력됩니다.
      const router = createBrowserRouter([
      {
        path: '/',
        element: <App />,
        children: [
          {
            path: '/',
            element: <Issue />,
            errorElement: <Navigate to="/notFound" />,
            children: [
              {
                path: '/issues',
                element: <Navigate to="/" />,
              },
              {
                path: '/issues/:id',
                element: <IssueDetail />,
              },
            ],
          },
          { path: '/notFound', element: <NotFound /> },
          { path: '*', element: <Navigate to="/notFound" /> },
        ],
      },
      ]);
  • Case 02.

    • 이슈

      • 빈값일 때 보여지는 "empty" 이미지와 데이터를 불러오는 중일 때 보여지는 "loading"이 함께 보여집니다. 빈값일 때는 "empty" 이미지만, 데이터를 불러오는 중일 때는 "loading"이 각각 따로 보여졌으면 좋겠습니다.
      const IssueDetail = () => {
      
        // ...
      
        return (
          <IssueDetailStyle>
            {detail ? (
              // ...
            ) : (
              <Loading />
            )}
          </IssueDetailStyle>
        );
      };
    • 해결

      • 삼항연산자를 중복 사용 (조건식(if) ? 참 : 조건식(else if) ? 참 : 거짓(else)) 하여 detail 데이터가 있으면 렌더링되고, 데이터를 불러오는 중일 땐 "loading"이 보이고, 데이터가 없을 땐 "empty" 이미지를 보여줍니다.
      const IssueDetail = () => {
      
        // ...
      
        return (
          <IssueDetailStyle>
            {detail ? (
              // ...
            ) : isLoading ? (
              <Loading />
            ) : (
              <WarningStyle>
                <ImWarning />
                존재하지 않는 이슈입니다
              </WarningStyle>
            )}
          </IssueDetailStyle>
        );
      };

🪄 리팩토링

  • Case 01.

    • ❌ Bad

      • useEffect의 콜백함수는 바로 비동기적일 수 없습니다. 비동기적인 함수를 바로 콜백 함수로 넣을 수 없기 때문에, 즉시실행함수라는 비동기 함수를 useEffect가 실행될 때마다 생성해주고, 그 안에서 데이터를 가져오고 setDetail을 해주게 됩니다. 그리고 만든 즉시실행함수를 실행하게 됩니다. useEffect가 실행될 때마다 매번 함수가 생성되고 실행되니 매우 비효율적입니다.
        useEffect(() => {
          (async () => {
            try {
              setDetail(await issueDetail(location));
            } catch (error) {
              console.log(error);
            }
          })();
        }, [location, issueDetail]);
    • ⭕ Good

      • useCallback을 활용하면, useCallback안에서 데이터를 가져오는 콜백 함수를 getIssueDetail에 넣어주고, useEffect안에서는 getIssueDetail만 호출을 해주면 코드가 훨씬 깔끔해지고, 로직도 분리되며 가독성도 올라가는 효과를 얻을 수 있습니다.
        const getIssueDetail = useCallback(async () => {
          try {
            setDetail(await issueDetail(location));
          } catch (error) {
            console.log(error);
          }
        }, [issueDetail, location]);
      
        useEffect(() => {
          getIssueDetail();
        }, [location, issueDetail, getIssueDetail]);
  • Case 02.

    • ❌ Bad

      • Class 내부에서만 사용하는 상수는 Class 안에서만 사용합니다.
      const url = '/repos/facebook/react';
      
      export class IssueApi {
        private httpClient: HttpClient;
      
        constructor(httpClient: HttpClient) {
        this.httpClient = httpClient;
        }
      
        // ...
      }
    • ⭕ Good

      • 클래스는 내부에 데이터를 가지고 있을 수 있기에, 굳이 바깥 범위인 파일에 놔두는 것 보다 내부에서만 사용되는 값이라면 내부로 가져와서 저장해두는 것이 효율적입니다.
      • 이 과정에서 외부로 노출될 필요가 없어 private + 수정되지 않는 상수이기에 readonly를 적용합니다.
      export class IssueApi {
      private httpClient: HttpClient;
      private readonly URL = '/repos/facebook/react';
      
      constructor(httpClient: HttpClient) {
      this.httpClient = httpClient;
      }
      
      // ...
      }
  • Case 03.

    • ❌ Bad

      • 유지보수를 위해 CSS-in-JS와 className을 이용한 스타일링 혼용은 금지합니다.
      const IssueUlStyle = styled.ul`
        width: 100%;
        padding: 16px;
        overflow-y: auto;
        height: calc(100% - 62px);
        position: relative;
      
        .imptyImg {
          width: 50%;
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
        }
      `;
      • 유지보수를 위해 tag selector 사용을 지양합니다.
      const IssueLiStyle = styled.li`
        padding: 15px 10px;
        border-radius: 10px;
      
        a {
          display: flex;
          justify-content: space-between;
          align-items: center;
      
          img {
            background-color: #fff;
            height: 100%;
            padding: 0 35%;
          }
        }
      
        &:hover {
          background-color: ${COLOR.DarkHover};
      
          h3 {
            text-decoration: underline;
            color: ${COLOR.White};
          }
        }
      `;
    • ⭕ Good

      • styled-component를 이용해서 스타일링이 된 태그들은 해당 definition으로 이동해서 바로 스타일링을 확인이 가능한데 className으로 선언한 부분은 바로 definition으로 이동이 불가능해서 스타일링 파악하기에 어려움이 있습니다.
      • 위의 내용에 덧붙여서 className을 이용한 스타일링의 nesting depth가 깊어지는 경우에는 더 더욱 찾기가 어려워집니다.
      • depth가 깊어지는 경우 뿐만 아니라 1depth로도 className을 이용한 스타일 선언부가 길어지는 경우에도 해당 스타일링을 찾기 어려워집니다.
      • 위의 사항들로 인해서 CSS in JS(styled-components 등)을 이용한 스타일링을 하는 경우에는 라이브러리 사용상 강제되는 등의 불가피한 부분을 제외하고는 className을 통한 스타일링은 최대한 지양하는게 권장됩니다.
      const IssueUlStyle = styled.ul`
        width: 100%;
        padding: 16px;
        overflow-y: auto;
        height: calc(100% - 62px);
        position: relative;
      `;
      
      const EmptyImgStyle = styled.img`
        width: 50%;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      `;
      • a, img 태그 처럼 빈번히 사용되는 태그로 스타일을 주는 건 depth에 무관하게 적용되어 의도치 않은 스타일링이 될 수 있습니다.
      • tag selector는 해당 요소 뿐만 아니라 동일한 태그로 선언된 다른 요소들에게도 영향을 미칠 여지가 너무 많기 때문에 추후 유지보수에 악영향을 미칠 수 있습니다.
      const IssueLiStyle = styled.li`
        padding: 15px 10px;
        border-radius: 10px;
      
        a {
          display: flex;
          justify-content: space-between;
          align-items: center;
        }
      
        &:hover {
          background-color: ${COLOR.DarkHover};
      
          h3 {
            text-decoration: underline;
            color: ${COLOR.White};
          }
        }
      `;
      
      const ImgStyle = styled.img`
        background-color: #fff;
        height: 100%;
        padding: 0 35%;
      `;

✒️ 회고

이름 좋았던 점 아쉬웠던 점
주희 - 사전 과제보다 성장했습니다. TypeScript 사용, Context API 활용한 API 연동, 라우트 중첩을 프로젝트에 적용할 수 있는 기회였습니다. - 시간이 너무 촉박했습니다 😭
상호 - Redux와 Context API의 차이점에 대해 에전보다 자세히 알게 되었다. - 상세 페이지에서 목록 페이지로 돌아왔을 때 데이터가 유지되는 기능을 구현하지 못한 점이 아쉽다.
민지 - 필수 요소가 동일한, 같은 조건 내에서도 사람마다 정말 다르게 구현해 내는 것이 신기했다.
- 다른 사람의 코드를 보는 것은 여전히 어렵지만 도움이 많이 됬다.
- 짧은 기한 내에 구현하려니 좋은 코드를 생각하면서 짤 시간이 부족했다.
혜림 - 글과 화면으로만 배운 class와 Context API를 직접 적용해 볼 수 있어서 좋았다.
- 리덕스 툴킷을 잘 알지 못했지만 리덕스 툴킷으로 구현하신 상호님의 코드를 보고 찾아보며 공부할 수 있어서 좋았다. 하지만 그래도 잘 모르겠는게 함정… 🤣 리덕스를 집중적으로 공부하고 프로젝트에 직접 적용해 봐야겠다.
- 과제 기간이 짧아서 더욱 자세하게 공부하지 못한 것 같아 조금 아쉽다.

pre-onboarding-11th-3-3's People

Contributors

hyerimhan avatar nononcrust avatar marie-ming avatar

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.