카테고리 없음

클론 프로젝트 회고2

seongEun95 2024. 5. 18. 11:30


프로젝트 및 스터디 모집 웹사이트를 클론하는 프로젝트를 진행했습니다.

 

https://github.com/seongEun95/holu

 

GitHub - seongEun95/holu

Contribute to seongEun95/holu development by creating an account on GitHub.

github.com

 

 

1.  새롭게 경험한 것

1) OAuth (카카오 로그인)

카카오로그인 과정

- 먼저 카카오 로그인을 사용하기 위한 api키를 얻기 위해 https://developers.kakao.com/ 카카오 개발자센터에서 REST API 키를 받았습니다. 

- 카카오개발자센터에서 카카오로그인 등록과정은 다음 노션링크에 정리했습니다. 

https://jumbled-carrot-837.notion.site/f1239dd3fbd34df08588cfc7b8f91037?pvs=4

- 새 글쓰기를 진행하기전에 로그인한 유저만 글을 쓸 수 있도록 하기 위하여  새 글쓰기 버튼을 클릭하면 카카오 로그인 화면으로 이동합니다.

- 로그인이 완료되면 사용자가 로그인 여부를 확인하기 위해 헤더영역의 ui가 변경되도록 했습니다. 로그인 완료 시 유저 정보를 리덕스에 저장하고 그 정보를 헤더 컴포넌트에서 가져와서 이름을 표시했습니다.

 

 

2) 반응형 디자인

 

- 데스크탑 뿐만 아니라 테블릿, 모바일에서도 서비스를 이용하기 위해 미디어쿼리를 이용하여 반응형 디자인을 구현했습니다.

const breakpoints = {
	mobile: 576,
	tablet: 768,
	notebook: 991,
	desktop: 1350,
}; // 미디어 쿼리 분기점

const mq = {
	mobile: `@media (max-width: ${breakpoints.mobile}px)`,
	tablet: `@media (max-width: ${breakpoints.tablet}px)`,
	notebook: `@media (max-width: ${breakpoints.notebook}px)`,
	desktop: `@media (max-width: ${breakpoints.desktop}px)`,
};

export default mq;

- 각각의 분기점을 객체 변수로 담아서 다른 파일에서 공통으로 사용할 수 있도록 했습니다.

const cardWrapCss = css`
	display: grid;
	grid-template-columns: repeat(4, 300px);

	${mq.desktop} {
		grid-template-columns: repeat(3, 300px);
		justify-content: center;
	}

	${mq.notebook} {
		grid-template-columns: repeat(2, 300px);
	}

	${mq.tablet} {
		grid-template-columns: repeat(1, 1fr);
	}
`;

- 미리 설정한 mq 변수값을 가져와서 깔끔하게 코드를 작성할 수 있습니다.

- EmotionCss 공식홈페이지에서 참고를 했습니다. https://emotion.sh/docs/media-queries

 

 

3) 글쓰기 (Create)

글쓰기 페이지

 

- 위 이미지와 같이 여러 데이터를 저장하는 경우 글 등록 시 개별 값을 저장하기 보단 데이터를 한 번에 처리하는 것이 더 관리가 쉬워집니다.

- input 등의 태그들은 value와 name 속성이 존재하여 해당 값을 가져올 수 있지만 디자인을 위해 select태그가 아닌 div태그로 드랍다운을 구현한 경우 입력받는 태그가 아니다보니 value, name속성이 존재하지 않는다. 이 문제를 해결하기 위해 name값과 value값을 추가하는 함수를 작성합니다.

// 유틸리티 함수
export const provideAttr = (name: string, value: any, e: any) => {
	e.target.name = name;
	e.target.value = value;
	return e;
};

- 이벤트 객체의 타겟에 name값과 value속성을 추가하고 name값에는 각 입력정보의 제목, value값은 사용자의 입력값을 받게 됩니다.

// SelectBox 컴포넌트
const handleClickSelectItme = (selectedValue: string, e: any) => {
		onClick?.(provideAttr(name, selectedValue, e));
	};

- 위 provideAttr함수를 사용할 컴포넌트에서 함수 인자로 name값, 유저의 선택된 값, 이벤트 객체를 전달합니다.

 

// SelectBox 컴포넌트를 사용하는 페이지
<SelectBox
	name="progressPeriod"
	value={userInput.progressPeriod}
	label="진행 기간"
	options={OPTIONS_PROGRESS_PERIOD}
	placeholder="기간 미정~6개월 이상"
	onClick={handleClickGetItem}
/>

- 최 상위 컴포넌트(페이지)에서 name값과 value값을 props로 전달합니다.

 

const [userInput, setUserInput] = useState<UserInput>({
		category: '',
		personCount: '',
		onlineOrOffline: '',
		progressPeriod: '',
		skillStack: [],
		deadline: dayjs(),
		position: [],
		contactMethod: '',
		contactDetail: '',
		projectTitle: '',
		contents: '',
	});

	const handleClickGetItem = (e: any) => {
		const { name, value } = e.target;
		setUserInput(prev => ({ ...prev, [name]: value }));
	};

- 결론적으로 유저가 드랍다운에서 값을 클릭하면 handleClickGetItem 함수를 실행하여 이벤트 객체의 타켓 속성의 name과 value값을 구조분해할당으로 변수에 할당합니다.

- setUserInput 함수를 실행하여 각각의 네임 값에 따라 value값을 넣어줍니다.

- name과 value값을 통일하게 되면 값을 받아오는 함수를 공통으로 사용할 수 있어 추후 유지보수하기에도 용이해집니다.

