WECODE 기업협업 프로젝트 1-2

김형석·2022년 6월 29일
0

WECODE

목록 보기
30/33
post-thumbnail

프로젝트 소개

프로젝트는 현재 코인고스트 앱 페이지에서 실제로 사용되고 있는, 블로고의 리스트페이지, 상세페이지 그리고 회원가입 페이지를 구현하는 것이다.
프로젝트 github 주소

기술스택

NextJS ,React Hook ,typescript, Styled Components, SWR, Recoil

리스트, 상세 페이지 요구 사항

  • typescript 으로 작성
  • React Hook 사용
  • styled-components 사용해서 css 작업
  • data fetch 는 SWR 라이브러리 사용해서 리스트 및 상세 api 호출
  • 전체글 , 인기글 필터링 기능
  • 리스트 페이지의 경우 swr infinte scroll 사용
  • 상세 페이지는 getStaticProps , getStaticPath 이용해서 pre-rendering

리스트 페이지 작동

상세페이지 개발자도구 네트워크 확인

getStaticProps , getStaticPath 이용한 pre-rendering

1. 상세 페이지 기능 구현

상세페이지는 getStaticProps, getStaticPaths 를 사용하여 pre-rendering 기능을 구현하였다.

next.js 프리렌더링(pre-rendering)

next.js는 주로 두가지 프리렌더링 방법을 제공하는데, 기본적인 차이점은 아는 것이 좋다고 생각한다.

  • Server-side rendering(SSR): 요청할 때마다 html이 생성되기 때문에 데이터가 계속 업데이트 된다. 계속 데이터가 바뀌어야하는 페이지의 경우 사용한다.

  • Static site rendering(SSG): 빌드할때 데이터를 가져와서 html 을 생성후 사용자의 요청이 들어올때마다 빌드된 html 을 재사용한다. 페이지가 계속 업데이트 되지 않는 페이지에 사용하며, 다이나믹 라우팅을 사용하여 정적페이지를 만들경우에 getStaticProps 를 사용한다면 getStaticPaths와 함께 써주어야한다.

나중에는 SSR도 꼭 써봐야지

상세페이지 getStaticProps , getStaticPath

//[id].tsx
export const getStaticPaths: GetStaticPaths = async () => {
	const res = await fetch(`${API.BLOGS}`);
	const post = await res.json();
	const posts = post.data.data;

	const paths = posts.map((post: { id: number }) => ({
		params: { id: post.id.toString() },
	}));
	return { paths, fallback: false };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
	const res = await fetch(`${API.BLOGS}/${params?.id}`);
	const post = await res.json();
	return { props: { post } };
};

정적페이지만 만든다고 한다면 getStaticProps만 사용하면 된다. 하지만 다이나믹 라우팅을 사용하여 정적페이지를 만들경우에 위에서 말했던 것처럼 getStaticProps 를 사용한다면 getStaticPaths와 함께 써주어야한다.

getStaticPaths 에서 params에 빌드하고 싶은 페이지를 넣어야 한다. blogs/[id].tsx에 값이 1이라면 params에 {id:'1'}로 넣어주는 것이다.
즉 빌드할 페이지들을 서버에서 다 가져오서 html 로 뽑아놓겠다는 것이다.
나는 map 함수를 사용하여 데이터의 모든 id값을 뽑아 params에 넣어주어 모든 페이지를 html로 뽑아 두었다.

미리 빌드한 페이지말고 새로운 페이지 요청이 들어 올 경우를 대비해서 fallback을 설정하는데 모든 페이지의 id값을 넣어주었기 때문에 false로 설정하였다.(React Suspense라고 알고 있는데 여기서도 쓰여 신기했음)

getStaticPaths과 getStaticProps을 사용하면서 데이터가 console에 안찍혀서 처음에 문제를 찾는데 시간이 오래 걸렸다...서버에서 작동이 되기 때문에 당연한건데 생각을 못했다...

상세페이지는 이게 끝이었다. 하지만 여러 components에 데이터를 주고 있어 전역관리를 해주고 싶었다.

//[id].tsx
		<Layout background="#5382eb">
			<Header />
			<UserInfo post={post.data} />
			<Content post={post.data}/>
			<DetileBtn />
			<Banner />
			<Chat post={post.data}/>
			<PreNexBtn post={post.data}/>
			<Footer />
		</Layout>

