웹 풀 사이클 데브코스

[TIL] Day60 - 컴포넌트, 헤더

닿다라다나닷 2024. 7. 17. 21:26

Title 컴포넌트

폰트크기 theme 에 적용하기

export type Headingsize = 'large' | 'medium' | 'small';

interface Theme {
  name: ThemeName;
  color: {
    [key in ColorKey]: string;
  };
  heading: {
    [key in Headingsize]: {
      fontSize: string;
    };
  };
  
export const light: Theme = {
  name: 'light',
  color: {
    primary: 'brown',
    background: 'lightgray',
    secondary: 'blue',
    third: 'green',
  },
  heading: {
    large: { fontSize: '2rem' },
    medium: { fontSize: '1.5rem' },
    small: { fontSize: '1rem' },
  },
};

export const dark: Theme = {
  ...light,
  name: 'dark',
  color: {
    primary: 'coral',
    background: 'midnightblue',
    secondary: 'darkblue',
    third: 'darkgreen',
  },
};

이때, 폰트크기는 테마마다 변할리가 없는데,,, 타입은 둘다 지정해주어야 한다! 따라서 `?` 옵셔널 또는 `...light` 오브젝트 복사로 해결할 수 있다. > undefined 일 경우도 파악해서 경우 작성하기

 

Omit 유틸리티 타입

`Omit<타입, '키'>` 전달받은 타입에서 키를 제외한 타입을 생성한다.

 

Documentation - Utility Types

Types which are globally included in TypeScript

www.typescriptlang.org

 

props 관련 테스트는 필수로 진행해야, 프로젝트 규모에 맞다.

 

import React from 'react';
import { styled } from 'styled-components';
import { ColorKey, Headingsize } from '../../style/theme';

interface Props {
  children: React.ReactNode;
  size: Headingsize;
  color?: ColorKey;
}

const Title = ({ children, size, color }: Props) => {
  return (
    <TitleStyle size={size} color={color}>
      {children}
    </TitleStyle>
  );
};

const TitleStyle = styled.h1<Omit<Props, 'children'>>`
  font-size: ${({ theme, size }) => theme.heading[size].fontSize};
  color: ${({ theme, color }) =>
    color ? theme.color[color] : theme.color.primary};
`;

export default Title;

 

컴포넌트 잘 렌더링 되는지 Jest 를 이용해 테스트 `Title.spec.tsx`

import { render, screen } from '@testing-library/react';
import Title from './Title';
import { BookStoreThemeProvider } from '../../context/themeContext';

describe('Title 컴포넌트 테스트', () => {
  it('렌더를 확인', () => {
    render(
      <BookStoreThemeProvider>
        <Title size="large">제목</Title>
      </BookStoreThemeProvider>,
    );
    expect(screen.getByText('제목')).toBeInTheDocument();
  });

  it('size props 적용', () => {
    const { container } = render(
      <BookStoreThemeProvider>
        <Title size="large">제목</Title>
      </BookStoreThemeProvider>,
    );
    expect(container?.firstChild).toHaveStyle({ fontSize: '2rem' });
  });

  it('color props 적용', () => {
    const { container } = render(
      <BookStoreThemeProvider>
        <Title size="large" color="primary">
          제목
        </Title>
      </BookStoreThemeProvider>,
    );
    expect(container?.firstChild).toHaveStyle({ color: 'brown' });
  });
});

 

Button 컴포넌트

컴포넌트로 만들면 유용하게 잘 쓴다!

scheme : 버튼의 border, background, text color 정도로 이루어진 프리셋 세트이다

pointer-events : none 일 경우 마우스 이벤트(클릭기능) 무시, auto 일 경우 이벤트 실행된다.

