[react 30] #3 갤러리 구현하기

dev__bokyoung·2022년 8월 3일
0
post-thumbnail

프롤로그

cra 와 typescript을 사용하여 갤러리를 구현하는 작업을 진행하였다.
패키지 매니저로는 yarn 을 사용했고,
두가지 방식으로 갤러리에 파일을 추가하는 방식을 정리해 보고자 한다.

CRA 개발환경 세팅

🗣 next.js 를 설치 한 상태에서 진행하였습니다.

1. yarn 설치

npm install -g yarn

yarn 을 설치 하고 yarn start 를 했을 때 오류
▶︎ couldn't find a package.json

해당 파일 내에 package.json 파일이 존재하지 않는다!
이럴때에는 yarn init 으로 package.json 파일을 생성해주면 된다.

몇가지 질문이 나타나는데 그에 대한 대답을 써주면 된다.

{
  "name": "project30", //프로젝트 이름 
  "version": "1.0.0", // 프로젝트 버전 
  "description": "practice react", // 설명
  "main": "index.js", // 첫 시작점 위치
  "repository": "https://github.com/bok-world/project30.git", // 저장소 위치 
  "author": "bk <rnjsqhrud10@gmail.com>", // 작업자 
  "license": "MIT", //라인센스 
  "private": true // 개인 & 공적 
}

2. 폴더 구조

cra 를 설치하면 기본으로 설치되는 것들 중 중요한 것들만 말하고자 한다.
처음 리액트를 배울때 저런 폴더구조에 대해서 궁금한게 많았는데 직접 개발환경을 설정하고 설명을 듣다보니 점차 해소가 되고 있는 중이다.

- node_moudles //node.js 에서 install 된 개발하는데 필요한 소스나 라이브러리
- public //개발된 리액트 코드가 들어가서 보여지게 되는 폴더
	⌞ index.html
- src // 여기서 주로 코드를 다루게 된다.
	⌞ components // 컴포넌트에 대한 폴더 (생성) 
    ⌞ App.css
	⌞ App.test.tsx
	⌞ index.css
	⌞ index.tsx
	⌞ logo.svg
	⌞ ...
- .gitignore // git에 올릴 때 제외하는 코드들 
- package.json // 어떤 라이브러리들이 install 되어있는지 보여주고 스크립트를 실행 할 수 있게 해주는 부분 
- README.md
- tsconfig.json //타입스크립트의 설정파일들 
- yarn-error.log
- yarn.lock //세부적인 라이브러리의 설정들 

구조와 Components

  1. 초기화면은 "이미지가 없습니다. 이미지를 추가해주세요" 라는 글귀와 + 박스

  1. 박스를 눌러 파일을 첨부하게 되면 이미지가 하나씩 추가되면서 화면상에 보여진다. 이미지를 추가 해 달라는 글귀는 없어지게 되고, 이미지를 추가 할 때 마다 한줄로 정렬되며 나타난다.

따라 할 때는 무심코 하니 쉬워보였지만
다시 거꾸로 돌아가 어떤식으로 짰구나 라고 돌아보니
처음에 어떻게 짰을지 막막 했을 것 같다.

큰 틀은 이러하다.

  • 초기화면을 만든다.
  • 박스를 클릭하면 파일 첨부를 한다. 현재 타겟 데이터를 받는다.
  • list 배열에 추가 시킨다.
  • 받은 파일 정보를 dataURL 로 변경시켜
  • 각각의 파일들을 이미지로 보여지게 만든다.

컴포넌트를 이용해 구조를 작성했다.

리액트의 장점은 컴포넌트를 사용할수 있다는 점이다.
컴포넌트의 좋은점은 "재사용 가능하다"

0. Component에 대해

컴포넌트는 이미 내장되어 있는 태그를 조합하고, 거기에 스타일, 동작까지 입혀서 (문서,스타일,동작,표현)을 한꺼번에 만들고 재활용하는 방식 이라고 정의 할 수 있다.

<내가지은이름 name ="Mark" />
<내가지은이름 props={false}>내용</내가지은이름>

// src,class,name,props 밖에서 넣어주는 데이터
// 문서(HTML), 스타일(css), 동작(JS) 를 합쳐서 내가 만든 일종의 태그

1. 초기화면 작성하기

html 구조 짜는 것과 비슷하면서도 비슷하지 않았다.
컴포넌트 활용한다는 것이 가장 큰 차이!
우선 밑에 코드를 보자

import React, { useRef, useState, useCallback } from "react";
import "./App.css";
import ImageBox from "./components/ImageBox";

