- 개요 : 원티드 프론트엔드 프리온보딩 7기 1팀 과제 1-2 중 Best Practice
- 주제 : 특정 깃헙 레파지토리의 이슈 목록과 상세 내용을 확인하는 웹 사이트 구축
- 기간 : 2022.10.29 ~ 2022.10.30
- React
- Typescript
- Styled-Components
- 이슈 목록 페이지 구현
- 무한스크롤
- 광고배너 삽입
- 상세 이슈 페이지 구현
- Context API를 활용한 이슈 상태관리 및 API 연동
- 데이터 요청 중 로딩 표시
- 에러 화면 구현
- 지정조건에 맞게 데이터 요청 및 표시
- 반응형 웹 구현
src
├── apis // issue 관련 api service 요청
├── assets // 전역 스타일링
├── components // 공용 컴포넌트
├── context // issue 상태관리 context
├── hooks // scroll 관련 커스텀 훅
├── pages // 페이지 및 페이지 고유 컴포넌트
├── types // 공용타입 관리
└── utils // dateformatting, axios 관련 유틸 함수
export const useInfiniteScroll = (
onIntersect: () => void,
options?: IntersectionObserverInit
) => {
const [target, setTarget] = useState<Element | null>(null);
const handleIntersect = useCallback(
([entry]: IntersectionObserverEntry[]) => {
if (entry.isIntersecting) {
onIntersect();
}
},
[onIntersect]
);
useEffect(() => {
const observer = new IntersectionObserver(handleIntersect, options);
target && observer.observe(target);
return () => {
observer.disconnect();
};
}, [handleIntersect, target, options]);
return [setTarget];
};
Intersection Opserver API
활용해 scrollEvent를 이용한 것에 비해 최적화를 효율적으로 적용한 점이 좋았습니다. - 박우빈- 재사용성을 위해 커스텀훅으로 분리한 부분이 좋았습니다. - 김진석
Context API
useReducer
를 활용하여Flux 패턴
으로 서버 데이터 관리
export enum IssueActionTypes {
GET_ISSUE_LIST_SUCCESS = "GET_ISSUES_LIST_SUCCESS",
GET_ISSUE_LIST_LOADING = "GET_ISSUES_LIST_LOADING",
GET_ISSUE_LIST_ERROR = "GET_ISSUES_LIST_ERROR",
GET_ISSUE_DETAIL_SUCCESS = "GET_ISSUES_DETAIL_SUCCESS",
GET_ISSUE_DETAIL_LOADING = "GET_ISSUES_DETAIL_LOADING",
GET_ISSUE_DETAIL_ERROR = "GET_ISSUES_DETAIL_ERROR",
}
const initialState: IssueCtxInitialState = {
isLoading: false,
isError: false,
issueList: [],
issueDetail: null,
};
const issueReducer = (
state: IssueCtxInitialState,
action: { type: IssueActionTypes; data?: Issue[] | Issue }
) => {
switch (action.type) {
case IssueActionTypes.GET_ISSUE_LIST_LOADING:
return {
...state,
isLoading: true,
isError: false,
};
case IssueActionTypes.GET_ISSUE_LIST_SUCCESS:
return {
...state,
issueList: [...state.issueList, ...(action.data as Issue[])],
isLoading: false,
isError: false,
};
case IssueActionTypes.GET_ISSUE_LIST_ERROR:
return {
...state,
isLoading: false,
isError: true,
};
case IssueActionTypes.GET_ISSUE_DETAIL_LOADING:
return {
...state,
isLoading: true,
isError: false,
};
case IssueActionTypes.GET_ISSUE_DETAIL_SUCCESS:
return {
...state,
issueDetail: action.data as Issue,
isLoading: false,
isError: false,
};
case IssueActionTypes.GET_ISSUE_DETAIL_ERROR:
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error("action type을 확인해주세요.");
}
};
const IssueStateContext = createContext<IssueCtxInitialState>(initialState);
const IssueDispatchContext = createContext<React.Dispatch<{
type: IssueActionTypes;
data?: Issue[] | Issue;
}> | null>(null);
export const IssueProvider = ({ children }: { children: JSX.Element }) => {
const [state, dispatch] = useReducer(issueReducer, initialState);
return (
<IssueStateContext.Provider value={state}>
<IssueDispatchContext.Provider value={dispatch}>
{children}
</IssueDispatchContext.Provider>
</IssueStateContext.Provider>
);
};
- 단방향 데이터 흐름으로 복잡성을 줄인점이 인상적이었습니다. - 조성호
- Git Clone
$ git clone https://github.com/pre-onboading-2team/Week1_2_Issue_List.git
- 프로젝트 패키지 설치
$ npm install
- 프로젝트 실행
$ npm start