Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

걸음마부터 달리기

[24/11/07] 리액트 중간정리 본문

카테고리 없음

[24/11/07] 리액트 중간정리

성추 2024. 11. 7. 15:28

현재까지의 프로젝트 진행 흐름을 좀 봐볼려고 한다.

 

 

크게 보면 이정도 Component가 있는거같다. 빨간색 원 같은 경우는 저것도 컴포넌트로 묶어야될거 같긴 한데 지금까지는 일단 냅뒀다. 

 

inputBox 컴포넌트 부터 봐보자.

import React, { ChangeEvent, Dispatch, KeyboardEvent, SetStateAction, forwardRef } from "react";
import './style.css'

// interface : InputBox 컴포넌트의 Properties
interface Props {
    label:string;
    type: 'text' | 'password'; //input type
    placeholder:string;
    value: string;
    setValue: React.Dispatch<React.SetStateAction<string>>; //?
    error:boolean;

    icon? : string;
    onButtonClick? : () => void;
    message?: string;
    onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
}

//InputBox는 전체 레이아웃 컴포넌트 안에서 자식 컴포넌트로 동작하며 이때 InputBox컴포넌트를 감싸는 부모 컴포넌트가 InputBox 컴포넌트 안에 있는 특정 DOM Element에
//접근하기 위해서 InputBox에 RorwardRef를 사용함
//input에 Enter를 쳤을때 다음 input으로 넘어가게
const InputBox= forwardRef<HTMLInputElement, Props>((props: Props, ref)=>{
    //state: properties
    const {label , type,placeholder,value,error,icon,message } = props;
    const {setValue,onButtonClick,onKeyDown} = props;


    //input 값 변경 이벤트 처리 함수
    const onChangeHanlder = (event:ChangeEvent<HTMLInputElement>) => {
        const {value} = event.target;
        setValue(value);
    }

    //keyEvent 핸들러
    const onKeyDownHandler = (event: KeyboardEvent<HTMLInputElement>) => {
        if(!onKeyDown) return;
        onKeyDown(event);
    }

    return (
        <div className="inputbox">
            <div className="inputbox-label">{label}</div>
            <div className={error ? "inputbox-container-error" : "inputbox-container"}> {/*error일때 빨간색으로 , 아닐경우 기존 디자인*/}
                <input ref={ref} type={type} className="input" placeholder={placeholder} value={value} onChange={onChangeHanlder} onKeyDown={onKeyDownHandler}/>
                {onButtonClick !== undefined && 
                    <div className="icon-button">
                    {icon !==undefined && <div className={`icon ${icon}`}></div>}
                    </div>
                } {/* 왼쪽 평가 이후 &&의 오른쪽 평가 > 코드 실행 */}

            </div>
            {message!==undefined && <div className="inputbox-message">{message}</div>}
        </div>
    )
});

export default InputBox;

props의 요소들 보면 해당 컴포넌트에서 바뀌어야 하는 속성들이 나와있다. 

잘 보면 잘못 입력했을때 빨간색으로 띄워야하는 요소들이 있다. 이는 error인지 아닌지 판단하여 message를 맞게 띄워야한다. 또한 각 inputBox에 따라 icon이 있을때(비밀번호) , 아이콘 눌렸을때 (onButtonClick) , placeholder, label , type(패스워드 형식인지 그냥 문자열 형식인지) 등등이 바뀌기때문에 이들을 Props로 정의해놓고 태그들에서 이 값들을 넣어준다. 

 

export default interface BoardListItem{
    boardNumber:number;
    title:string;
    content:string;
    boardTitleImage:string | null;
    favoriteCount:number;
    commentCount:number;
    viewCount:number;
    writeDatetime:string;
    writerNickname:string;
    writerProfileImage:string | null;
}
------------------------------------------------------------------------------
import React from 'react';
import './style.css';
import defaultProfileImage from 'assets/image/default-profile-image.png';
import { BoardListItem } from 'types/interface';
import { useNavigate } from 'react-router-dom';

interface Props{
    top3ListItem: BoardListItem
}