recoil 사용해보기

그레서 간편하다는 recoil을 사용해보기로 했다.
처음 사용해보고 이미 받아 온 데이터를 저장해서 뿌려주는 기능만을 사용하여 recoil의 atom이라는 기능만 사용하였다.
recoil사용법을 많이 찾아 보았는데, 받은 데이터를 전역으로 관리 및 사용한 자료는 없어 사용법을 익혀 내가 그냥 사용해보았다. 더 좋은 방법도 있을 것이다.

//_app.tsx
import { RecoilRoot } from 'recoil';

function MyApp({ Component, pageProps }: AppProps) {
	return (
		<ThemeProvider theme={theme}>
			<GlobalStyle />
			<RecoilRoot>
				<Component {...pageProps} />
			</RecoilRoot>
		</ThemeProvider>
	);
}

recoil 상태를 사용하는 컴포넌트는 RecoilRoot가 필요하다. 루트 컴포넌트가 RecoilRoot를 넣기에 가장 좋은 장소다.

//recoil.tsx
import { atom } from 'recoil';

export interface DetailProps {
	id: number;
	title: string;
	contents: string;
	creator: { nickName: string };
	createdAt: string;
	defaultThumbnail: { url: string };
	likes: number;
	comments: number;
	views: number;
}

interface DataProps {
    nextID: number;
	nextTitle: string;
	nextCreatedAt: string;
	previousID: number;
	previousTitle: string;
	previousCreatedAt: string;
    data: DetailProps;
}

interface Props {
	data: DataProps
}

export const blogStore = atom<Props>({
	key: 'Blog',
	default: {
		data: {
            nextID: 0,
            nextTitle: '',
            nextCreatedAt: '',
            previousID: 0,
            previousTitle: '',
            previousCreatedAt: '',
			data: {
				id: 0,
				title: '',
				contents: '',
				creator: { nickName: '' },
				createdAt: '',
				defaultThumbnail: { url: '' },
				likes: 0,
				comments: 0,
				views: 0,
			},
		},
	},
});

atom에 키 값(유니크한 값)을 정해주고 저장 될 데이터의 기본 값을 default로 넣어준다.(저렇게 하나하나 다 정해야하는 건지는 아직 의문이다.)

//[id].tsx
import { useSetRecoilState } from 'recoil';
import { blogStore } from '../utils/recoilStart';


const blog = useSetRecoilState(blogStore);
useEffect(() => {
  blog(post);
}, [blog, post]);

기본적인 사용법은 useState와 사용이 비슷했다.
받은 데이터를 저장만 해주면 되었기에 useSetRecoilState만 사용했다. 보통 useRecoilState를 사용하여 state처럼 사용하는 것 같다. blogStore라는 atom을 import하여, useSetRecoilState 사용해 데이터를 넣어주었다.

//UserInfo.tsx
import { useRecoilValue } from 'recoil';
import { blogStore } from '../../utils/recoilStart';


export default function UserInfo() {
	const posts = useRecoilValue(blogStore);
	const { title, creator, createdAt, views } = posts.data.data;
	return (
		<Container>
			<ProfileBox>
				<Image src="/images/detail.png" width={80} height={80} alt="profile" />
				<ProfileInfo>
					<NickName>{creator.nickName}</NickName>
					<Date>{timeCut(createdAt)}</Date>
					<Views>조회수 {views}</Views>
				</ProfileInfo>
				<Vertical>
					<VeticalIcon />
				</Vertical>
			</ProfileBox>
			<Bar />
		</Container>
	);
}

데이터를 받을 components에서 useRecoilValue와 atom을 import하여 저장되어 있는 데이터를 가져와 사용했다.

작동이 되기 전까지는 이렇게만 하면 작동이 정말 작동이 되려나라는 생각을 많이 했는데 잘 작동한다...맞게 사용했기를...

recoil을 완전히 사용해보지는 못했지만 아직은 Redux를 많이 사용하고 있어 Redux를 사용해 보는 것이 좋다고 들었다. 2차 과제에는 Redux를 사용해 보고싶다.
회원가입 페이지는 다음 글에 한번에 정리해 볼 생각이다.

profile
블로그 이사 : https://hengxi.tistory.com

0개의 댓글