Hello It's good to be back ^_^

[ECC-프론트엔드 2팀] 17주차 스터디 본문

Study/프론트엔드

[ECC-프론트엔드 2팀] 17주차 스터디

HongYeon 2025. 1. 16. 15:20

교재:  소플의 처음 만난 리액트 14 ~ 16

공부한 페이지: pp. 390 ~ 513

실습한 내용: https://github.com/HongYeonLee/ECC-Frontend-Study

 

GitHub - HongYeonLee/ECC-Frontend-Study: ECC 49기 FW 프론트엔드 2팀 스터디 내용을 기록하는 레포지토리입

ECC 49기 FW 프론트엔드 2팀 스터디 내용을 기록하는 레포지토리입니다. Contribute to HongYeonLee/ECC-Frontend-Study development by creating an account on GitHub.

github.com

 


목차

14 컨텍스트

  • 14.1 컨텍스트란 무엇인가?
  • 14.2 언제 컨텍스트를 사용해야 할까?
  • 14.3 컨텍스트를 사용하기 전에 고려할 점
  • 14.4 컨텍스트 API
  • 14.5 여러 개의 컨텍스트 사용하기
  • 14.6 useContext
  • 14.7 컨텍스트를 사용하여 테마 변경 기능 만들기

15 스타일링

  • 15. 1 CSS
  • 15.2  styled-components
  • 15.3 styled-components를 사용하여 스타일링해 보기

16 미니 프로젝트-미니 블로그 만들기


실습 - 컨텍스트를 사용하여 테마 변경 기능 만들기 

코드
ThemeContext.jsx

import React from "react";

const ThemeContext = React.createContext();

ThemeContext.displayName = "ThemeContext";

export default ThemeContext;

 

MainContent.jsx

import { useContext } from "react";
import ThemeContext from "./ThemeContext";

function MainContent(props){
    const { theme, toggleTheme } = useContext(ThemeContext);

    return (
        <div style = {{
            width: "100vw",
            height: "100vh",
            padding: "1.5rem",
            backgroundColor: theme == "light" ? "white" : "black",
            color: theme == "light" ? "black" : "white",
        }}
        >
            <p>안녕하세요, 테마 변경이 가능한 웹사이트 입니다.</p>
            <button onClick = {toggleTheme}>테마 변경</button>
        </div>
    );
}

export default MainContent;

 

DarkOrLight.jsx

import { useState, useCallback } from "react";
import ThemeContext from "./ThemeContext";
import MainContent from "./MainContent";

function DarkOrLight(props){
    const [theme, setTheme] = useState("light");

    const toggleTheme = useCallback(() => {
        if (theme == "light"){
            setTheme("dark");
        }
        else if (theme == "dark"){
            setTheme("light");
        }
    }, [theme]);

    return (
        <ThemeContext.Provider value = {{ theme, toggleTheme }}>
            <MainContent />
        </ThemeContext.Provider>
    );
}

export default DarkOrLight;

 

실행화면

 


 

실습 - styled-components를 사용하여 스타일링해 보기
코드

import styled from "styled-components";

const Wrapper = styled.div`
    padding: 1rem;
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    justify-content: flex-start;
    background-color: lightgrey;
`;

const Block = styled.div`
    padding: ${(props) => props.padding};
    border: 1px solid black;
    border-radius: 1rem;
    background-color: ${(props) => props.backgroundColor};
    color: white;
    font-size: 2rem;
    font-weight: bold;
    text-align: center;
`;

const blockItems = [
    {
        label: "1",
        padding: "1rem",
        backgroundColor: "red",
    },
    {
        label: "2",
        padding: "3rem",
        backgroundColor: "green",
    },
    {
        label: "3",
        padding: "2rem",
        backgroundColor: "blue",
    },

];

function Blocks(props){
    return (
        <Wrapper>
            {blockItems.map((blockItem) => {
                return(
                    <Block
                        padding = {blockItem.padding}
                        backgroundColor = {blockItem.backgroundColor}
                    >
                        {blockItem.label}
                    </Block>
                );
            })}
        </Wrapper>
    );
}

export default Blocks;

 

실행내용


코드

Button.jsx

import React from "react";
import styled from "styled-components";

const StyledButton = styled.button`
    padding: 8px 16px;
    font-size: 16px;
    border_width: 1px;
    obrder-radius: 8px;
    cursor: pointer;
`;

function Button(props){
    const { title, onClick } = props;

    return <StyledButton onClick={onClick}>{title || "button"}</StyledButton>;
}