// component: Top3 List item 컴포넌트
export default function Top3Item({top3ListItem}:Props){

    //properties
    const {boardNumber , title , content, boardTitleImage}=top3ListItem;
    const {favoriteCount,commentCount, viewCount} = top3ListItem;
    const{writeDatetime,writerNickname,writerProfileImage} = top3ListItem

    //function: 네비게이트 함수//
    //const navigator = useNavigate();

    //event handler : 게시물 아이템 클릭 이벤트 처리 함수
    const onClickHandler= () => {
        //navigator(boardNumber);
    }

    //render
    return (
        <div className='top-3-list-item' style={{backgroundImage:`url(${boardTitleImage})`}} onClick={onClickHandler}>
            <div className='top-3-list-item-main-box'>
                <div className='top-3-list-item-top'>
                    <div className='top-3-list-item-profile-box'>
                        <div className='top-3-list-item-profile-image' style={{backgroundImage: `url(${writerProfileImage ? writerProfileImage : defaultProfileImage})`}}></div>
                    </div>
                    <div className='top-3-list-item-write-box'>
                        <div className='top-3-list-item-nickname'>{writerNickname}</div>
                        <div className='top-3-list-item-write-date'>{writeDatetime}</div>
                    </div>
                </div>
                <div className='top-3-list-item-middle'>
                    <div className='top-3-list-item-title'>{title}</div>
                    <div className='top-3-list-item-content'>{content}</div>
                </div>
                <div className='top-3-list-item-bottom'>
                    <div className='top-3-list-item-counts'>
                        {`댓글 ${commentCount} 좋아요 ${favoriteCount} 조회수 ${viewCount}`}
                    </div>
                </div>
            </div>
        </div>
    )
}

 이 코드도 마찬가지로 컴포넌트들이 각각 와야할 요소들, 즉 컴포넌트들마다 달라져야 할 상태값들을 Props 타입으로 정의하여 주고 있다. Props 타입 정의할때 하나씩 전부 하는것보다 이 타입 자체를 따로 빼서 할당하고 있다.

 

 

interface Props{
    boardNumber:number;
    title:string;
    content:string;
    boardTitleImage:string | null;
    favoriteCount:number;
    commentCount:number;
    viewCount:number;
    writeDatetime:string;
    writerNickname:string;
    writerProfileImage:string | null;
}

즉 위의 코드처럼 다 때려박아도 상관은 없지만 편의성을 위해 타입을 따로 지정하여 해당 객체를 받는 형식으로 갔다.

 

export default interface FavoriteListItem {
    email: string;
    nickname: string;
    profileImage: string | null;
}

------------------------------------------------------------------------------

import React from "react";
import './style.css'
import { FavoriteListItem } from "types/interface";
import defaultProfileImage from "assets/image/default-profile-image.png"

interface Props{
    favoriteListItem: FavoriteListItem;
}

export default function FavoriteItem({favoriteListItem} : Props){
    const {nickname,profileImage} = favoriteListItem
    return (
        <div className='favorite-list-item'>
            <div className="favorite-list-item-profile-box">
                <div className="favorite-list-item-profile-image" style={{backgroundImage: `url(${profileImage? profileImage : defaultProfileImage})`}}></div>
            </div>
            <div className="favorite-list-item-nickname">{nickname}</div>
        </div>



    )
}

마찬가지로 favoriteComponent도 Props를 위해 따로 타입 정의하여 빼놔야 간단하다.

 

import React from "react";
import './style.css';
import { CommentListItem } from "types/interface";
import defaultProfileImage from "assets/image/default-profile-image.png";

export default interface CommentListItem {
    nickname: string;
    profileImage: string | null;
    writeDatetime: string;
    content: string;
}
------------------------------------------------------------------------
interface Props{
    commentListItem: CommentListItem;
}

