Coder Social home page Coder Social logo

chaedie / team7-week1-2 Goto Github PK

View Code? Open in Web Editor NEW

This project forked from wanted-fe-7th-team7/team7-week1-2

0.0 0.0 0.0 2 MB

원티드 프론트엔드 프리온보딩 7차 7팀 1-2 과제 레포리토리입니다

TypeScript 88.34% HTML 10.81% Shell 0.84%

team7-week1-2's Introduction

team7-week1-2

원티드 프론트엔드 프리온보딩 7차 7팀 1-2 과제 레포리토리입니다

배포 주소

http://team7-week1-2.s3-website.ap-northeast-2.amazonaws.com

폴더구조

📦src
  📂apis
   📜axiosUtils.ts
   📜issuesApi.ts
  📂components
   📜IssueDetail.tsx
   📜IssueHeader.tsx
   📜IssueItem.tsx
   📜IssueLayout.tsx
   📜IssueList.tsx
   📜IssueLoader.tsx
   📜IssueRouter.tsx
   📜MainRouter.tsx
   📜Providers.tsx
  📂contexts
   📜IssuesContext.tsx
  📂hooks
   📜useIssues.ts
  📂models
   📜issue.ts
  📂pages
   📜ErrorPage.tsx
   📜IssueDetailPage.tsx
   📜IssuesPage.tsx
  📂utils
   📜env.ts
   📜parseIssue.ts
  📜App.tsx
  📜index.tsx
  📜react-app-env.d.ts
  1. apis : api 통신 함수 관리
  2. components : 공통된 컴포넌트 관리
  3. contexts : Context API 관리
  4. hooks : 공통으로 사용되는 hooks 관리
  5. models : 공통으로 사용되는 interface 관리
  6. pages : 페이지 단위 컴포넌트 폴더
  7. utils : 공통으로 사용되는 기타 함수 관리

API 연동 (이슬)

  • API responseinterface 로 작성
export interface IssueResponse {
  id: number;
  number: number;
  title: string;
  body: string;
  user: {
    login: string;
    avatar_url: string;
  };
  comments: number;
  created_at: string;
}

  • API 요청시 필요한 상수변수 정의
export type IssuesSort = 'created' | 'updated' | 'comments';

export const GITHUB_API_URL = 'https://api.github.com';
export const GITHUB_ACCEPT = 'application/vnd.github+json';
export const GITHUB_OWNER_NAME = 'angular';
export const GITHUB_REPO_NAME = 'angular-cli';

const path = {
  issues: `/repos/${GITHUB_OWNER_NAME}/${GITHUB_REPO_NAME}/issues`,
};

  • API 요청 class 작성
class IssuesApi {
  private axiosInstance: AxiosInstance;

  constructor(axiosInstance: AxiosInstance) {
    this.axiosInstance = axiosInstance;
  }

  getIssues = (
    sort: IssuesSort = 'comments',
    page: number = 1,
    per_page: number = 10
  ) => {
    return this.axiosInstance.get<IssueResponse[]>(path.issues, {
      params: {
        sort,
        page,
        per_page,
      },
    });
  };
}

  • issuesApiInstanceIssuesApi 에 주입하여 인스턴스화 한 것을 export 함.
const issuesApiInstance = createAxiosInstance(GITHUB_API_URL, {
  Accept: GITHUB_ACCEPT,
  Authorization: createJwtAuthorization(env.token),
});

const issuesApi = new IssuesApi(issuesApiInstance);
export { IssuesApi, issuesApi };

무한 스크롤 (신상오)

  • IntersectionObserver API 사용
  • useRef를 활용해 마지막 요소에 스크롤 닿을 경우 페이지가 넘어가도록 구현되었습니다.
