본문 바로가기

내일배움캠프 TIL

내일 배움 캠프 23_08_25 TIL

이미지를 선택하고 생성하는 기능을 컴포넌트로 만들어서 하고있는데

어찌저찌 여러 이미지들을 생성하고 안에 태그를 찍고 불러와서 보이는것 까지는 성공을 했는데 이게 조금 정석이 아니라

다르게 구현해 놓은것이라 문제가 될 것 같지만 우선 기록을 위해 써놓아본다.

 

import React, { useState } from 'react';
import ImageTag from './ImageTag';
import { Tag, Data } from 'src/types/types';
import PostWriteInput from '../post/PostWriteInput';

interface AddImageTagComponentProps {
  onTagsAndResultsChange: (tags: Tag[], searchResults: Data[]) => void;
  onImageSelect: (image: File) => void;
  body: string;
  onBodyChange: (newBody: string) => void;
}

const AddImageTagComponent: React.FC<AddImageTagComponentProps> = ({
  onTagsAndResultsChange,
  onImageSelect,
  body,
  onBodyChange
}) => {
  const [imageTagComponents, setImageTagComponents] = useState<JSX.Element[]>([]);

  const addImageTagComponent = () => {
    const newImageTagComponent = (
      <div key={Date.now()}>
        <ImageTag onTagsAndResultsChange={onTagsAndResultsChange} onImageSelect={onImageSelect} />
        <PostWriteInput
          type="text"
          name="body"
          title="body"
          value={body}
          onChange={(e) => {
            onBodyChange(e.target.value);
          }}
        />
      </div>
    );

    setImageTagComponents((prevComponents) => [...prevComponents, newImageTagComponent]);
  };

  return (
    <div>
      <button onClick={addImageTagComponent}>이미지 추가</button>
      {imageTagComponents.map((component, index) => (
        <div key={index}>{component}</div>
      ))}
    </div>
  );
};

export default AddImageTagComponent;

이 코드에서 이미지 추가 버튼을 하나 생성하고 저 버튼을 누를때마다 이미지와 태그를 찍는 ImageTag 컴포넌트를 

생성해주는 코드를 짜 놓았다 이는 근데 하나의 State를 공유하고 있기 때문에

 

import React from 'react';
import { useState, useRef } from 'react';
import { useNavigate } from 'react-router';

import { Tag } from 'src/types/types';
import AddImageTagComponent from '../ImageTag/AddImageTagComponent';
import supabase from 'src/lib/supabaseClient';
import usePost from 'src/hooks/usePost';
import PostWriteInput from './PostWriteInput';

// recipe, common write component 정리 필요
const PostWriteRecipe = () => {
  // user id 윤수님
  const userId = 'be029d54-dc65-4332-84dc-10213d299c53';

  const navigate = useNavigate();
  const { addPostMutate } = usePost();

  const [title, setTitle] = useState<string>('');
  const [body, setBody] = useState<string>('');
  const [, setSelectedImage] = useState<File | null>(null);

  const [allSelectedImages, setAllSelectedImages] = useState<File[]>([]);
  const [allSelectedTags, setAllSelectedTags] = useState<Set<Tag>>(new Set());

  const handleTagSelect = (selectedTags: Tag[]) => {
    setAllSelectedTags((prevSelectedTags) => {
      const newTags = new Set([...prevSelectedTags, ...selectedTags]);
      return newTags;
    });
  };

  const handleImageSelect = (image: File) => {
    setSelectedImage(image);
    setAllSelectedImages((prevImages) => [...prevImages, image]);
  };

  const postRef = useRef<HTMLInputElement>(null);

  const submitPost = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const imageUrls = [];

    for (const selectedImage of allSelectedImages) {
      const { data, error } = await supabase.storage.from('photos').upload(`tags/${selectedImage.name}`, selectedImage);

      if (error) {
        console.error('Error uploading image to Supabase storage:', error);
        alert('이미지 업로드 중 에러가 발생했습니다!');
        return;
      }

      imageUrls.push(data.path);
    }

    const newPost = {
      postCategory: 'recipe',
      userId,
      title,
      body: body,
      tags: Array.from(allSelectedTags),
      tagimage: imageUrls
    };

    addPostMutate.mutate(newPost);
    navigate(`/`);
  };

  return (
    <>
      <AddImageTagComponent
        onTagsAndResultsChange={handleTagSelect}
        onImageSelect={handleImageSelect}
        body={body}
        onBodyChange={setBody}
      />

      <form onSubmit={submitPost}>
        <PostWriteInput
          ref={postRef}
          type="text"
          name="title"
          title="title"
          value={title}
          onChange={(e) => {
            setTitle(e.target.value);
          }}
          autoFocus
        />

        <button type="submit">add</button>
      </form>
    </>
  );
};

