프론트엔드 개발자의 기록 공간

[React] React(5) 본문

프로그래머스 데브코스_FE/TIL

[React] React(5)

[리우] 2022. 1. 24. 17:05

❗❗  데브코스 53일차 (10.13)

오늘은 Base 컴포넌트 중에서 많이 사용되는 것을 위주로 컴포넌트 만들기 연습으로 진행된다.
여러 컴포넌트를 만들어보는 실습을 통해 컴포넌트를 구성하고 사용하는 스킬을 기를 수 있다.
여러 개의 컴포넌트 중 예시로 1~2개만 예시로 설명하겠다.

✅ Upload - 파일 업로드 컴포넌트

파일을 업로드해야 하는 경우 파일 업로드 기능도 컴포넌트로 구현할 수 있다.
파일 업로드를 선택해서 파일을 추가할 수도 있고, 파일 드래그를 통해 파일을 넣어줄 수 있다.

import styled from "@emotion/styled";
import { useRef, useState } from "react";

//emotion styled컴포넌트
const UploadContainer = styled.div`
  display: inline-block;
  cursor: pointer;
`;

const Input = styled.input`
  display: none;
`;

//상위에서 props로 필요한 정보 받아옴
const Upload = ({
  children,
  droppable,
  name,
  accept,
  value,
  onChange,
  ...props
}) => {
  // 업로드된 파일
  const [file, setFile] = useState(value);
  // 드로잉 상태 (파일을 직접 드래그해서 추가 시)
  const [dragging, setDragging] = useState(false);
  // input DOM에 직접 접근할 때 사용한다.
  const inputRef = useRef(null);

  //파일 업로드 상태 변화
  const handleFileChange = (e) => {
    const files = e.target.files;
    const changedFile = files[0];
    setFile(changedFile);
    //상위 컴포넌트에 파일 정보를 넘겨줌
    onChange && onChange(changedFile);
  };
  // DOM에 직접 접근해서 파일 선택 이벤트 발생
  const handleChooseFile = () => {
    inputRef.current.click();
  };
  // 드래그를 통해 컴포넌트 안으로 들어왔을 때
  const handleDragEnter = (e) => {
    if (!droppable) return;

    e.preventDefault(); // 브라우저 기본 이벤트를 막는다.
    e.stopPropagation(); // 부모나 자식 컴포넌트로 이벤트가 전파되는 것을 막는다.

    if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
      setDragging(true);
    }
  };
  // 드래그를 통해 컴포넌트 밖으로 나갔을 때 overvout
  const handleDragLeave = (e) => {
    if (!droppable) return;

    e.preventDefault();
    e.stopPropagation();

    setDragging(false);
  };
  // 파일이 새창으로 열리는 것을 막기 위해
  const handleDragOver = (e) => {
    if (!droppable) return;

    e.preventDefault();
    e.stopPropagation();
  };
  // 파일을 컴포넌트 위에 놓았을 때
  const handleFileDrop = (e) => {
    if (!droppable) return;

    e.preventDefault();
    e.stopPropagation();

    const files = e.dataTransfer.files;
    const changedFile = files[0];
    setFile(changedFile);
    onChange && onChange(changedFile);
    setDragging(false);
  };

  return (
    <UploadContainer
      onClick={handleChooseFile}
      onDrop={handleFileDrop}
      onDragEnter={handleDragEnter}
      onDragLeave={handleDragLeave}
      onDragOver={handleDragOver}
      {...props}
    >
      <Input
        ref={inputRef}
        type="file"
        name={name}
        accept={accept}
        onChange={handleFileChange}
      />
      {typeof children === "function" ? children(file, dragging) : children}
    </UploadContainer>
  );
};

export default Upload;

📚 Upload 컴포넌트 사용법 - StoryBook 코드

import Upload from "../../components/Upload";

export default {
  title: "Component/Upload",
  component: Upload,
};

// Default 업로드 storyBook
export const Default = () => {
  return (
    <Upload>
      <button>Click me</button>
    </Upload>
  );
};

// 파일이 선택되었을 때, 선택된 파일 이름 나타내는 storyBook
export const AccessFile = () => {
  return (
    <Upload>
      {(file) => <button>{file ? file.name : "Click me"}</button>}
    </Upload>
  );
};