export function IssueLoader() {
  const observerRef = useRef<IntersectionObserver | null>(null);
  const divRef = useRef<HTMLDivElement>(null);
  const page = useRef(0);

  const [isEnd, setIsEnd] = useState(false);
  const { isLoading } = useIssuesState();
  const dispatch = useIssuesDispatch();
  const navigate = useNavigate();

  const onIntersect: IntersectionObserverCallback = useCallback(
    async ([entry], observer) => {
      if (entry.isIntersecting) {
        observer.unobserve(entry.target);
        dispatch({ type: 'GET_ISSUES' });
        try {
          const response = await issuesApi.getIssues(
            'comments',
            ++page.current
          );
          dispatch({
            type: 'GET_ISSUES_SUCCESS',
            data: response.data.map(parseIssue),
          });
          if (response.data.length < 10) {
            setIsEnd(true);
          } else {
            observer.observe(entry.target);
          }
        } catch (e) {
          const axiosError = e as AxiosError;
          dispatch({ type: 'GET_ISSUES_ERROR', error: axiosError });
          navigate('/error');
        }
      }
    },
    [dispatch, navigate]
  );

  useEffect(() => {
    if (divRef.current !== null) {
      observerRef.current = new IntersectionObserver(onIntersect);
      observerRef.current.observe(divRef.current);
      return () => observerRef.current?.disconnect();
    }
  }, [onIntersect]);

  if (isEnd) {
    return null;
  } else {
    return (
      <div ref={divRef}>
        {isLoading && <S.Loading>데이터를 불러오는 중입니다.</S.Loading>}
      </div>
    );
  }
}

로딩 / 에러 처리(재현)

  • useRef 사용하여 스크롤이 닿을경우 로딩이나오도록 구현
  • 에러가 있을 경우 /error로 이동됩니다.
//로딩
if (isEnd) {
    return null;
  } else {
    return (
      <div ref={divRef}>
        {isLoading && <S.Loading>데이터를 불러오는 중입니다.</S.Loading>}
      </div>
    );
  }
}

//에러 처리
const onIntersect: IntersectionObserverCallback = useCallback(
    async ([entry], observer) => {
      if (entry.isIntersecting) {
        observer.unobserve(entry.target);
        dispatch({ type: 'GET_ISSUES' });
        try {
          const response = await issuesApi.getIssues(
            'comments',
            ++page.current
          );
          dispatch({
            type: 'GET_ISSUES_SUCCESS',
            data: response.data.map(parseIssue),
          });
          if (response.data.length < 10) {
            setIsEnd(true);
          } else {
            observer.observe(entry.target);
          }
        } catch (e) {
          const axiosError = e as AxiosError;
          dispatch({ type: 'GET_ISSUES_ERROR', error: axiosError });
          navigate('/error');
        }
      }
    },
    [dispatch, navigate]
  );

Context API (승범)

  • Context 를 만들 땐 다음과 같이 React.createContext() 를 사용
  • Context 안에 Provider 컴포넌트를 통하여 Context 의 value 생성
  • Context에서 보내는 value는 ‘state’와 ‘action’
  • action type
    • GET_ISSUES_TYPE : error or loading 여부 확인
    • GET_ISSUES_SUCCESS_TYPE : 성공 처리
    • GET_ISSUES_ERROR_TYPE : error 처리
import React, { createContext, useReducer } from 'react';

...

const issuesReducer = (state: State, action: Action) => {
  switch (action.type) {
    case GET_ISSUES_TYPE:
      return {
        ...state,
        ...loadingState,
      };
    case GET_ISSUES_SUCCESS_TYPE:
      return {
        isLoading: false,
        data: [...state.data, ...action.data],
        error: null,
      };
    case GET_ISSUES_ERROR_TYPE:
      return {
        ...state,
        error: action.error,
      };
    default:
      throw new Error('유효하지 않은 타입입니다.');
  }
};

...

export const IssuesStateContext = createContext<State>(initialState);
export const IssuesDispatchContext = createContext<React.Dispatch<any>>(
  () => {}
);