export default PostWriteRecipe;

위 코드에서 파일이 선택되고 태그들을 찍었을 때 데이터들을 계속해서 추가해주면서 관리를 했고 중복을 제거하기 위해

set으로 중복을 제거해주었다. 그리하여 모든 데이터가 db에 올라가기는 했으나 또 다른 문제

각 이미지별로 태그를 적중시키는게 또 문제가 되었다 그래서 조금 편법이긴하지만 tags에 image 파일명을 같이 저장해서

import React, { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useNavigate, useParams } from 'react-router-dom';
import { ImageTag, PostBookmark, PostLike } from 'src/types/types';
// custom hoooks
import useLoginUserId from 'src/hooks/useLoginUserId';
import useMutate from 'src/hooks/usePost';
import usePostLikes from 'src/hooks/usePostLikes';
import usePostBookmark from 'src/hooks/usePostBookmark';
// api
import { getPost } from 'src/api/posts';
import { getPostLike } from 'src/api/postLikes';
import { Tag } from 'src/types/types';

const PostDetail = () => {
  // user id
  const userId: string | undefined = useLoginUserId();

  const { id } = useParams<string>();
  const navigate = useNavigate();

  // 게시글 삭제, 좋아요, 좋아요 취소
  const { deletePostMutate } = useMutate();
  const { addPostLikeMutate, deletePostLikeMutate } = usePostLikes();
  const { addPostBookmarkMutate, deletePostBookmarkMutate } = usePostBookmark();
  const [selectedTag, setSelectedTag] = useState<Tag | null>(null);

  // read data
  const { isLoading, data } = useQuery({ queryKey: ['posts'], queryFn: () => getPost(id!) });
  const { data: postLikeData } = useQuery({ queryKey: ['post_likes'], queryFn: () => getPostLike(id!) });
  const { data: postBookmarkData } = useQuery({ queryKey: ['post_bookmark'], queryFn: () => getPostLike(id!) });
  const post = data?.data?.[0];
  const postLike = postLikeData?.data?.find((like) => like.userId === userId);
  const postBookmark = postBookmarkData?.data?.find((bookmark) => bookmark.userId === userId);

  // delete post
  const clickDelete = (id: string) => {
    deletePostMutate.mutate(id);
    navigate('/');
  };

  const clickEdit = () => {
    navigate(`/edit/${id}`);
  };

  // 좋아요 기능
  const clickPostLike = (postLike: PostLike) => {
    if (!postLike) {
      const newPostLike = {
        postId: post.id,
        userId: userId as unknown as string
      };
      addPostLikeMutate.mutate(newPostLike);
    } else {
      deletePostLikeMutate.mutate(postLike.id);
    }
  };

  const clickPostBookmark = (postBookmark: PostBookmark) => {
    if (!postBookmark) {
      const newPostBookmark = {
        postId: post.id,
        userId
      };
      addPostBookmarkMutate.mutate(newPostBookmark);
    } else {
      deletePostBookmarkMutate.mutate(postBookmark.id);
    }
  };

  if (isLoading) {
    return <p>Loading…</p>;
  }
  if (data?.error) {
    return <p>Error</p>;
  }
  if (data?.data.length === 0) {
    return <p>none</p>;
  }
  const handleTagClick = (tag: Tag) => {
    setSelectedTag(selectedTag === tag ? null : tag);
  };

  console.log('post', post);
  console.log('post', post.imageUrl);

  return (
    <div>
      <div>{post.title}</div>
      {/* component 분리 예정 */}
      {post.postCategory === 'common' ? (
        <pre dangerouslySetInnerHTML={{ __html: post.body }} />
      ) : (
        <div>{post.body}</div>
      )}
      {/* 우선확인을 위해 추가해두었습니다 나중에 컴포넌트로 분리해놓을게요! */}
      <div>
        {post.tagimage && (
          <div style={{ position: 'relative' }}>
            {post.tagimage.map((imageUrl: string, imageIndex: number) => {
              const tagsForImage = post.tags.filter((tag: ImageTag) => tag.selectedimg);

              return (
                <div key={imageIndex} style={{ position: 'relative' }}>
                  <img src={`${process.env.REACT_APP_SUPABASE_STORAGE_URL}${imageUrl}`} alt={imageUrl} />
                  {tagsForImage.map((tag: ImageTag, tagIndex: number) => (
                    <div
                      key={tagIndex}
                      style={{
                        position: 'absolute',
                        left: tag.x + 'px',
                        top: tag.y + 'px',
                        backgroundColor: 'red',
                        width: '30px',
                        height: '30px',
                        display: `tags/${tag.selectedimg}` === imageUrl ? 'block' : 'none'
                      }}
                      onClick={() => handleTagClick(tag)}
                    >
                      {/* 선택된 태그가 있다면 해당 태그를 열고 안에있는 데이터를 보여줌 */}
                      {selectedTag === tag && (
                        <div
                          className="details"
                          style={{
                            backgroundColor: 'skyblue',
                            width: '300px',
                            padding: '10px',
                            position: 'absolute',
                            zIndex: 1
                          }}
                        >
                          {tag.prodData}
                          <br />
                          {tag.price}
                          <img src={tag.img} alt="상품 이미지" />
                        </div>
                      )}
                    </div>
                  ))}
                </div>
              );
            })}
          </div>
        )}
      </div>
      <button onClick={() => clickDelete(post.id)}>delete</button>
      <button onClick={clickEdit}>edit</button>
      <button onClick={() => clickPostLike(postLike)}>{postLike ? '좋아요 취소' : '좋아요'}</button>
      <button onClick={() => clickPostBookmark(postBookmark)}>{postBookmark ? '북마크 취소' : '북마크'}</button>
      <button>인용하기</button>
    </div>
  );
};

