요약1) create-react-app & Typescripttsconfig.json2) ESLint & Prettier 설정package.json.eslintrc.js.prettierrc타입스크립트 경로 설정추가한 패키지tsconfig.jsontsconfig.paths.jsoncraco.config.js스토리북 설정3) emotion, styledcraco(create-react-app-config-override)craco.config.jspackage.json4) Storybookstorybook 설치 후 start 오류5) Axios6) .env7) react-router-dom기술 스택1. Recoil사용 방법1) state.js ⇒ 상태들을 저장, 관리한다.2) 사용될 컴포넌트3) App.js2. formik + Yup기본 사용 방법3. moment jsdurationTime 구하기4. react-dnd5. emoji-picker최종 package.json
요약
- Framework :
React
,React BoilerPlate(create-react-app)
- Route:
react-router-dom(5.3.0)
- State Management Tool:
redux
- Network:
Axios
- Convention:
ESLint
,Prettier
- Style:
Emotion
,styled component
- etc:
formik + Yup
,momentjs
,react-dnd
,emoji-picker-react
1) create-react-app & Typescript
- 기본:
yarn
기반 패키지
- CRA를 Typescript 버전으로 설치.
yarn create react-app yas --template typescript
tsconfig.json
{ "compilerOptions": { "target": "es6", // 기본값: es5 => es6로 수정 "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": [ "src" ], }
2) ESLint & Prettier 설정
yarn add -D eslint prettier yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser // ESLint Rule 추가 플러그인 yarn add -D eslint-config-airbnb // airbnb ESLint 규칙 yarn add -D eslint-config-prettier eslint-plugin-prettier // prettier Rule 플러그인 yarn add -D eslint-plugin-react eslint-plugin-react-hooks // airbnb ESLint 규칙 yarn add -D eslint-plugin-jsx-a11y eslint-plugin-import // airbnb ESLint 규칙
package.json
- 설치 완료 후 아래의
devDependencies
가 추가된다.
// ... "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "eslint": "^8.3.0", "eslint-config-airbnb": "^19.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.3", "eslint-plugin-jsx-a11y "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", "prettier": "^2.5.0" } // ...
.eslintrc.js
- 자동 생성된
.eslintrc.js
코드를 아래와 같이 수정하여 rule을 추가한다.
module.exports = { env: { browser: true, node: true, commonjs: true, es2021: true, }, extends: [ 'plugin:react/recommended', 'plugin:jsx-a11y/recommended', 'plugin:import/errors', 'plugin:import/warnings', 'plugin:@typescript-eslint/recommended', ], parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 2018, sourceType: 'module', }, plugins: ['react', '@typescript-eslint'], rules: { 'react/react-in-jsx-scope': 0, }, settings: { 'import/resolver': { node: { extensions: ['.js', '.jsx', '.ts', '.tsx'], }, }, }, };
.prettierrc
- 루트 폴더에
.prettierrc
파일 생성 후 아래 코드를 작성한다.
{ "singleQuote": true, "semi": true, "useTabs": false, "tabWidth": 2, "trailingComma": "all", "printWidth": 80, "arrowParens": "always", "orderedImports": true, "bracketSpacing": true, "jsxBracketSameLine": false }
타입스크립트 경로 설정
추가한 패키지
tsconfig-paths-webpack-plugin
craco-alias
tsconfig.json
{ "extends": "./tsconfig.paths.json", // 추가, 해당 파일에서 속성을 상속받는다. "compilerOptions": { "target": "es6", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": [ "src" ], "exclude": [ // 추가, 내용 더 찾아보기 "node_modules", "**/node_modules/*" ] }
tsconfig.paths.json
tsconfig의 절대경로 별칭 지정
{ "compilerOptions": { "baseUrl": "./src", "paths": { "@/*": ["./*"] } } }
craco.config.js
craco의 절대경로 별칭을 지정
// eslint-disable-next-line const CracoAlias = require('craco-alias'); // 추가 module.exports = { babel: { presets: ['@emotion/babel-preset-css-prop'], }, plugins: [ // 추가, craco의 절대경로 별칭을 지정 { plugin: CracoAlias, options: { source: 'tsconfig', baseUrl: './src', tsConfigPath: './tsconfig.paths.json', }, }, ], };
스토리북 설정
index.css
추가
preview-head.html
추가
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset-css@5.0.1/reset.min.css" /> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet" />
mian.js
내용 추가- 스토리북 경로 alias 설정 (feat.
tsconfig-paths-webpack-plugin
패키지)
const path = require('path'); const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); module.exports = { stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', '@storybook/preset-create-react-app', ], webpackFinal: async (config) => { config.resolve.plugins.push(new TsconfigPathsPlugin({})); return config; }, };
참고자료
preview.js
내용 추가- 커스텀 뷰포트 추가
- mobile
- tablet
- pc
MemoryRouter
추가- 스토리북상에서 링크 이동 관련 액션발생시 홈으로 라우팅
import './index.css'; import React from 'react'; import { addDecorator } from '@storybook/react'; import { MemoryRouter } from 'react-router-dom'; addDecorator((story) => ( <MemoryRouter initialEntries={['/']}>{story()}</MemoryRouter> )); const customViewports = { mobile: { name: 'Mobile', styles: { width: '320px', height: '100%', }, }, tablet: { name: 'Tablet', styles: { width: '768px', height: '100%', }, }, pc: { name: 'PC', styles: { width: '1920px', height: '100%', }, }, }; export const parameters = { actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, viewport: { viewports: customViewports }, };
3) emotion, styled
- styled component
- javascript 파일 내부에서 스타일 적용 패키지
yarn add @emotion/react // emotion yarn add --dev @emotion/babel-plugin // babel yarn add @emotion/styled // styled
// ... "dependencies": { "@emotion/react": "^11.6.0", "@emotion/styled": "^11.6.0", // ...
craco(create-react-app-config-override)
- 위의 패키지 3개만 추가하면 styled 패키지를 사용할 때 마다
/** @jsxImportSource @emotion/react */
fragma 주석을 추가해야 되는데, 이를 해결하기 위해craco
를 설치한다.
yarn add -D @craco/craco yarn add -D @emotion/babel-preset-css-prop
craco.config.js
- 루트 폴더에 craco.config.js 파일을 생성하여 아래와 같이 추가한다.
module.exports = { babel: { presets: ["@emotion/babel-preset-css-prop"] } }
package.json
- package.json 파일에서 scripts 부분을
react-scripts
에서craco
로 변경한다.
// ... "scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "craco eject" }, // ...
4) Storybook
- UI 컴포넌트를 모아 문서화하고 보여주는 오픈소스 Tool
npx -p @storybook/cli sb init

