react.js Ⅲ Storybook과 emotion 활용하기

young-gue Park·2023년 3월 8일
0

React

목록 보기
5/17
post-thumbnail

⚡ Storybook과 emotion 활용하기


📌 emotion

🔷 react 컴포넌트에 스타일을 적용시키는 방법 중 하나로, 사용하기 쉽기 때문에 주로 이용된다.

  • emotion과 babel-plugin을 설치하고 emotion을 import하여 사용한다.
  • emotion을 이용한 스타일링 시 코드 최상단에 프라그마를 넣거나 Craco를 이용해야한다.

💡 프라그마(Pragma)

  • 컴파일러를 위한 주석과 비슷한 개념이다. 자바에서의 애너테이션과 같은 역할이다.

💡 Craco(create-react-app-config-override)
프라그마 없이 정상적인 emotion 적용을 하기 위해 사용한다.
craco.config.js 생성 및 package.json에서의 실행을 craco로 변환하여 사용한다.

// craco 추가 명령어
yarn add @craco/craco
// craco.config.js
module.exports = {
    babel: {
        "presets": ["@emotion/babel-preset-css-prop"],
    },
}
// package.json 중 scripts 부분
"scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "craco eject",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "init-msw": "msw init public/"
  },
  • css 파일과 같은 코드를 이용하여 스타일을 컴포넌트 내부에서 지정할 수 있다.
import styled from "@emotion/styled";

const Button = styled.button`
    diplay: block;
    width: 100%;
    height: 32px;
    padding: 8px 6px;
    color: white;
    border: none;
    border-radius: 4px;
    outline: none;
    background-color: black;
    box-sizing: border-box;
    cursor: pointer;

    &:hover {
        background-color: #111;
    }

    &:active {
        background-color: #222;
    }

    &:disabled {
        background-color: #888;
    }
`;

export default Button;

이전에 실습에 실패했던 가장 큰 이유는 설치의 문제였다. window os의 경우 yarn이 아닌 npm을 이용한 설치를 할 때 VSCode 내에서 오류가 발생했었다. 그렇기 때문에 yarn을 이용한 설치가 필수적이었는데 yarn 명령어가 VSCode 내에서 먹히지 않았다. 이것 역시 window os를 사용할 때 발생하는 문제였다. 파워셸을 관리자 권한으로 실행한 후 정책을 업데이트 한 뒤에 오류 관련 부분을 직접 수정해주어야만 yarn 명령어가 정상 작동한다.
참고했던 곳, 여기서는 정책 업데이트를 하지 않고 진행했음.


📌 Storybook과 emotion으로 로그인/회원가입 양식 만들기

🔷 로그인과 회원가입 양식을 만든다.

  • 버튼과 입력창, 타이틀, 에러메시지, 폼 양식의 컴포넌트를 따로 제작하여 로그인/회원가입 양식에서 조합한다.
  • 스토리를 이용하여 컴포넌트 별로 정상작동하는지 확인할 수 있게 한다.

🔷 useForm 훅을 이용한다.

  • 아이디와 비밀번호가 들어있지 않을 때, 비밀번호와 비밀번호 확인이 틀릴 때 등을 검증했을 때의 오류메시지가 송출될 수 있게끔한다.
  • 상태를 불러오는 중이라면 로딩 상태를 유지하게끔 한다.

위에서 언급한 Craco를 사용하였다. 그래서 이번 실습에서는 프라그마가 등장하지 않는다.

*.stories.js에는 버튼 동작과 관련된 상태 변화 외엔 스타일을 따로 지정할 수 있게 한 것이 없다. 거의 동일한 코드들이므로 하나의 코드만 예시로 두고 나머지는 생략한다.

💻 Button.js

import styled from "@emotion/styled";

const Button = styled.button`
    diplay: block;
    width: 100%;
    height: 32px;
    padding: 8px 6px;
    color: white;
    border: none;
    border-radius: 4px;
    outline: none;
    background-color: black;
    box-sizing: border-box;
    cursor: pointer;

    &:hover {
        background-color: #111;
    }

    &:active {
        background-color: #222;
    }

    &:disabled {
        background-color: #888;
    }
`;

export default Button;

💻 Button.stories.js

import Button from "../components/Button";

export default {
    title: 'Components/Button',
    component: Button,
    argTypes: {
        onClick: { action: "onClick" }
    }
};

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

🖨 버튼 컴포넌트

💻 Title.js

import styled from "@emotion/styled";

const Title = styled.h1`
    font-size: 24px;
    text-align: center;
`;

export default Title;

🖨 타이틀 컴포넌트

💻 CardForm.js

import styled from "@emotion/styled";

const CardForm = styled.form`
    padding: 16px;
    width: 400px;
    background-color: white;
    box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
`;

export default CardForm;

🖨 카드 양식 컴포넌트