export default PostDetail;

이미지의 파일명과 tags안에 들어있는 파일명이 같으면 보이는 조건부 처리를 해줘서 기능을 구현되게만 해놓았다

이는 분명히 비효율적이기에 다른 적중시킬 수 있는 코드를 짜야겠지만 우선은 떠오르지 않기에 이렇게 구현만 해두었다

하지만 이거 수정할때는 어찌할지.... 뭔가 단추가 처음부터 잘못꿰어진 느낌이다...

 

아래는 오늘 CS 공부한것을 적어놓는다

 

2. 쓰레드 풀

쓰레드 풀이란?

-컴퓨터 프로그래밍에서 쓰레드 풀은 컴퓨터 프로그램에서 실행의 동시성을 달성하기 위한 소프트웨어 디자인 패턴이다.
-프로그램이 작업을 동시에 실행할 수 있도록 여러 스레드를 미리 생성해두고 유지 관리한다.
-여러 Thread를 동시에 만들어 실행(병렬처리)할 수 있다.

하지만 쓰레드가 계속 늘어나는것은 좋은것이 아니라서
쓰레드 풀이라는 개념을 이용한다

쓰레드 풀은 작업 처리에 사용되는 쓰레드를 제한된 개수만큼 정해 놓고 작업 큐에 들어오는 작업들을 하나씩 쓰레드가 맡아 처리하는 것을 말한다.
-치킨집에서 직원 뽑기가 어려우니 직원들을 미리 뽑아두는 것과 비슷하다.
-해야할 일을 순서대로 (작업 큐) 직원들에게 할당 해주며 처리하도록 한다.

쓰레드 풀의 동작
1.초기화 : 쓰레드 풀을 사용하기 전에 초기화해야 하는데 이 단계에서는 쓰레드 풀의 크기, 최대 쓰레드 수, 작업 큐 등의 매개변수를 설정한다.
2.작업 수신 : 쓰레드 풀은 작업을 수신하고 처리할 준비를 한다. 작업은 일반적으로 작업 큐에 추가가 된다.
3.작업 수행 : 쓰레드 풀에서는 미리 생성된 쓰레드들이 작업 큐를 모니터링하고 대기 중인 작업을 가져와 처리한다.
이때 쓰레드 풀 내의 쓰레드들은 일반적으로 무한 루프를 돌면서 작업을 가져오기 위해 대기한다.
4.작업 처리 : 쓰레드 풀의 스레드가 작업을 가져와서 처리한다. 작업은 일반적으로 작업 큐에서 FIFO 방식으로 가져오게 된다.
5.작업 완료 및 반환 : 작업이 완료되면 해당 결과를 반환하고, 쓰레드는 다시 작업 큐에서 새로운 작업을 가져오기 위해 대기 상태로 돌아간다
6.작업 대기 : 작업 큐에 새로운 작업이 추가되면 쓰레드 풀의 스레드들은 대기 상태를 벗어나 작업을 가져와 처리한다. 이를 반복하여 계속적으로
작업을 수행한다.
7.종료 :쓰레드 풀은 더 이상 사용하지 않을 때 종료한다. 종료할 때는 모든 작업이 완료되었는지 확인하고, 필요에 따라 남은
작업들을 처리하거나 버릴 수 있다.

