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<타입, '키'>` 전달받은 타입에서 키를 제외한 타입을 생성한다.
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 등 다양한 기술을 사용하는 것도 큰 틀을 봐야지만 할 수 있는것!!
화면 큰틀 보기는 어느정도 익숙해진 것 같다.