💻 ErrorText.js

import styled from "@emotion/styled";

const ErrorText = styled.span`
    font-size: 12px;
    color: red;
`

export default ErrorText;

🖨 에러메시지 컴포넌트

💻 Input.js

import styled from '@emotion/styled';

const Input = styled.input `
    display: block;
    padding: 4px 6px;
    width: 100%;
    height: 28px;
    font-size: 14px;
    border-radius: 5px;
    border: 2px solid #360;
    background-color: white;
`

export default Input;

🖨 입력창 컴포넌트

💻 useForm.js(Hook)

import { useState } from "react";

const useForm = ( { initialValues, onSubmit, validate } ) => {
    const [values, setValues] = useState(initialValues);
    const [errors, setErrors] = useState({});
    const [isLoading, setIsLoading] = useState(false);

    const handleChange = (e) => {
        const { name, value } = e.target;
        setValues({ ...values, [name]: value })
    };

    const handleSubmit = async (e) => {
        setIsLoading(true);
        e.preventDefault();
        const newErrors = validate(values);
        if(Object.keys(newErrors).length === 0) {
            await onSubmit();
        }
        setErrors(newErrors);
        setIsLoading(false);
    } 

    return {
        values,
        errors,
        isLoading,
        handleChange,
        handleSubmit
    }
};

export default useForm;

💻 LoginForm.js

import Input from "./Input";
import Button from "./Button";
import useForm from "../hooks/useForm";
import ErrorText from "./ErrorText";
import CardForm from "./CardForm";
import Title from "./Title";

const LoginForm = ( { onSubmit }) => {
    const { errors, isLoading, handleChange, handleSubmit } = useForm({
        initialValues: {
            name:'',
            password: ''
        },
        onSubmit,
        validate: ({ name, password }) => {
            const newErrors = {};
            if(!name) newErrors.name = '아이디를 입력해주세요.';
            if(!password) newErrors.password = "비밀번호를 입력해주세요.";
            return newErrors;
        }
    })

    return (
    <CardForm onSubmit={handleSubmit}>
        <Title>로그인</Title>
        <Input type="text" name="id" placeholder="아이디" onChange={handleChange}/>
        {errors.name && <ErrorText>{errors.name}</ErrorText>}
        <Input type="password" name="password" placeholder="비밀번호" onChange={handleChange} style={{marginTop: 8}}/>
        {errors.password && <ErrorText>{errors.password}</ErrorText>}
        <Button type="submit" disabled={isLoading} style={{marginTop: 16}}>로그인</Button>
    </CardForm>
    );
};

export default LoginForm;

🖨 로그인 양식 컴포넌트

  • 기본

  • 아이디/비밀번호를 입력하지 않고 버튼을 누를 때

💻 SignUpForm.js

import Input from "./Input";
import Button from "./Button";
import useForm from "../hooks/useForm";
import ErrorText from "./ErrorText";
import CardForm from "./CardForm";
import Title from "./Title";

const SignUpForm = ({ onSubmit }) => {
    const { errors, isLoading, handleChange, handleSubmit } = useForm({
        initialValues: {
            name:'',
            password: '',
            passwordConfirm: '',
        },
        onSubmit,
        validate: ({ name, password, passwordConfirm }) => {
            const newErrors = {};
            if(!name) newErrors.name = '아이디를 입력해주세요.';
            if(!password) newErrors.password = "비밀번호를 입력해주세요.";
            if(password !== passwordConfirm) newErrors.passwordConfirm = "비밀번호가 일치하지 않습니다.";
            return newErrors;
        }
    })

    return (
    <CardForm onSubmit={handleSubmit}>
        <Title>회원 가입</Title>
        <Input type="text" name="id" placeholder="아이디" onChange={handleChange}/>
        {errors.name && <ErrorText>{errors.name}</ErrorText>}
        <Input type="password" name="password" placeholder="비밀번호" onChange={handleChange} style={{marginTop: 8}}/>
        {errors.password && <ErrorText>{errors.password}</ErrorText>}
        <Input type="password" name="password-confirm" placeholder="비밀번호 확인" onChange={handleChange} style={{marginTop: 8}}/>
        {errors.passwordConfirm && <ErrorText>{errors.passwordConfirm}</ErrorText>}
        <Button type="submit" disabled={isLoading} style={{marginTop: 16}}>회원 가입</Button>
    </CardForm>
    );
}

export default SignUpForm;

🖨 회원가입 양식 컴포넌트

  • 기본

  • 아이디/비밀번호가 없거나 비밀번호와 비밀번호 확인이 다를 때


스토리북과 이모션을 활용하는 법을 익히기 위해 실습해보았다.
이제 컴포넌트에 좀 더 익숙해지기 위한 연습을 해야겠다.

profile
Hodie mihi, Cras tibi

0개의 댓글