 button: {
    [key in ButtonSize]: {
      fontSize: string;
      padding: string;
    };
  };
  buttonScheme: {
    [key in ButtonScheme]: {
      color: string;
      backgroundColor: string;
    };
  };
  borderRadius: {
    default: string;
  };
////////
import React from 'react';
import { styled } from 'styled-components';
import { ButtonScheme, ButtonSize } from '../../style/theme';

interface Props {
  children: React.ReactNode;
  size: ButtonSize;
  scheme: ButtonScheme;
  disabled?: boolean;
  isLoading?: boolean;
}

const Button = ({ children, size, scheme, disabled, isLoading }: Props) => {
  return (
    <ButtonStyle
      size={size}
      scheme={scheme}
      disabled={disabled}
      isLoading={isLoading}
    >
      {children}
    </ButtonStyle>
  );
};

const ButtonStyle = styled.button<Omit<Props, 'children'>>`
  font-size: ${({ theme, size }) => theme.button[size].fontSize};
  padding: ${({ theme, size }) => theme.button[size].fontSize};
  color: ${({ theme, scheme }) => theme.buttonScheme[scheme].color};
  background-color: ${({ theme, scheme }) =>
    theme.buttonScheme[scheme].backgroundColor};
  border: 0;
  border-radius: ${({ theme }) => theme.borderRadius.default};
  opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
  pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')};
  cursor: ${({ disabled }) => (disabled ? 'none' : 'pointer')};
`;

export default Button;

 

`Button.spec.tsx` 로 테스트 하기

import { render, screen } from '@testing-library/react';
import { BookStoreThemeProvider } from '../../context/themeContext';
import Button from './Button';

describe('Button 컴포넌트 테스트', () => {
  it('렌더를 확인', () => {
    render(
      <BookStoreThemeProvider>
        <Button size="large" scheme="primary">
          버튼
        </Button>
      </BookStoreThemeProvider>,
    );
    expect(screen.getByText('버튼')).toBeInTheDocument();
  });
  
  it('size props 적용', () => {
    render(
      <BookStoreThemeProvider>
        <Button size="large" scheme="primary">제목</Button>
      </BookStoreThemeProvider>,
    );
    expect(screen.getByRole("button")).toHaveStyle({ fontSize: '1.5rem' });
  });

});

//disable, isLoading

expect의 getByRole()로 컴포넌트 아이템 button 로딩한다.

 

 

Input 컴포넌트

input 의 타입을 지정하는 `attrs` 어트리뷰트 메서드 사용한다.

import React, { ForwardedRef } from 'react';
import styled from 'styled-components';

interface Props {
  placeholder?: string;
}

const InputText = React.forwardRef(
  ({ placeholder }: Props, ref: ForwardedRef<HTMLInputElement>) => {
    //reactr 내장 input 컴포넌트
    return <InputTextStyle placeholder={placeholder} ref={ref} />;
  },
);

const InputTextStyle = styled.input.attrs({ type: 'text' })`
  padding: 0.25rem 0.75rem;
  border: 1px solid ${({ theme }) => theme.color.border};
  border-radius: ${({ theme }) => theme.borderRadius.default};
  font-size: 1rem;
  line-height: 1.5;
  color: ${({ theme }) => theme.color.text};
`;

export default InputText;

 

`InputText.spec.tsx`로 테스트 코드

import { render, screen } from '@testing-library/react';
import { BookStoreThemeProvider } from '../../context/themeContext';
import InputText from './InputText';
import React from 'react';

describe('Input 컴포넌트 테스트', () => {
  it('렌더를 확인', () => {
    render(
      <BookStoreThemeProvider>
        <InputText placeholder="여기에 입력" />
      </BookStoreThemeProvider>,
    );
    expect(screen.getByPlaceholderText('여기에 입력')).toBeInTheDocument();
  });

  it('forwardRef 테스트', () => {
    const ref = React.createRef<HTMLInputElement>();
    render(
      <BookStoreThemeProvider>
        <InputText placeholder="여기에 입력" ref={ref} />
      </BookStoreThemeProvider>,
    );
    expect(ref.current).toBeInstanceOf(HTMLInputElement);
  });
});

 

헤더, 푸터

로고영역, 카테고리, 개인화 영역(로그인 여부)

로고, 카피라이트

Layout.tsx 에서 전체 스타일 고려하기

 

☑️ 배운점

타입스크립트를 하면서 느끼는점은, 단순 react 코드를 짜는게 아닌 설계부터 정확히 체크해가며 예외를 고려해가며 코드를 짜야한다는 사실이다.

styled-components 등 다양한 기술을 사용하는 것도 큰 틀을 봐야지만 할 수 있는것!!

 

화면 큰틀 보기는 어느정도 익숙해진 것 같다.