export default Button;

 

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;
`;

function TextInput(props){
    const { height, value, onChange } = props;

    return <StyledTextarea height = {height} value = {value} onChange = {onChange}></StyledTextarea>;
}

export default TextInput;

 

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;
    }
`;

function PostList(props){
    const { posts, onClickItem } = props;

    return (
        <Wrapper>
            {posts.map((post, index) => {
                return(
                    <PostListItem
                        key = {post.id}
                        post = {post}
                        onClick = {() => {
                            onClickItem(post);
                        }}
                    ></PostListItem>
                );
            })}
        </Wrapper>
    );
}

export default PostList;

 

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;
    aligh-items: flex-start;
    justify-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;
`;

function PostListItem(props){
    const { post, onClick } = props;

    return (
        <Wrapper onClick={onClick}>
            <TitleText>{post.title}</TitleText>
        </Wrapper>
    );
}

export default PostListItem;

 

CommentList.jsx

import React from "react";
import styled from "styled-components";
import CommentListItem from "./CommentListItem";

const Wrapper = styled.div`
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;

    :not(:last-child){
        margin-bottom: 16px;
    }
`;

function CommentList(props){
    const { comments } = props;

    return (
        <Wrapper>
            {comments.map((comment, index) => {
                return (
                    <CommentListItem
                        key = {comment.id}
                        comment = {comment}
                    >
                    </CommentListItem>
                );
            })}
        </Wrapper>
    );
}

export default CommentList;

 

CommentListItem.jsx

import React from "react";
import styled from "styled-components";

const Wrapper = styled.div`
    width: calc(100% - 32px);
    padding: 8px 16px;
    display: flex;
    flex-direction: 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: 16px;
    white-space: pre-wrap;
`;

function CommentListItem(props){
    const { comment } = props;

    return(
        <Wrapper>
            <ContentText>
                {comment.content}
            </ContentText>
        </Wrapper>
    );
}

export default CommentListItem;

 

MainPage.jsx

import React from "react";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
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-items: center;
    justify-content: center;
`;

const Container = styled.div`
    width: 100%;
    max-width: 720px;
    :not(:last-child){
        margin-bottom: 16px;
    }
`;

function MainPage(props){
    const {} = props;

    const navigate = useNavigate();

    return (
        <Wrapper>
            <Container>
                <Button
                    title = "글 작성하기"
                    onClick = {() => {
                        navigate("/post-write");
                    }}
                >
                </Button>

                <PostList
                    posts = {data}
                    onClickItem = {(item) => {
                        navigate(`/post/${item.id}`);
                    }}
                >
                </PostList>
            </Container>
        </Wrapper>
    )
}

export default MainPage;

 

PostViewPage.jsx

import React, { 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-content: 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;
`;

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("/");
                    }}
                >
                </Button>
                
                <PostContainer>
                    <TitleText>
                        {post.title}
                    </TitleText>
                    <ContentText>
                        {post.content}
                    </ContentText>
                </PostContainer>
                
                <CommentLabel>댓글</CommentLabel>
                <CommentList comments = {post.comments}></CommentList>

                <TextInput
                    height = {40}
                    value = {comment}
                    onChange = {(event) => {
                        setComment(event.target.value);
                    }}
                ></TextInput>

                <Button
                    title = "댓글 작성하기"
                    onClick = {() => {
                        navigate("/");
                    }}
                >
                </Button>
            </Container>
        </Wrapper>
    );
}

export default PostViewPage;

 

PostWritePage.jsx

import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import PostList from "../list/PostList";
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-content: center;
`;

const Container = styled.div`
    width: 100%;
    max-width: 720px;
    :not(:last-child) {
        margin-bottom: 16px;
    }
`;

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>

                <TextInput
                    height = {480}
                    value = {content}
                    onChange ={(event) => {
                        setContent(event.target.value);
                    }}
                ></TextInput>

                <Button
                    title = "글 작성하기"
                    onClick = {() => {
                        navigate("/");
                    }}
                >
                </Button>
            </Container>
        </Wrapper>
    );
}

export default PostWritePage;

 

App.js

import React from "react";
import {
  BrowserRouter,
  Routes,
  Route
} from "react-router-dom";
import styled from "styled-components";

//Pages
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(props) {
  return (
    <BrowserRouter>
      <MainTitleText>홍연의 미니 블로그</MainTitleText>
      <Routes>
        <Route index element = {<MainPage></MainPage>}></Route>
        <Route path = "post-write" element = {<PostWritePage></PostWritePage>}></Route>
        <Route path = "post/:postId" element = {<PostViewPage></PostViewPage>}></Route>
      </Routes>
    </BrowserRouter>
  );
}

export default App;

 

실행내용