export function IssuesProvider({ children }: Props) {
  const [state, dispatch] = useReducer(issuesReducer, initialState);

  return (
    <IssuesStateContext.Provider value={state}>
      <IssuesDispatchContext.Provider value={dispatch}>
        {children}
      </IssuesDispatchContext.Provider>
    </IssuesStateContext.Provider>
  );
}

...
//사용부

const onIntersect: IntersectionObserverCallback = useCallback(
    async ([entry], observer) => {
      if (entry.isIntersecting) {
        observer.unobserve(entry.target);
        dispatch({ type: 'GET_ISSUES' });
        try {
          const response = await issuesApi.getIssues(
            'comments',
            ++page.current
          );
          dispatch({
            type: 'GET_ISSUES_SUCCESS',
            data: response.data.map(parseIssue),
          });
          if (response.data.length < 10) {
            setIsEnd(true);
          } else {
            observer.observe(entry.target);
          }
        } catch (e) {
          const axiosError = e as AxiosError;
          dispatch({ type: 'GET_ISSUES_ERROR', error: axiosError });
          navigate('/error');
        }
      }
    },
    [dispatch, navigate]
  );

TS 적용

  • interface 사용

API Response 중 사용되는 부분을 따로 interface 작성

export interface Issue {
  id: number;
  number: number;
  title: string;
  body: string;
  comments: number;
  created_at: string;
  user: {
    name: string;
    profile_url: string;
  };
}

  • 컴포넌트 Props 사용 시

컴포넌트 props의 interface 이름 Props로 통일

interface Props {
  issue: Issue;
  index?: number;
}

export function _IssueItem({ issue, index }: Props) {
}

ContextAPI 커스텀 훅

  • useIssuesState()

context API를 활용하여 “상태값”을 간단히 찾는 커스텀 훅

export function useIssuesState() {
  const state = useContext(IssuesStateContext);

  if (!state) {
    throw new Error('Provider를 찾을 수 없습니다.');
  }

  return state;
}

// 예제
const { isLoading } = useIssuesState();

return (
    <div ref={divRef}>
      {isLoading && <S.Loading>데이터를 불러오는 중입니다.</S.Loading>}
    </div>
  );


  • useIssuesValue()

context API를 활용하여 “데이터”를 찾는 커스텀 훅

export function useIssuesValue() {
  const state = useContext(IssuesStateContext);

  if (!state) {
    throw new Error('Provider를 찾을 수 없습니다.');
  }

  return state.data;
}

// 예제
const issues = useIssuesValue();

return (
    <S.IssueList>
      {issues.map((issue: Issue, index) => (
        <IssueItem issue={issue} key={issue.id} index={index} />
      ))}
    </S.IssueList>
  );


  • useIssuesDispatch()

context API를 활용하여 “dispatcher” (setState)를 찾는 커스텀 훅

export function useIssuesDispatch() {
  const dispatch = useContext(IssuesDispatchContext);

  if (!dispatch) {
    throw new Error('Provider를 찾을 수 없습니다.');
  }

  return dispatch;
}

// 예제
const dispatch = useIssuesDispatch();

const onIntersect: IntersectionObserverCallback = useCallback(
  async ([entry], observer) => {
    if (entry.isIntersecting) {
      observer.unobserve(entry.target);
      dispatch({ type: 'GET_ISSUES' });
      try {
        const response = await issuesApi.getIssues(
          'comments',
          ++page.current
        );
        dispatch({
          type: 'GET_ISSUES_SUCCESS',
          data: response.data.map(parseIssue),
        });
        if (response.data.length < 10) {
          setIsEnd(true);
        } else {
          observer.observe(entry.target);
        }
      } catch (e) {
        const axiosError = e as AxiosError;
        dispatch({ type: 'GET_ISSUES_ERROR', error: axiosError });
        navigate('/error');
      }
    }
  },
  [dispatch, navigate]
);

team7-week1-2's People

Contributors

idjaeha avatar chaedie 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.