2-2.쓰레드 풀을 사용하는 이유
1.프로그램 성능 저하를 방지하기 위해
-매번 발생하는 작업을 병렬처리하기 위해 쓰레드를 생성/수거하는 데 따른 부담은 프로그램 전체적인 퍼포먼스를 저하시킨다. 
따라서 쓰레드풀을 만들어 놓고 사용한다.
-쓰레드 또한 프로세스가 할당한 메모리를 사용한다.
-즉, Java의 경우 쓰레드를 생성하면 JVM 메모리를 소비하게 되는 것이다. 쓰레드 자체도 레지스터와 스택을 가지고,
쓰레드도 컨텍스트 스위칭이 일어나기 때문에 쓰레드 생성에 따른 메모리 할당을 무시할 수 없다.

2.다수의 사용자 요청을 처리하기 위해
  -대규모 프로젝트에서 특히 중요합니다.
  -다수의 사용자의 요청을 수용하고, 빠르게 처리하고 대응하기 위해 쓰레드풀을 사용한다.
  -특히 Bottle Neck 현상이 발생하는 I/O 작업과 데이터베이스 작업에서 주로 사용된다.
  -쓰레드가 아무리 빠르게 생성되더라도 시스템 스케줄러에서 쓰레드의 우선 순위를 매번 할당해야 하는데,
   쓰레드풀을 이용하면 일정 쓰레드가 이미 생성되기 때문에 쓰레드풀에 의해 라이프 사이클이 관리되고, 쓰레드 풀에 의해
   작업이 큐를 이용하게 되어 우선순위가 배분되고 처리된다.

2-3 쓰레드 풀의 장단점
쓰레드 풀의 장점
-쓰레드를 생성/수거하는데 비용이 들지 않는다.
-쓰레드가 생성될 때 OS가 메모리 공간을 확보해주고 메모리를 쓰레드에게 할당해준다.
-쓰레드 풀을 미리 만들어 두기 때문에 처음에 생성하는 비용은 들지만 이전의 쓰레드를 재사용할 수 있으므로
시스템 자원을 줄일 수 있고, 작업을 요청 시 이미 쓰레드가 대기 중인 상태이기 때문에 작업을 실행하는 데 딜레이가 발생하지 않는다.

쓰레드 풀의 단점
-thread pool에 thread를 너무 많이 생성해 두었다가 사용하지 않으면 메모리 낭비가 발생한다.
-Thread pool의 단점 개선 : Fork Join Thread Pool

2-4 동시성과 병렬성

동시성 :
싱글 코어에서 멀티 스레드를 동작시키기 위한 방식으로 멀티 태스킹을 위해 여러 개의 스레드가 번갈아가면서 실행되는 성질을 말한다.
동시성을 이용한 싱글 코어의 멀티 태스킹은 각 스레드들이 병렬적으로 실행되는 것처럼 보이지만 사실은 번갈아가면서 조금씩 실행되고 있는 것이다.

병렬성 : 멀티 코어에서 멀티 스레드를 동작시키는 방식으로, 한 개 이상의 스레드를 포함하는 각 코어들이 동시에 실행되는 성질을 말한다.
병렬성은 데이터 병렬성과 작업 병렬성으로 구분된다.
   [데이터 병렬성]
   -데이터 병렬성은 전체 데이터를 쪼개 서브 데이터들로 만든 뒤, 서브 데이터들을 병렬 처리하여 작업을 빠르게 수행하는 것을 말한다.
   -자바8에서 지원하는 병렬 스트림이 데이터 병렬성을 구현한 것이다.
   -서브 데이터는 멀티 코어의 수만큼 쪼개어 각각의 데이터들을 분리된 스레드에서 병렬 처리한다.
    [작업 병렬성]
     -작업 병렬성은 서로 다른 작업을 병렬 처리하는 것을 말한다.
     -대표적인 예는 웹 서버로, 각각의 브라우저에서 요청한 내용을 개별 스레드에서 병렬로 처리한다.

쓰레드 풀 요약
-작업 처리에 사용되는 쓰레드를 제한된 개수만큼 정해 놓고,
작업 큐에 들어오는 작업들을 하나씩 쓰레드가 맡아 처리하는 기법

     

'내일배움캠프 TIL' 카테고리의 다른 글

내일 배움 캠프 23_08_29 TIL  (0) 2023.08.29
내일 배움 캠프 23_08_28 TIL  (0) 2023.08.28
내일 배움 캠프 23_08_24 TIL  (0) 2023.08.24
내일 배움 캠프 23_08_23 TIL  (1) 2023.08.23
내일 배움 캠프 23_08_22 TIL  (0) 2023.08.22