storybook 설치 후 start 오류
- storybook 설치 후 react-scripts와 storybook의 babel-loader 버전이 달라서 yarn start로 로컬 환경 Open이 되지 않는 현상이 발생한다.
- package.json 파일에 아래 코드를 추가하여 해결할 수 있다.
// package.json // ... "resolutions": { "babel-loader": "8.1.0" } // ...
5) Axios
- 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리
yarn add axios
6) .env
ESLint
설치 후 package.json과 yarn.lock에서의 package version 차이가 발생해서 yarn start 같은 로컬 환경이 Open되지 않는다.
- 루트 폴더에 .env 파일을 생성 후 아래 코드를 추가한다.
SKIP_PREFLIGHT_CHECK=true
7) react-router-dom
- react-router-dom을 설치하면 최신버전인 6버전으로 설치가 된다.
- 하지만 6버전은 아직 문법이 많이 낮설기 때문에 아래와 같이 5버전으로 설치를 진행한다.
(5버전이랑 6버전 사용방법이 많이 다르다.)
yarn add react-router-dom@5.3.0
기술 스택
1. Recoil
- React 상태관리 라이브러리이다.
- 부모 트리(보통
App.js
)에RecoilRoot
태그로 한 번 감싸준 후 Atom(State) 등을 통해 상태를 관리, 사용한다.
사용 방법
1) state.js ⇒ 상태들을 저장, 관리한다.
import { atom } from "recoil"; // atom은 상태를 나타낸다. export const textState = atom({ key: "textState", // default: "", });
2) 사용될 컴포넌트
import { useRecoilState } from "recoil"; import { textState } from "./state"; const RecoilTest = () => { const [value, setValue] = useRecoilState(textState); // ... }
3) App.js
import { RecoilRoot } from "recoil"; import "./App.css"; function App() { return ( // 사용될 컴포넌트를 RecoilRoot로 감싸주면 자식 컴포넌트에서 전역으로 사용할 수 있다. <RecoilRoot> // ... </RecoilRoot> ); } export default App;
2. formik + Yup
- form 태그 내부에서 사용되는 input 태그의 입력 값의 Change, Blur, Submit 등의 폼 기능을 간편하게 사용할 수 있다.
- Yup 라이브러리와 함께 사용해서 입력 값의 유효성 검사를 쉽게 작성할 수 있다.
- Yup을 사용하지 않고 validation을 직접 작성할 수 있다.
- 회원가입, 로그인 페이지에서 사용될 수 있다.
기본 사용 방법
const formik = useFormik({ initialValues: { userEmail: "", userPassword: "", }, validationSchema: Yup.object({ userEmail: Yup.string() .email(invalidErrorMessage.email) .required(invalidErrorMessage.email), userPassword: Yup.string() .min(8, invalidErrorMessage.password) .max(15, invalidErrorMessage.password) .matches(/^[a-zA-Z0-9]+$/, invalidErrorMessage.password) .required(invalidErrorMessage.password), }), onSubmit: (value) => { alert(value); }, }); // {...} <form onSubmit={formik.handleSubmit}> <input id="userEmail" name="userEmail" type="email" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.userEmail} placeholder="이메일" /> {formik.errors.userEmail} <input id="userPassword" name="userPassword" type="password" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.userPassword} placeholder="비밀번호" /> {formik.errors.userPassword} </form> // {...}
3. moment js
- 날짜 계산을 쉽게 할 수 있다.
- 각 루틴, 미션의 startTime, endTime을 받아서 durationTime을 구할 때
그 외 날짜와 관련된 기능을 쉽게 사용할 수 있다.
durationTime 구하기
const startTime = moment(); // 미션이 시작될 때 startTime 변수 생성 const endTime = moment(); // 미션이 끝났을 때 endTime 변수 생성 const durationTime = moment.duration(endTime.diff(startTime)).asSeconds(); // 초(second) 단위 Number type으로 변환된다. // duration값에서는 asDays(), asHours() 등으로 원하는 값을 얻을 수 있다.
4. react-dnd
5. emoji-picker
최종 package.json
{ "name": "yas", "version": "0.1.0", "private": true, "dependencies": { "@emotion/react": "^11.6.0", "@emotion/styled": "^11.6.0", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@types/jest": "^26.0.15", "@types/node": "^12.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", "axios": "^0.24.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", "typescript": "^4.1.2", "web-vitals": "^1.0.1" }, "scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "craco eject", "storybook": "start-storybook -p 6006 -s public", "build-storybook": "build-storybook -s public" }, "resolutions": { "babel-loader": "8.1.0" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ], "overrides": [ { "files": [ "**/*.stories.*" ], "rules": { "import/no-anonymous-default-export": "off" } } ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@craco/craco": "^6.4.2", "@emotion/babel-plugin": "^11.3.0", "@emotion/babel-preset-css-prop": "^11.2.0", "@storybook/addon-actions": "^6.3.12", "@storybook/addon-essentials": "^6.3.12", "@storybook/addon-links": "^6.3.12", "@storybook/node-logger": "^6.3.12", "@storybook/preset-create-react-app": "^3.2.0", "@storybook/react": "^6.3.12", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "eslint": "8.3.0", "eslint-config-airbnb": "^19.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.3", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", "prettier": "^2.5.0" } }