- 마지막으로 벡엔드와 통신을 통해 body에 데이터를 담아 전달하면 됩니다.

 

* 추가

- 라이브러리(MuiDatePicker) 컴포넌트로 감싸서 사용하기

export default function DatePicker({ name, value, label, onChange }: DatePickProps) {
	const handleChangeValue = (value: Dayjs | null) => {
		onChange?.({ target: { name, value } } as any);
	};

	return (
		<React.Fragment>
			<div css={labelCss}>{label}</div>
			<MuiDatePicker name={name} value={value} css={datePickerCss} format="YYYY-MM-DD" onChange={handleChangeValue} />
		</React.Fragment>
	);
}

- 라이브러리를 한 번 더 컴포넌트로 사용하여 다른 컴포넌트의 ui와 획일화된 모습으로 사용할 수 있게 합니다.

- 다른 컴포넌트와 동일하게 props를 전달받기 위해 한 번 더 컴포넌트로 감싸기도 합니다.

MuiDatePicker 컴포넌트

 

 

4) 벡엔드 api작업 전 목업데이터

- 벡엔드 api작업이 완료되기 전에 ui시안을 확인하여 프론트엔드 쪽 선 작업하는 과정을 진행했습니다.

- api가 완료된 후 목업데이터와 실제 api의 데이터 양식이 달라 에러가 발생하였고 이 에러들을 수정하는 작업을 진행했습니다.

- 실제 업무 중에서도 충분히 발생할 수 있는 문제이기에 좋은 경험이라고 생각합니다.

목업데이터 타입 vs 실제데이터

 

 

5) 필터 기능

필터 기능 gif

- 벡엔드 통신을 통해 받아온 데이터를 프론트엔드에서 중첩 필터기능이 적용되는 기능입니다.

cardData.filter(item => {
		let isCategoryPassed = true;
		let isSkillPassed = true;
		let isPositionPassed = true;
		let isProgressMethodPassed = true;
		let isSearch = true;

		if (selectedTab !== 'ALL') isCategoryPassed = item.type === selectedTab;

		...
        
		return isCategoryPassed && isSkillPassed && isPositionPassed && isProgressMethodPassed && isSearch;
	});

- 각각 카테고리별로 변수 초기값을 true로 설정하여 모든 값을 반환하지만 사용자가 필터를 설정하면 조건에 따라 false가 되는 데이터는 필터링이 됩니다.

 

 

6) 북마크

북마크

- 유저가 북마크 버튼을 클릭했을 때 내 관심글 페이지에서 이를 확인할 수 있습니다.

- 북마크 버튼 클릭 시 리덕스에 카드 데이터를 저장하고 저장된 데이터를 내 관심글 페이지에서 map메소드로 렌더하는 과정을 거쳤습니다.

// 내 관심글 페이지
const bookmarkItems = useSelector((state: RootState) => state.bookmark.items);

return (
	{bookmarkItems.length === 0 ? (
		<div css={emptyBookmarkCss}>
			<CgTrashEmpty size={40} />
			<div>내 관심글 없음</div>
		</div>
	) : (
		bookmarkItems.map((item: Bookmark) => <Card key={item.id} {...item} />)
	)}
)

 

2. 프로젝트 후기

- 웹사이트에 흔히 볼 수 있는 카카오로그인을 직접 설정해보면서 어떤 과정을 거치는지 학습할 수 있었고 다음 프로젝트 때 적용한다면 더 빠르게 구현해볼 수 있을 것 같습니다.

 

- 순수 Css와 리액트 프로젝트에서의 반응형 적용하는 원리는 비슷하지만 emotionCss 라이브러리에 알맞게 적용해보았습니다. 반복되는 코드는 변수에 담고 이를 불러와서 사용하면서 코드량은 줄어들고 더 효율적으로 반응형을 구현할 수 있었습니다.

 

- CRUD 중 Create기능을 집중적으로 해보면서 여러 데이터를 한 번에 서버로 전달하는 방법을 학습할 수 있었습니다. 대부분의 웹사이트에서 사용하는 기능이라 생각하여 이 프로젝트에서 가장 중요한 영역이라고 생각합니다.

 

- Create페이지에서 muiDatePicker와 antd라이브러리 등 잘 만들어진 ui를 적용해보면서 실제 업무에서 납기기간이 충분하지 않을 경우 이런 부분에서 빠르게 적용할 수 있음을 학습할 수 있었습니다. 

 

- 벡엔드 api가 다 만들어지기 전까지 프론트엔드에서 계속 기다릴 수 없으므로 디자인 시안을 토대로 임의의 목업데이터를 만들어 미리 ui를 구현했고 실제 api를 전달받았을 때 상이한 영역은 빠르게 수정하여 ui를 구현할 수 있었습니다. 위에서도 언급 했듯이 실제 업무에서 api를 늦게 전달받을 수도 있으므로 선 작업 해보는 과정은 좋은 학습이었습니다.

 

- 쇼핑몰이나 여러 웹사이트에서 빠르게 데이터를 찾는 필터링 기능을 학습할 수 있었습니다. 중첩 필터링 기능을 학습해보면서 filter메소드 내부 로직 원리를 이해할 수 있었습니다.

 

- 마지막으로 프로젝트를 진행해보면서 단순히 ui를 구현하는 것은 조금이나마 익숙해졌다고 생각하지만 좀 더 설계하는 영역, 전체를 볼 수 있는 부분에서는 부족하다는 것을 느낍니다. 다음 프로젝트에서는 더 개선해볼 수 있도록 하겠습니다.