유투브 소플님의 강의를 보고 미니블로그 만들기를 진행한 내용을 정리해봤다.
✅ 글 목록 보기
✅ 글 보기
✅ 댓글 보기
✅ 글 작성하기(일단 화면만 개발)
✅ 댓글 작성하기(일단 화면만 개발)
✅ npx create-react-app 명령어 사용
✅ react-router-dom v6와 styled-components v5를 사용할 예정
npm install --save react-router-dom styled-components
--save : 지금 설치하는 패키지들을 package.json파일이 관리하는 의존성 목록에 저장하겠다는 의미이다.
설치가 완료되면 package.json의 dependencies 쪽에서 확인이 가능하다.(react-router-dom, styled-components)
dependencies은 해당 프로젝트의 패키지들. 즉 의존성 목록을 나타낸다.
다른 사람들이 이 프로젝트를 설치할 때 npm install 명령어를 실행하면 dependencies 목록을 기준으로 설치가 되기 때문에 프로젝트에 꼭 필요한 패키지를 설치할 때는 —save 옵션을 추가하여 dependencies 목록에 추가되도록 해야한다.
→ 이 컴포넌트들을 사용해서 다른 컴포넌트를 빠르게 개발 할 수 있음
✅ 글 목록을 볼 수 있는 리스트 형태의 컴포넌트 : PostList, PostListItem
✅ 글 내용을 볼 수 있는 컴포넌트 : Post
✅ 댓글 목록을 볼수 있는 컴포넌트 : CommentList, CommentListItem
✅ 글을 작성 할 수 있는 컴포넌트 : PostWrite
✅ 댓글을 작성 할 수 있는 컴포넌트 : CommentWrite
src
- component
- list : 리스트와 관련된 컴포넌트들을 모아놓은 폴더
- page : 페이지 컴포너트들을 모아놓은 폴더
- ui : UI 컴포넌트들을 모아놓은 폴더
✅ Bottom up 방식으로 작은 부분부터 구현할 것
✅ 버튼을 클릭할 수 있는 Button 컴포넌트
✅ 텍스트를 입력할 수 있는 Text Input 컴포넌트
// Button.jsx
import React from 'react';
import styled from 'styled-components'
const StyledButton = styled.button`
padding: 8px 16px;
font-size : 16px;
border-width: 1px;
border-radius: 8px;
cursor:pointer;
`;
export default function Button(props) {
const {title, onClick} = props;
return <StyledButton onClick={onClick}>{title || 'button'}</StyledButton>
}
// TextInput.jsx
import React from "react";
import styled from "styled-components";
const StyledTextarea = styled.textarea`
width: calc(100% - 32px);
${(props)=>
props.height &&
`
height: ${props.height}px;
`
}
padding: 16px;
font-size: 16px;
line-height: 20px;
`;
export default function TextInput(props) {
const {height, value, onChange} = props;
return <StyledTextarea height={height} value={value} onChange={onChange} />;
}
✅ PostList 컴포넌트에서 PostListItem를 사용할 것이기 때문에 PostListItem 컴포넌트부터 만들 것임!(작은 컴포넌트를 먼저 구현하고 그걸 사용하는 큰 컴포넌트를 구현한다.)
// PostListItem.jsx
import React from "react";
import styled from "styled-components";
const Wrapper = styled.div`
width: calc(100$ - 32px);
padding: 16px;
display: flex;
flex-direction: column;
align-items: flex-start;
justidy-content: center;
border: 1px solid grey;
border-radius: 8px;
cursor: pointer;
background: white;
:hover {
background: lightgrey;
}
`;
const TitleText = styled.p`
font-size: 20px;
font-weight: 500;
`;
export default function PostListItem(props) {
const {post, onClick} = props;
return (
<Wrapper onClick={onClick}>
<TitleText>{post.title}</TitleText>
</Wrapper>
);
}
✅ map() 함수를 사용하여 글의 개수만큼 생성할 것임!
// PostList.jsx
import React from "react"
import styled from "styled-components"
import PostListItem from "./PostListItem"
const Wrapper = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
& > * {
:not(:last-child) {
margin-bottom: 16px;
}
}
`;
export default function PostList(props) {
const {posts, onClickItem} = props;
return (
<Wrapper>
{posts.map((post, index)=>{
return (
<PostListItem
key={post.id}
post={post}
onClick={()=>{
onClickItem(post);
}}
/>
);
})}
</Wrapper>
)
}
✅ props로 comment 객체를 받아 처리한다.
// CommentListItem.jsx
import React from "react";
import styled from "styled-components";
const Wrapper = styled.div`
width: calc(100% - 32px);
padding: 16px;
display: flex;
flex-derection: column;
align-items: flex-start;
justify-content: center;
border: 1px solid grey;
border-radius: 8px;
cursor: pointer;
background: white;
:hover {
background: lightgrey;
}
`;
const ContentText = styled.p`
font-size: 14px;
`;
export default function CommentListItem(props) {
const {comment} = props;
return (
<Wrapper>
<ContentText>{comment.content}</ContentText>
</Wrapper>
);
}
✅ PostList 컴포넌트와 비슷. map() 함수를 사용하여 넘겨받은 comments 만큼 댓글을 생성한다.
// CommentList.jsx
import React from "react";
import styled from "styled-components";
import CommentListItem from "./CommentListItem";
const Wrapper = styled.div`
display: flex;
flex-direction: column;
aligj-items: flex-start;
justify-content: center;
& > * {
:not(:last-child) {
margin-bottom: 16px;
}
}
`;
export default function CommentList(props) {
const { comments } = props;
return (
<Wrapper>
{comments.map((comment, index)=>{
return <CommentListItem key={comment.id} comment={comment} />
})}
</Wrapper>
);
}
✅ src 폴더 내에 data.json 파일 만들기(화면에 뿌려줄 글 목록, 댓글 관련 데이터를 미리 생성해둔다)
// MainPage.jsx
import React from "react";
import styled from "styled-components";
import {useNavigate} from "react-router-dom";
import PostList from "../list/PostList";
import Button from "../ui/Button";
import data from "../../data.json";
const Wrapper = styled.div`
padding: 16px;
width: calc(100% - 32px);
display: flex;
flex-direction: column;
align-item:center;
justify-content: center;
`;
const Container = styled.div`
width: 100%;
max-width: 720px;
& > * {
:not(:last-child) {
margin-bottom: 16px;
}
}
`;
export default function MainPage(props) {
const {} = props;
const navigate = useNavigate();
return (
<Wrapper>
<Container>
<Button title="글 작성하기" onClick={()=>{
navigate("/post-write");
}}
/>
<PostList
posts={data}
onClickItem={(item)=>{
navigate(`/post/${item.id}`);
}}
/>
</Container>
</Wrapper>
);
}
// PostWritePage.jsx
import React, {useState} from "react"
import styled from "styled-components"
import { useNavigate } from "react-router-dom"
import TextInput from "../ui/TextInput"
import Button from "../ui/Button"
const Wrapper = styled.div`
padding: 16px;
width: calc(100% - 32px);
display: flex;
flex-direction:column;
align-items: center;
justify-contet: center;
`;
const Container = styled.div`
width: 100%;
max-width: 720px;
& > * {
:not(:last-child){
margin-bottom: 16px;
}
}
`;
export default function PostWritePage(props) {
const navigate = useNavigate();
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
return (
<Wrapper>
<Container>
<TextInput
height={20}
value={title}
onChange={(event)=>{
setTitle(event.target.value);
}}
/>
<TextInput
height={480}
value={content}
onChange={(event)=>{
setContent(event.target.value);
}}
/>
<Button
title="글 작성하기"
onClick={()=>{
navigate('/');
}}
/>
</Container>
</Wrapper>
)
}
✅ props 로 전달받은 글의 id를 이용하여 전체 데이터에서 해당되는 글을 찾는다.
→ 찾은 글의 제목 내용 댓글을 화면에 렌더링하고 그 아래에는 textInput 컴포넌트와 버튼 컴포넌트를 사용하여 댓글을 작성할 수 있도록 ui 제공한다.
// PostViewPage.jsx
import React, {useState, usestate} from "react";
import { useNavigate, useParams } from "react-router-dom";
import styled from "styled-components";
import CommentList from "../list/CommentList";
import TextInput from "../ui/TextInput";
import Button from "../ui/Button";
import data from "../../data.json";
const Wrapper = styled.div`
padding: 16px;
width: calc(100% - 32px);
display: flex;
flex-direction: column;
align-items: center;
justify-contetn: center;
`;
const Container = styled.div`
width:100%;
max-width: 720px;
& > * {
:not(:last-child){
margin-bottom: 16px;
}
}
`;
const PostContainer = styled.div`
padding: 8px; 16px;
border: 1px; solid grey;
border-radius: 8px;
`;
const TitleText = styled.p`
font-size: 28px;
font-weight: 500;
`;
const ContentText = styled.p`
font-size: 20px;
line-height: 32px;
white-space: pre-wrap;
`;
const CommentLabel = styled.p`
font-size: 16px;
font-weight: 500;
`;
export default function PostViewPage(props) {
const navigate = useNavigate();
const {postId} = useParams();
const post = data.find((item)=> {
return item.id = postId;
});
const [comment, setComment] = useState('');
return (
<Wrapper>
<Container>
<Button
title="뒤로가기"
onClick={()=>{
navigate('/');
}}
/>
<PostContainer>
<TitleText>{post.title}</TitleText>
<ContentText>{post.content}</ContentText>
</PostContainer>
<CommentLabel>댓글</CommentLabel>
<CommentList comments={post.comments} />
<TextInput
height={40}
value={comment}
onChange={(event)=>{
setComment(event.target.value);
}}
/>
<Button
title="댓글 작성하기"
onClick={()=>{
navigate('/');
}}
/>
</Container>
</Wrapper>
)
}
✅ 각 경로에 맞게 맵핑 해주는 작업이 필요하다.
해당 프로젝트에서는 라우팅을 구성하기 위해 세 가지 컴포넌트를 사용한다.
✅ BrowserRouter - HTML5을 지원하는 브라우저의 주소를 감지
✅ Routes - 여러 개의 라우트 컴포넌트를 children으로 가짐
✅ Route - Routes 컴포넌트의 하위 컴포넌트 path와 element 라는 props를 가짐
→ path는 경로를 나타낸다.
→ element는 경로가 일치할 경우 렌더링 할 리액트의 element를 의미한다.
✅ 페이지 이동을 위해 사용할 Hook
✅ Link 컴포넌트를 사용하지 않고 다른 페이지로 이동해야할 경우 뒤로 가기 등에 사용하는 Hook
✅ replace 옵션을 사용하면 페이지 이동시 히스토리를 남기지않는다.
✅ Link 컴포넌트로 만든 부분을 클릭하면 URL 경로가 바뀌고 해당 경로로 지정된 컴포넌트가 노출된다.
✅ 클릭하면 이동하기 때문에 다른 연산과정 없이 페이지를 이동할 때 사용한다.
✅ 개발자도구에서는 <a href=#>로 보인다.
✅ 실행하면 페이지 이동을 할 수 있게 해주는 함수를 반환한다.
✅ 반환하는 함수를 변수에 저장한 후 변수의 인자로 설정한 path값을 넘겨주면 해당 경로로 이동 가능하다.
✅ 조건이 필요한 곳에서 navigate함수를 호출해서 경로 이동할 수 있다.
✅ useNavigate는 Hook이기 때문에 Hook의 규칙을 지켜야한다.
✅ Hook의 규칙 → 컴포넌트 내 최상위에서만 Hook을 호출해야한다.
✅ Link : 클릭 시 바로 이동하는 로직을 구현할 때 사용
✅ useNavigate : 페이지 이동시 추가로 처리해야하는 로직이 있을 경우 사용
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import styled from "styled-components";
import MainPage from "./component/page/MainPage";
import PostWritePage from "./component/page/PostWritePage";
import PostViewPage from "./component/page/PostViewPage";
const MainTitleText = styled.p`
font-size: 24px;
font-weight: bold;
text-align: center;
`;
function App() {
return (
<BrowserRouter>
<MainTitleText>미니 블로그</MainTitleText>
<Routes>
<Route index element={<MainPage />} />
<Route path="/post-write" element={<PostWritePage />} />
<Route path="/post/:postId" element={<PostViewPage />} />
</Routes>
</BrowserRouter>
);
}
export default App;
✅ :postId
동적으로 변하는 파라미터를 위한 값.
실제 컴포넌트에서 useParams Hook을 사용하여 id로 해당 값을 가져올 수 있다.
Reference
유투브 소플님의 처음 만난 리액트 강의를 보고 작성하였습니다.
https://ko.legacy.reactjs.org/docs/hooks-rules.html
상태 관리 라이브러리 - Recoil (0) | 2023.07.04 |
---|---|
상태 관리 라이브러리 - Redux (0) | 2023.07.04 |
[React] 기초 정리 - 여러 개의 input 상태 관리하기 (0) | 2023.05.30 |
[React] 기초 정리 - 조건부 렌더링 (0) | 2023.05.30 |
[React] 기초 정리 - 초기세팅, CSS 적용, useState (0) | 2023.05.22 |
댓글 영역