// component: Comment List Item 컴포넌트
export default function CommentItem({commentListItem }: Props){
    const{nickname,profileImage,writeDatetime,content} = commentListItem

    return (
        //render : Comment List Item 렌더링
        <div className="comment-list-item">
            <div className="comment-list-item-top">
                <div className="comment-list-item-profile-box">
                    <div className="comment-list-item-profile-image" style={{backgroundImage: `url(${profileImage? profileImage: defaultProfileImage})`}}></div>
                </div>
                <div className="comment-list-item-nickname">{nickname}</div>
                <div className="comment-list-item-divider">{`\|`}</div>
                <div className="comment-list-item-time">{writeDatetime}</div>
            </div>
            <div className="comment-list-item-main">
                <div className="comment-list-item-content">
                    {content}
                </div>
            </div>
        </div>
    )
}

commentItem 컴포넌트도 마찬가지로 컴포넌트들마다 시간, 글쓴이 닉네임, 프로필 이미지 , 컨텐츠를 매 컴포넌트들마다 바꿔끼워야하니까 Props로 정의해놓고 이들의 요소들을 Interface로 빼놨다.

 

export default interface BoardListItem{
    boardNumber:number;
    title:string;
    content:string;
    boardTitleImage:string | null;
    favoriteCount:number;
    commentCount:number;
    viewCount:number;
    writeDatetime:string;
    writerNickname:string;
    writerProfileImage:string | null;
}
----------------------------------------------------------------------------
import React from 'react'
import './style.css';
import { BoardListItem } from 'types/interface';
import { useNavigate } from 'react-router-dom';
import defaultProfileImage from 'assets/image/default-profile-image.png';

interface Props{
    boardListItem: BoardListItem
}

// Component : Board List Item 컴포넌트
export default function BoardItem({boardListItem}: Props) {

    //properties //
    const {boardNumber, title , content , boardTitleImage} = boardListItem;
    const {favoriteCount, commentCount, viewCount}=boardListItem;
    const{writeDatetime,writerNickname,writerProfileImage} = boardListItem

    //function: 네비게이트 함수
    //const navigator = useNavigate();

    // event handler : 게시물 아이템 클릭 이벤트 처리함수 //

    const onClickHandler=()=>{
        //navigator(boardNumber);
    }


    //render : Board List Item 컴포넌트 렌더링
  return (
    <div className='board-list-item' onClick={onClickHandler}>
        <div className='board-list-item-main-box'>
            <div className='board-list-item-top'>
                <div className='board-list-item-profile-box'>
                    <div className='board-list-item-profile-image' style={{backgroundImage: `url(${writerProfileImage ? writerProfileImage : defaultProfileImage})`}}></div>
                </div>
                <div className='board-list-item-write-box'>
                    <div className='board-list-item-nickname'>{writerNickname}</div>
                    <div className='board-list-item-write-datetime'>{writeDatetime}</div>
                </div>
            </div>
            <div className='board-list-item-middle'>
                <div className='board-list-item-title'>{title}</div>
                <div className='board-list-item-content'>
                    {content}
                </div>
            </div>
            <div className='board-list-item-bottom'>
                <div className='board-list-item-counts'>
                    {`댓글 ${commentCount} 좋아요 ${favoriteCount} 조회수 ${viewCount}`}
                </div>
            </div>
        </div>
        {boardTitleImage !== null && (
        <div className='board-list-item-image-box'>
            <div className='board-list-item-image' style={{backgroundImage: `url(${boardTitleImage})`}}></div>
        </div>            
        )}
    </div>
  )
}

boardItem 컴포넌트도 매번 제목, 컨텐츠 , 보드 이미지, 등등 바뀔만한 것들은 모두 Props를 통해 컴포넌트들을 다르게 표시하고 이를 위해 interface로 따로 뺴놓은거다. 

 

그런데 email이나 boardNumber 같은 직접 화면에 보이지 않는 Props에 들어오는 것들은 어디다 쓰이는지 추후 진행하면서 추가하겠다.

 

이제 저런 레이아웃을 <Outlet/> 를 이용하여 중첩 라우팅을 통해 <Outlet/> 의 컴포넌트들을 바꿔가면서 URL에 따라 렌더링한다.