function App() {
  const inpRef = useRef<HTMLInputElement>(null);
  const [imageList, setImageList] = useState<string[]>([]);


  return (
    <div className="container">
      <div className={"gallery-box " + (imageList.length > 0 && "row")}>
        {imageList.length === 0 && (
          <div className="text-center">
            이미지가 없습니다. <br /> 이미지를 추가해주세요.
          </div>
        )}

        <div
          className="plus-box"
        >
          +
          <input
             type="file"
             ref={inpRef}
          />
        </div>
      </div>
    </div>
  );
}

export default App;

❗️ 알고가면 좋을 내용들 ❗️

파일 첨부를 id 를 사용해서 label 을 만드는 방법은 react 스럽지 못하다. 리액트에서는 useRef 를 사용한다.

리액트는 가상 dom 이기 때문에 id 값을 사실상 잡을 수 없다.
훅을 이용해서 useRef 를 만들어주고 input 태그에는 ref={inpRef} 라는 변수(?) 를 지정해 준다.

2. 공통구조는 컴포넌트로

이미지 박스는 사실 같은 구조이다. 그 안에 들어가는 src 만 다를뿐이기 때문에 구조를 공통화 시킨다.

function ImageBox(props: { src: string }) {
  return (
    <div className="img-box">
      <img src={props.src} />
    </div>
  );
}

export default ImageBox;

❗️ 알고가면 좋을 내용들 ❗️

  • 변경되는 부분은 props 로 받을 수 있다.
  • props는 object 들만 올 수 있다.
  • 이 프로젝트는 ts 기반이기 때문에 타입을 지정해 주었다.
  • 리액트는 js 기반이다. js 에서 class는 예약어이므로 className 로 클래스명을 지정해 준다.
  • 다른 부분에서도 사용할 것이기 때문에 export 를 시켜준다.

3. 파일 데이터 상태 변경 & 저장

  1. 클릭했을 때 파일 정보를 받아 올것이고 ▸ click 이벤트
  2. 파일에 대한 정보는 useState로 변경해준다. ▸ 하나가 아니므로 배열로 저장해 준다.
  3. 배열로 저장한 값들 각각을 map 함수를 이용해 컴포넌트로 바꿔준다. ▸ 각각의 배열들은 key 값을 설정 해 준다.
import React, { useRef, useState } from "react";
import "./App.css";
import ImageBox from "./components/ImageBox";

function App() {
  const inpRef = useRef<HTMLInputElement>(null);
  const [imageList, setImageList] = useState<string[]>([]);


  return (
    <div className="container">
      <div className={"gallery-box " + (imageList.length > 0 && "row")}>
        {imageList.length === 0 && (
          <div className="text-center">
            이미지가 없습니다. <br /> 이미지를 추가해주세요.
          </div>
        )}

        {imageList.map((el, idx) => (
          <ImageBox key={el + idx} src={el} />
        ))}
        <div
          className="plus-box"
           onClick={() => {
             inpRef.current?.click();
           }}
        >
          +
          <input
             type="file"
             ref={inpRef}
           
             onChange={(event) => {
               if (event.currentTarget.files?.[0]) {
                 console.log(event.currentTarget.files[0]);
                 const file = event.currentTarget.files[0];

                 const reader = new FileReader();
                 reader.readAsDataURL(file);
                 reader.onloadend = (event) => {
                   setImageList((prev) => [
                     ...prev,
                     event.target?.result as string,
                   ]);
                 };
               }
             }}
          />
        </div>
      </div>
    </div>
  );
}

export default App;

❗️ 알고가면 좋을 내용들 ❗️

  1. true && true ▸ true
    true && 1 ▸ 1
    이란 특성을 이용해서
    삼항연산자를 && 로써 단축 할 수 있다.
  2. event.currentTarget.files?.[0] 부분도 마찬가지이다.

에필로그

각각의 파일을 배열에 담고, url 로 변경시켜 화면에 나타나게 할 수 있었다. 복잡한 코드를 함축시켜 놓은 느낌이라 정말 간편하다고 느꼈고,
별도의 화면의 전환 없이 한 화면에서 해결 되는 점이 정말 깔끔하다고 느꼈다.

추가적으로클릭 이벤트 말고도 드래그 드롭으로 react-dropzone 이라는 라이브러리를 설치 해 사용자의 경험을 훨씬 더 개선시켰다.

라이브러리를 사용하니 앞서 사용했던 hook 들이 소용없어졌고 훨씬 더 간편해졌다는 점이다 (허무) 그래도 저런 라이브러리는 사용하고 적용시키며 원리를 파악해보는 것도 중요하다고 생각한다.

profile
개발하며 얻은 인사이트들을 공유합니다.

0개의 댓글