// 파일 드로깅 상태에 따른 css처리 storyBook
export const Droppable = () => {
  return (
    <Upload droppable>
      {(file, dragging) => (
        <div
          style={{
            width: 300,
            height: 100,
            border: "4px dashed #aaa",
            borderColor: dragging ? "black" : "#aaa",
          }}
        >
          {file ? file.name : "Click or drag file to this area to upload."}
        </div>
      )}
    </Upload>
  );
};

 

✅ Icon- 아이콘 컴포넌트

상위 컴포넌트에서 사용할 Icon 이름을 지정하면 해당 Icon이 컴포넌트로 생긴다.

Icon 크기나, 굵기, 색상, 각도 등을 제어하려면 props로 넘겨주면 된다.

 

여기서 사용할 icon 라이브러리는 feather icon 사용할 예정이다. 

설치 및 사용법

import styled from "@emotion/styled";

const IconWrapper = styled.i`
  display: inline-block;
`;

const Icon = ({
  // name에 따라 Icon이 달라진다
  name,
  size = 16,
  strokeWidth = 2,
  rotate,
  color = "#222",
  ...props
}) => {
  const shapeStyle = {
    width: size,
    height: size,
    transform: rotate ? `rotate(${rotate}deg)` : undefined,
  };
  const iconStyle = {
    "stroke-width": strokeWidth,
    stroke: color,
    width: size,
    height: size,
  };
  // 라이브러리에서 사용할 아이콘 가져오기
  const icon = require("feather-icons").icons[name];
  // icon이 있을 경우 svg로 가져옴
  const svg = icon ? icon.toSvg(iconStyle) : "";
  // 이미지 태그를 사용할려면 base64로 변환해줘야 한다.
  const base64 = Buffer.from(svg, "utf8").toString("base64");

  return (
    <IconWrapper {...props} style={{ ...props.style, ...shapeStyle }}>
      <img src={`data:image/svg+xml;base64,${base64}`} alt={name} />
    </IconWrapper>
  );
};

export default Icon;

📚 Icon컴포넌트 사용법 - StoryBook 코드

import Icon from "../../components/Icon";

export default {
  title: "Component/Icon",
  component: Icon,
  argTypes: {
    name: { defaultValue: "box", control: "text" },
    // 크기 컨트롤
    size: { defaultValue: 16, control: { type: "range", min: 16, max: 80 } },
    // 굵기 컨트롤
    strokeWidth: {
      defaultValue: 2,
      control: { type: "range", min: 2, max: 6 },
    },
    // 각도 컨트롤
    rotate: { defaultValue: 0, control: { type: "range", min: 0, max: 360 } },
    // 색상 컨트롤
    color: { defaultValue: "#222", control: "color" },
  },
};

export const Default = (args) => {
  return <Icon {...args} />;
};

 

👨‍💻 여러 컴포넌트 강의를 듣고 직접 실습하면서 컴포넌트 설계가 얼마나 어려운 것인지 깨달았다.
여러 곳에서 공통으로 사용될 컴포넌트를 각기 다르게 사용하기 위해 어떤 props가 필요한지 미리 알아서 설계해야 하고, 또한 onClick 등 이벤트 관련 함수 정의도 사전에 설계하는 것이 얼마나 어려운지 알 수 있었다.
강사님 말씀처럼 이러한 base 컴포넌트 설계 능력과 시야는 개발자의 숙련도에 많이 영향이 가는 것 같다.

대략적인 컴포넌트 설계는 어떤 기능을 컴포넌트화 시켜서 재사용 할지 정한 다음, 이 컴포넌트를 사용하기 위해 어떤 props, 함수, 컨트롤, 디자인을 고려하면서 컴포넌트 개발을 구체화시키는 것 같다.

728x90

'프로그래머스 데브코스_FE > TIL' 카테고리의 다른 글

[React] React(7)  (0) 2022.01.26
[React] React(6)  (0) 2022.01.26
[React] React(4)  (0) 2022.01.23
[React] React(3)  (0) 2022.01.20
[React] React(2)  (0) 2022.01.18
Comments