From b71fe1b085416d9634e734dda539b08cd8ce3672 Mon Sep 17 00:00:00 2001 From: r0o0 Date: Sat, 13 Jun 2020 09:39:07 +0900 Subject: [PATCH 1/2] Configure TypeScript --- client/package.json | 13 ++- client/src/components/Button/Button.jsx | 15 ---- client/src/components/Button/Button.md | 2 +- client/src/components/Button/Button.tsx | 11 +++ client/src/components/Button/Button.types.ts | 7 ++ client/src/components/Sample/ReduxSample.md | 4 +- .../{ReduxSample.jsx => ReduxSample.tsx} | 18 ++-- .../components/Sample/ReduxSample.types.ts | 7 ++ client/src/components/{index.js => index.ts} | 0 .../{configureStore.js => configureStore.ts} | 12 ++- client/src/{index.jsx => index.tsx} | 0 client/src/pages/{App.jsx => App.tsx} | 0 client/src/pages/Home/Home.jsx | 13 --- .../Home/{Home.test.js => Home.test.tsx} | 1 + client/src/pages/Home/Home.tsx | 15 ++++ client/src/pages/{Router.jsx => Router.tsx} | 4 +- client/src/pages/{index.js => index.ts} | 0 client/src/react-app-env.d.ts | 1 + .../{serviceWorker.js => serviceWorker.ts} | 0 client/src/{setupTests.js => setupTests.ts} | 0 .../store/ReduxSample/ReduxSample.actions.js | 21 ----- .../store/ReduxSample/ReduxSample.actions.ts | 32 +++++++ ...le.reducers.js => ReduxSample.reducers.ts} | 13 ++- .../store/ReduxSample/ReduxSample.types.ts | 16 ++++ client/src/store/action-helpers.ts | 9 ++ .../{actions.test.js => actions.test.ts} | 0 client/src/store/{actions.js => actions.ts} | 0 client/src/store/{index.js => index.ts} | 4 +- client/src/store/store.types.ts | 3 + .../{StoreProvider.jsx => StoreProvider.tsx} | 0 .../styleguide/components/StyleContainer.jsx | 22 ----- .../styleguide/components/StyleContainer.tsx | 23 +++++ .../{common.types.js => common.types.ts} | 0 client/src/types/constants.js | 18 ---- client/src/types/enums.ts | 21 +++++ client/src/types/{index.js => index.ts} | 0 client/src/types/ui.types.js | 9 -- client/src/types/ui.types.ts | 5 ++ client/styleguide.config.js | 22 ++++- client/tsconfig.json | 20 +++++ client/yarn.lock | 90 ++++++++++++++++++- 41 files changed, 324 insertions(+), 127 deletions(-) delete mode 100644 client/src/components/Button/Button.jsx create mode 100644 client/src/components/Button/Button.tsx create mode 100644 client/src/components/Button/Button.types.ts rename client/src/components/Sample/{ReduxSample.jsx => ReduxSample.tsx} (58%) create mode 100644 client/src/components/Sample/ReduxSample.types.ts rename client/src/components/{index.js => index.ts} (100%) rename client/src/{configureStore.js => configureStore.ts} (67%) rename client/src/{index.jsx => index.tsx} (100%) rename client/src/pages/{App.jsx => App.tsx} (100%) delete mode 100644 client/src/pages/Home/Home.jsx rename client/src/pages/Home/{Home.test.js => Home.test.tsx} (83%) create mode 100644 client/src/pages/Home/Home.tsx rename client/src/pages/{Router.jsx => Router.tsx} (88%) rename client/src/pages/{index.js => index.ts} (100%) create mode 100644 client/src/react-app-env.d.ts rename client/src/{serviceWorker.js => serviceWorker.ts} (100%) rename client/src/{setupTests.js => setupTests.ts} (100%) delete mode 100644 client/src/store/ReduxSample/ReduxSample.actions.js create mode 100644 client/src/store/ReduxSample/ReduxSample.actions.ts rename client/src/store/ReduxSample/{ReduxSample.reducers.js => ReduxSample.reducers.ts} (52%) create mode 100644 client/src/store/ReduxSample/ReduxSample.types.ts create mode 100644 client/src/store/action-helpers.ts rename client/src/store/{actions.test.js => actions.test.ts} (100%) rename client/src/store/{actions.js => actions.ts} (100%) rename client/src/store/{index.js => index.ts} (55%) create mode 100644 client/src/store/store.types.ts rename client/src/styleguide/components/{StoreProvider.jsx => StoreProvider.tsx} (100%) delete mode 100644 client/src/styleguide/components/StyleContainer.jsx create mode 100644 client/src/styleguide/components/StyleContainer.tsx rename client/src/types/{common.types.js => common.types.ts} (100%) delete mode 100644 client/src/types/constants.js create mode 100644 client/src/types/enums.ts rename client/src/types/{index.js => index.ts} (100%) delete mode 100644 client/src/types/ui.types.js create mode 100644 client/src/types/ui.types.ts create mode 100644 client/tsconfig.json diff --git a/client/package.json b/client/package.json index a916724..8aad72f 100644 --- a/client/package.json +++ b/client/package.json @@ -4,18 +4,27 @@ "private": true, "dependencies": { "@reduxjs/toolkit": "^1.3.5", + "@types/classnames": "^2.2.10", + "@types/jest": "^25.2.3", + "@types/node": "^14.0.12", + "@types/react": "^16.9.35", + "@types/react-dom": "^16.9.8", + "@types/react-redux": "^7.1.9", + "@types/webpack-env": "^1.15.2", "antd": "^4.2.2", "axios": "^0.19.2", "classnames": "^2.2.6", "customize-cra": "^0.9.1", "node-sass": "^4.14.0", - "prop-types": "^15.7.2", "react": "^16.13.1", "react-app-rewired": "^2.1.6", + "react-docgen": "^5.3.0", + "react-docgen-typescript": "^1.16.6", "react-dom": "^16.13.1", "react-redux": "^7.2.0", "react-router-dom": "^5.1.2", - "react-scripts": "3.4.1" + "react-scripts": "3.4.1", + "typescript": "^3.9.5" }, "husky": { "hooks": { diff --git a/client/src/components/Button/Button.jsx b/client/src/components/Button/Button.jsx deleted file mode 100644 index b9c669e..0000000 --- a/client/src/components/Button/Button.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { string } from 'prop-types'; - -// TODO 버튼 컴포넌트 작성 -const Button = ({ text, ...rest }) => ( - -); - -Button.propTypes = { - text: string.isRequired, -}; - -export default Button; diff --git a/client/src/components/Button/Button.md b/client/src/components/Button/Button.md index 6f2012a..c507cd5 100644 --- a/client/src/components/Button/Button.md +++ b/client/src/components/Button/Button.md @@ -1,5 +1,5 @@ Button example: ```js - ``` diff --git a/client/src/components/Button/Button.tsx b/client/src/components/Button/Button.tsx new file mode 100644 index 0000000..5357de6 --- /dev/null +++ b/client/src/components/Button/Button.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Props } from './Button.types'; + +// TODO 버튼 컴포넌트 작성 +const Button: React.FC = ({ children, type = 'button', ...rest }) => ( + +); + +export default Button; diff --git a/client/src/components/Button/Button.types.ts b/client/src/components/Button/Button.types.ts new file mode 100644 index 0000000..96c11fa --- /dev/null +++ b/client/src/components/Button/Button.types.ts @@ -0,0 +1,7 @@ +type AttributeProps = React.ButtonHTMLAttributes; + +type CustomProps = { + theme?: 'primary' | 'secondary'; +}; + +export type Props = AttributeProps & CustomProps; diff --git a/client/src/components/Sample/ReduxSample.md b/client/src/components/Sample/ReduxSample.md index f81870f..d54e93a 100644 --- a/client/src/components/Sample/ReduxSample.md +++ b/client/src/components/Sample/ReduxSample.md @@ -1,8 +1,8 @@ ReduxSample example: ```js -import StyleContainer from '../../styleguide/components/StyleContainer.jsx'; -import StoreProvider from '../../styleguide/components/StoreProvider.jsx'; +import StyleContainer from '../../styleguide/components/StyleContainer.tsx'; +import StoreProvider from '../../styleguide/components/StoreProvider.tsx'; diff --git a/client/src/components/Sample/ReduxSample.jsx b/client/src/components/Sample/ReduxSample.tsx similarity index 58% rename from client/src/components/Sample/ReduxSample.jsx rename to client/src/components/Sample/ReduxSample.tsx index a92c947..51f990d 100644 --- a/client/src/components/Sample/ReduxSample.jsx +++ b/client/src/components/Sample/ReduxSample.tsx @@ -2,19 +2,21 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Button } from '../index'; import { ReduxSampleActions } from '../../store/actions'; +import { UserSampleState } from './ReduxSample.types'; +import { AppState } from '../../store/store.types'; // TODO redux 쓰는 법 익히게 되면 제거 -const ReduxSample = () => { +const ReduxSample: React.FC = () => { const { sampleButton: { message }, sampleUsers, - } = useSelector((state) => state.sample); + } = useSelector((state: AppState) => state.sample); const dispatch = useDispatch(); const handleClick = () => { dispatch(ReduxSampleActions.getSampleUsers()); - dispatch(ReduxSampleActions.triggerBtnClick('SUCCESS')); + dispatch(ReduxSampleActions.triggerBtnClick({ message: 'SUCCESS' })); }; const handleReset = () => dispatch(ReduxSampleActions.reset()); @@ -22,12 +24,16 @@ const ReduxSample = () => { return ( <>
- +
{sampleUsers.length > 0 && (
    - {sampleUsers.map((user) => ( + {sampleUsers.map((user: UserSampleState) => (
  • {user.email}
  • ))}
diff --git a/client/src/components/Sample/ReduxSample.types.ts b/client/src/components/Sample/ReduxSample.types.ts new file mode 100644 index 0000000..f6ff065 --- /dev/null +++ b/client/src/components/Sample/ReduxSample.types.ts @@ -0,0 +1,7 @@ +export interface UserSampleState { + id: number; + email: string; + first_name: string; + last_name: string; + avatar: string; +} diff --git a/client/src/components/index.js b/client/src/components/index.ts similarity index 100% rename from client/src/components/index.js rename to client/src/components/index.ts diff --git a/client/src/configureStore.js b/client/src/configureStore.ts similarity index 67% rename from client/src/configureStore.js rename to client/src/configureStore.ts index d941a7f..a41d214 100644 --- a/client/src/configureStore.js +++ b/client/src/configureStore.ts @@ -2,22 +2,26 @@ import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'; import rootReducer from './store'; import loggerMiddleware from './middlewares/logger'; import monitorReducerEnhancer from './enhancers/monitorReducers'; +import { AppState } from './store/store.types'; -export default function configureAppStore(preloadedState) { +export type AppDispatch = ReturnType['dispatch']; + +const configureAppStore = (preloadedState = {}) => { const store = configureStore({ reducer: rootReducer, preloadedState, - middleware: [loggerMiddleware, ...getDefaultMiddleware()], + middleware: [loggerMiddleware, ...getDefaultMiddleware()], enhancers: [monitorReducerEnhancer], }); if (process.env.NODE_ENV === 'development' && module.hot) { module.hot.accept('./store', () => { - // eslint-disable-next-line global-require const newRootReducer = require('./store').default; store.replaceReducer(newRootReducer); }); } return store; -} +}; + +export default configureAppStore; diff --git a/client/src/index.jsx b/client/src/index.tsx similarity index 100% rename from client/src/index.jsx rename to client/src/index.tsx diff --git a/client/src/pages/App.jsx b/client/src/pages/App.tsx similarity index 100% rename from client/src/pages/App.jsx rename to client/src/pages/App.tsx diff --git a/client/src/pages/Home/Home.jsx b/client/src/pages/Home/Home.jsx deleted file mode 100644 index 344cff5..0000000 --- a/client/src/pages/Home/Home.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -import './Home.css'; - -const Home = () => ( -
-
-

d.log

-
-
-); - -export default Home; diff --git a/client/src/pages/Home/Home.test.js b/client/src/pages/Home/Home.test.tsx similarity index 83% rename from client/src/pages/Home/Home.test.js rename to client/src/pages/Home/Home.test.tsx index f1faa3d..a86e083 100644 --- a/client/src/pages/Home/Home.test.js +++ b/client/src/pages/Home/Home.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; import Home from './Home'; diff --git a/client/src/pages/Home/Home.tsx b/client/src/pages/Home/Home.tsx new file mode 100644 index 0000000..85b5dbf --- /dev/null +++ b/client/src/pages/Home/Home.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import './Home.css'; + +function Home() { + return ( +
+
+

d.log

+
+
+ ); +} + +export default Home; diff --git a/client/src/pages/Router.jsx b/client/src/pages/Router.tsx similarity index 88% rename from client/src/pages/Router.jsx rename to client/src/pages/Router.tsx index 5523812..fda9e03 100644 --- a/client/src/pages/Router.jsx +++ b/client/src/pages/Router.tsx @@ -12,11 +12,11 @@ export const PAGE_URL = { const ROUTES = [{ path: PAGE_URL.HOME, exact: true, component: Home }]; -const Router = () => ( +const Router: React.FC = () => ( - {ROUTES.map(route => ( + {ROUTES.map((route) => ( ))} diff --git a/client/src/pages/index.js b/client/src/pages/index.ts similarity index 100% rename from client/src/pages/index.js rename to client/src/pages/index.ts diff --git a/client/src/react-app-env.d.ts b/client/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/client/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/client/src/serviceWorker.js b/client/src/serviceWorker.ts similarity index 100% rename from client/src/serviceWorker.js rename to client/src/serviceWorker.ts diff --git a/client/src/setupTests.js b/client/src/setupTests.ts similarity index 100% rename from client/src/setupTests.js rename to client/src/setupTests.ts diff --git a/client/src/store/ReduxSample/ReduxSample.actions.js b/client/src/store/ReduxSample/ReduxSample.actions.js deleted file mode 100644 index 3f45618..0000000 --- a/client/src/store/ReduxSample/ReduxSample.actions.js +++ /dev/null @@ -1,21 +0,0 @@ -import axios from 'axios'; -import { createAction, createAsyncThunk } from '@reduxjs/toolkit'; - -const triggerClick = (message) => ({ payload: { message } }); - -const fetchSampleUsers = async () => { - try { - const { data } = await axios.get('https://reqres.in/api/users'); - return data.data; - } catch (error) { - console.error(error); - } -}; - -const ReduxSampleActions = { - getSampleUsers: createAsyncThunk('sample/GET_SAMPLE_USERS', fetchSampleUsers), - triggerBtnClick: createAction('sample/TRIGGER_BTN_CLICK', triggerClick), - reset: createAction('sample/RESET'), -}; - -export default ReduxSampleActions; diff --git a/client/src/store/ReduxSample/ReduxSample.actions.ts b/client/src/store/ReduxSample/ReduxSample.actions.ts new file mode 100644 index 0000000..307c69e --- /dev/null +++ b/client/src/store/ReduxSample/ReduxSample.actions.ts @@ -0,0 +1,32 @@ +import axios from 'axios'; +import { createAsyncThunk } from '@reduxjs/toolkit'; +import { createStandardAction } from '../action-helpers'; +import { UserSampleState } from './ReduxSample.types'; +import { AppState } from '../store.types'; +import { AppDispatch } from '../../configureStore'; + +interface ThunkAPI { + state: AppState; + dispatch: AppDispatch; +} + +export const GET_SAMPLE_USERS = '@sample/GET_SAMPLE_USERS'; +export const TRIGGER_BTN_CLICK = '@sample/TRIGGER_BTN_CLICK'; +export const RESET = '@sample/RESET'; + +const fetchSampleUsers = async () => { + try { + const { data } = await axios.get('https://reqres.in/api/users'); + return data.data; + } catch (error) { + console.error(error); + } +}; + +const ReduxSampleActions = { + getSampleUsers: createAsyncThunk(GET_SAMPLE_USERS, fetchSampleUsers), + triggerBtnClick: createStandardAction<{ message: string }>(TRIGGER_BTN_CLICK), + reset: createStandardAction(RESET), +}; + +export default ReduxSampleActions; diff --git a/client/src/store/ReduxSample/ReduxSample.reducers.js b/client/src/store/ReduxSample/ReduxSample.reducers.ts similarity index 52% rename from client/src/store/ReduxSample/ReduxSample.reducers.js rename to client/src/store/ReduxSample/ReduxSample.reducers.ts index f34a687..99f398f 100644 --- a/client/src/store/ReduxSample/ReduxSample.reducers.js +++ b/client/src/store/ReduxSample/ReduxSample.reducers.ts @@ -1,25 +1,30 @@ import { createReducer } from '@reduxjs/toolkit'; -import ReduxSampleActions from './ReduxSample.actions'; +import ReduxSampleActions, { GET_SAMPLE_USERS, TRIGGER_BTN_CLICK, RESET } from './ReduxSample.actions'; +import { ReduxSampleState } from './ReduxSample.types'; -export const initialState = { +export const initialState: ReduxSampleState = { sampleUsers: [], sampleButton: { message: 'Fetch Users', isClicked: false, + loading: false, }, }; +// TODO createReducer 타입 추가 const reduxSampleReducer = createReducer(initialState, { + // TODO createAsyncAction helper 생성 후 fulfilled 상태 타입 확인 [ReduxSampleActions.getSampleUsers.fulfilled]: (state, { payload: userData }) => { + console.log(userData); state.sampleUsers = userData; }, - [ReduxSampleActions.triggerBtnClick]: (state, action) => { + [TRIGGER_BTN_CLICK]: (state, action) => { const { message } = action.payload; state.sampleButton.message = message; state.sampleButton.loading = true; }, - [ReduxSampleActions.reset]: () => initialState, + [RESET]: () => initialState, }); export default reduxSampleReducer; diff --git a/client/src/store/ReduxSample/ReduxSample.types.ts b/client/src/store/ReduxSample/ReduxSample.types.ts new file mode 100644 index 0000000..959c719 --- /dev/null +++ b/client/src/store/ReduxSample/ReduxSample.types.ts @@ -0,0 +1,16 @@ +export interface UserSampleState { + id: number; + email: string; + first_name: string; + last_name: string; + avatar: string; +} + +export interface ReduxSampleState { + sampleUsers: UserSampleState[]; + sampleButton: { + message: string; + isClicked: boolean; + loading: boolean; + }; +} diff --git a/client/src/store/action-helpers.ts b/client/src/store/action-helpers.ts new file mode 100644 index 0000000..047856a --- /dev/null +++ b/client/src/store/action-helpers.ts @@ -0,0 +1,9 @@ +import { createAction } from '@reduxjs/toolkit'; + +function actionWithPayload() { + return (payload?: T) => ({ payload }); +} + +export const createStandardAction = (action: string) => createAction(action, actionWithPayload()); + +// TODO createStandardAction와 같이 createThunkAction 용으로 createAsyncAction helper 생성 필요 (thunkApi를 이용하려면 generic을 받아야함) diff --git a/client/src/store/actions.test.js b/client/src/store/actions.test.ts similarity index 100% rename from client/src/store/actions.test.js rename to client/src/store/actions.test.ts diff --git a/client/src/store/actions.js b/client/src/store/actions.ts similarity index 100% rename from client/src/store/actions.js rename to client/src/store/actions.ts diff --git a/client/src/store/index.js b/client/src/store/index.ts similarity index 55% rename from client/src/store/index.js rename to client/src/store/index.ts index 3f56345..39ebe8c 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.ts @@ -1,7 +1,7 @@ -import { combineReducers } from '@reduxjs/toolkit'; +import { combineReducers, Reducer } from '@reduxjs/toolkit'; import reduxSampleReducer from './ReduxSample/ReduxSample.reducers'; -const rootReducer = combineReducers({ +const rootReducer: Reducer = combineReducers({ sample: reduxSampleReducer, }); diff --git a/client/src/store/store.types.ts b/client/src/store/store.types.ts new file mode 100644 index 0000000..0bcacef --- /dev/null +++ b/client/src/store/store.types.ts @@ -0,0 +1,3 @@ +import rootReducer from './index'; + +export type AppState = ReturnType; diff --git a/client/src/styleguide/components/StoreProvider.jsx b/client/src/styleguide/components/StoreProvider.tsx similarity index 100% rename from client/src/styleguide/components/StoreProvider.jsx rename to client/src/styleguide/components/StoreProvider.tsx diff --git a/client/src/styleguide/components/StyleContainer.jsx b/client/src/styleguide/components/StyleContainer.jsx deleted file mode 100644 index c83787c..0000000 --- a/client/src/styleguide/components/StyleContainer.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import classNames from 'classnames'; -import './StyleContainer.scss'; -import { DIRECTION } from '../../types/constants'; -import * as type from '../../types'; - -const StyleContainer = ({ direction = 'horizontal', posX = 'left', posY = 'top', ...rest }) => ( -
-); - -StyleContainer.propTypes = { - direction: type.Direction, - posX: type.Position.posX, - posY: type.Position.posY, -}; - -export default StyleContainer; diff --git a/client/src/styleguide/components/StyleContainer.tsx b/client/src/styleguide/components/StyleContainer.tsx new file mode 100644 index 0000000..06dd04d --- /dev/null +++ b/client/src/styleguide/components/StyleContainer.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import classNames from 'classnames'; +import './StyleContainer.scss'; +import { DirectionType, PosXType, PosYType } from '../../types/ui.types'; + +type AttributeProps = React.HTMLAttributes; + +type Props = AttributeProps & { + direction: DirectionType; + posX: PosXType; + posY: PosYType; +}; + +const StyleContainer: React.FC = ({ direction = 'horizontal', posX = 'left', posY = 'top', ...rest }) => ( +
+); + +export default StyleContainer; diff --git a/client/src/types/common.types.js b/client/src/types/common.types.ts similarity index 100% rename from client/src/types/common.types.js rename to client/src/types/common.types.ts diff --git a/client/src/types/constants.js b/client/src/types/constants.js deleted file mode 100644 index 54483d1..0000000 --- a/client/src/types/constants.js +++ /dev/null @@ -1,18 +0,0 @@ -// enums - -export const DIRECTION = { - HORIZONTAL: 'horizontal', - VERTICAL: 'vertical', -}; - -export const POSX = { - LEFT: 'left', - RIGHT: 'right', - CENTER: 'center', -}; - -export const POSY = { - TOP: 'top', - BOTTOM: 'bottom', - CENTER: 'center', -}; diff --git a/client/src/types/enums.ts b/client/src/types/enums.ts new file mode 100644 index 0000000..974855c --- /dev/null +++ b/client/src/types/enums.ts @@ -0,0 +1,21 @@ +// enums + +export enum Direction { + HORIZONTAL = 'horizontal', + VERTICAL = 'vertical', +} + +export enum PosX { + LEFT = 'left', + CENTER = 'center', + RIGHT = 'right', + TOP = 'top', + BOTTOM = 'bottom', + CENTR = 'center', +} + +export enum PosY { + TOP = 'top', + BOTTOM = 'bottom', + CENTER = 'center', +} diff --git a/client/src/types/index.js b/client/src/types/index.ts similarity index 100% rename from client/src/types/index.js rename to client/src/types/index.ts diff --git a/client/src/types/ui.types.js b/client/src/types/ui.types.js deleted file mode 100644 index 6d718b3..0000000 --- a/client/src/types/ui.types.js +++ /dev/null @@ -1,9 +0,0 @@ -import { oneOf } from 'prop-types'; -import { DIRECTION, POSX, POSY } from './constants'; - -export const Direction = oneOf([DIRECTION.HORIZONTAL, DIRECTION.VERTICAL]); - -export const Position = { - posX: oneOf([POSX.LEFT, POSX.RIGHT, POSX.CENTER]), - posY: oneOf([POSY.TOP, POSY.BOTTOM, POSY.CENTER]), -}; diff --git a/client/src/types/ui.types.ts b/client/src/types/ui.types.ts new file mode 100644 index 0000000..be3dfef --- /dev/null +++ b/client/src/types/ui.types.ts @@ -0,0 +1,5 @@ +import { Direction, PosY, PosX } from './enums'; + +export type DirectionType = keyof Direction; +export type PosXType = keyof PosX; +export type PosYType = keyof PosY; diff --git a/client/styleguide.config.js b/client/styleguide.config.js index d06af26..0ad74f5 100644 --- a/client/styleguide.config.js +++ b/client/styleguide.config.js @@ -1,6 +1,10 @@ +const jsParser = require('react-docgen'); +const tsParser = require('react-docgen-typescript'); + module.exports = { title: 'd.log UI guide', - components: 'src/components/**/*.jsx', + components: 'src/components/**/*.tsx', + skipComponentsWithoutExample: true, pagePerSection: true, sections: [ { @@ -9,18 +13,28 @@ module.exports = { }, { name: 'Components', - components: 'src/components/**/*.jsx', - ignore: 'src/components/Sample/*.jsx', + components: 'src/components/**/*.tsx', + ignore: 'src/components/Sample/*.tsx', exampleMode: 'expand', usageMode: 'expand', sectionDepth: 0, }, { name: 'Sample', - components: 'src/components/Sample/*.jsx', + components: 'src/components/Sample/*.tsx', exampleMode: 'expand', usageMode: 'expand', sectionDepth: 0, }, ], + resolver: jsParser.resolver.findAllComponentDefinitions, + propsParser: tsParser.withCustomConfig('./tsconfig.json', { + propFilter(prop) { + if (prop.parent) { + return !prop.parent.fileName.includes('node_modules'); + } + + return true; + }, + }).parse, }; diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..48b672c --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src"], + "exclude": ["./node_modules", "./public", "./build"] +} diff --git a/client/yarn.lock b/client/yarn.lock index 39dcd1e..8393951 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1492,6 +1492,11 @@ dependencies: "@babel/types" "^7.3.0" +"@types/classnames@^2.2.10": + version "2.2.10" + resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" + integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -1516,6 +1521,14 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -1536,6 +1549,14 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/jest@^25.2.3": + version "25.2.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" + integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + "@types/json-schema@^7.0.3": version "7.0.4" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" @@ -1551,6 +1572,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c" integrity sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA== +"@types/node@^14.0.12": + version "14.0.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.12.tgz#9c1d8ffb8084e8936603a6122a7649e40e68e04b" + integrity sha512-/sjzehvjkkpvLpYtN6/2dv5kg41otMGuHQUt9T2aiAuIfleCQRQHXXzF1eAw/qkZTj5Kcf4JSTf7EIizHocy6Q== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -1573,6 +1599,23 @@ dependencies: "@types/react" "*" +"@types/react-dom@^16.9.8": + version "16.9.8" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" + integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.9": + version "7.1.9" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.9.tgz#280c13565c9f13ceb727ec21e767abe0e9b4aec3" + integrity sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react@*": version "16.9.34" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.34.tgz#f7d5e331c468f53affed17a8a4d488cd44ea9349" @@ -1581,6 +1624,14 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/react@^16.9.35": + version "16.9.35" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.35.tgz#a0830d172e8aadd9bd41709ba2281a3124bbd368" + integrity sha512-q0n0SsWcGc8nDqH2GJfWQWUOmZSJhXV64CjVN5SvcNti3TdEaA3AH0D8DwNmMdzjMAC/78tB8nAZIlV8yTz+zQ== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -1631,6 +1682,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@types/webpack-env@^1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.15.2.tgz#927997342bb9f4a5185a86e6579a0a18afc33b0a" + integrity sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ== + "@types/webpack-sources@*": version "0.1.7" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.7.tgz#0a330a9456113410c74a5d64180af0cbca007141" @@ -4119,6 +4175,11 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff-sequences@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" + integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -6697,6 +6758,16 @@ jest-diff@^24.0.0, jest-diff@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-diff@^25.2.1: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" + integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== + dependencies: + chalk "^3.0.0" + diff-sequences "^25.2.6" + jest-get-type "^25.2.6" + pretty-format "^25.5.0" + jest-docblock@^24.3.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" @@ -6755,6 +6826,11 @@ jest-get-type@^24.9.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== +jest-get-type@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" + integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== + jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -9649,7 +9725,7 @@ pretty-format@^24.0.0, pretty-format@^24.3.0, pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" -pretty-format@^25.1.0: +pretty-format@^25.1.0, pretty-format@^25.2.1, pretty-format@^25.5.0: version "25.5.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== @@ -10290,7 +10366,12 @@ react-docgen-displayname-handler@^3.0.0: dependencies: ast-types "0.13.3" -react-docgen@^5.0.0: +react-docgen-typescript@^1.16.6: + version "1.16.6" + resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-1.16.6.tgz#334c1b346cdaba9a347c7395fa4fc37a3a3292b5" + integrity sha512-Nt+Oireji1q/6ZV7LAZHLJfd86gpYSiuX519MTgnPbyXsTTK5iCYm5SZz1bkD42xTK7nXZen2UicY2xER3J3Nw== + +react-docgen@^5.0.0, react-docgen@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-5.3.0.tgz#9aabde5e69f1993c8ba839fd9a86696504654589" integrity sha512-hUrv69k6nxazOuOmdGeOpC/ldiKy7Qj/UFpxaQi0eDMrUFUTIPGtY5HJu7BggSmiyAMfREaESbtBL9UzdQ+hyg== @@ -12354,6 +12435,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^3.9.5: + version "3.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" + integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== + unherit@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" From 012873a13bf323a68206b83d1ea44f1154350352 Mon Sep 17 00:00:00 2001 From: r0o0 Date: Sat, 27 Jun 2020 00:17:33 +0900 Subject: [PATCH 2/2] Fix app crash --- client/.eslintignore | 2 + client/.eslintrc.json | 62 ++++++--- client/package.json | 7 +- client/src/components/Button/Button.types.ts | 2 + .../components/Sample/ReduxSample.types.ts | 4 +- client/src/components/index.ts | 1 + client/src/configureStore.ts | 27 ---- client/src/index.tsx | 2 +- .../{serviceWorker.ts => serviceWorker.js} | 29 ++--- .../store/ReduxSample/ReduxSample.actions.ts | 2 +- .../store/ReduxSample/ReduxSample.reducers.ts | 9 +- .../store/ReduxSample/ReduxSample.types.ts | 4 +- client/src/store/action-helpers.ts | 2 +- client/src/store/index.ts | 29 ++++- client/src/store/reducers.ts | 8 ++ client/src/store/store.types.ts | 6 +- .../styleguide/components/StoreProvider.tsx | 4 +- client/src/types/common.types.ts | 0 client/tsconfig.json | 2 + client/yarn.lock | 123 +++++++++++++++++- 20 files changed, 236 insertions(+), 89 deletions(-) delete mode 100644 client/src/configureStore.ts rename client/src/{serviceWorker.ts => serviceWorker.js} (89%) create mode 100644 client/src/store/reducers.ts delete mode 100644 client/src/types/common.types.ts diff --git a/client/.eslintignore b/client/.eslintignore index a9529ad..376a2b0 100644 --- a/client/.eslintignore +++ b/client/.eslintignore @@ -2,3 +2,5 @@ src/registerServiceWorker.js # ignore test files src/**/*.test.js +config-overrides.js +styleguide.config.js diff --git a/client/.eslintrc.json b/client/.eslintrc.json index 500e13b..bd10ac5 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -1,27 +1,59 @@ { - "extends": ["airbnb", "prettier/react", "plugin:react/recommended"], - "plugins": ["react", "prettier"], + "root": true, + "env": { + "browser": true, + "jest/globals": true + }, + "extends": [ + "airbnb", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended", + "prettier/@typescript-eslint" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json", + "sourceType": "module" + }, + "plugins": ["@typescript-eslint", "jest"], "rules": { - "prettier/prettier": ["error"], + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "never", + "jsx": "never", + "ts": "never", + "tsx": "never" + } + ], + "import/prefer-default-export": "off", + "import/no-extraneous-dependencies": "off", + "global-require": "off", + "no-param-reassign": [2, { "props": false }], + "spaced-comment": ["error", "always", { "markers": ["/"] }], "consistent-return": "off", - "import/no-unresolved": "off", - "import/prefer-default-export": "off", - "no-param-reassign": "off", - "operator-linebreak": "off", - "object-curly-newline": "off", + "react/prop-types": "off", + "react/jsx-filename-extension": [1, { "extensions": [".jsx", ".tsx"] }], "react/jsx-props-no-spreading": "off", - "react/require-default-props": "off" - }, - "parser": "babel-eslint", - "env": { - "browser": true, - "jest": true + "react/button-has-type": "off", + + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" }, "settings": { + "import/extensions": [".js", ".jsx", ".ts", ".tsx"], + "import/parsers": { + "@typescript-eslint/parser": [".ts", ".tsx"] + }, + "import/resolver": { + "node": { + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + }, "react": { - "pragma": "React", "version": "detect" } } diff --git a/client/package.json b/client/package.json index 8aad72f..186b20a 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ "@types/react": "^16.9.35", "@types/react-dom": "^16.9.8", "@types/react-redux": "^7.1.9", + "@types/react-router-dom": "^5.1.5", "@types/webpack-env": "^1.15.2", "antd": "^4.2.2", "axios": "^0.19.2", @@ -37,7 +38,7 @@ "test": "react-app-rewired test --watchAll=false --coverage --detectOpenHandles --forceExit", "styleguide": "styleguidist server --config styleguide.config.js", "styleguide:build": "styleguidist build --config styleguide.config.js", - "lint:fix": "eslint './src/**/*.{js, jsx}'" + "lint:fix": "eslint \"src/**/*.ts*\" --fix" }, "engines": { "node": ">= 12.0.0", @@ -60,6 +61,8 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", + "@typescript-eslint/eslint-plugin": "^3.3.0", + "@typescript-eslint/parser": "^3.3.0", "babel-eslint": "^10.1.0", "babel-plugin-import": "^1.13.0", "eslint": "^6.8.0", @@ -67,9 +70,9 @@ "eslint-config-babel": "^9.0.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-import": "^2.20.2", + "eslint-plugin-jest": "^23.16.0", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-react": "^7.19.0", - "eslint-plugin-standard": "^4.0.1", "husky": "^4.2.5", "prettier": "^2.0.5", "react-styleguidist": "^11.0.5", diff --git a/client/src/components/Button/Button.types.ts b/client/src/components/Button/Button.types.ts index 96c11fa..5b79414 100644 --- a/client/src/components/Button/Button.types.ts +++ b/client/src/components/Button/Button.types.ts @@ -1,3 +1,5 @@ +import React from 'react'; + type AttributeProps = React.ButtonHTMLAttributes; type CustomProps = { diff --git a/client/src/components/Sample/ReduxSample.types.ts b/client/src/components/Sample/ReduxSample.types.ts index f6ff065..f3e8cef 100644 --- a/client/src/components/Sample/ReduxSample.types.ts +++ b/client/src/components/Sample/ReduxSample.types.ts @@ -1,7 +1,7 @@ export interface UserSampleState { id: number; email: string; - first_name: string; - last_name: string; + firstName: string; + lastName: string; avatar: string; } diff --git a/client/src/components/index.ts b/client/src/components/index.ts index 239222a..513da76 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -1 +1,2 @@ +/* eslint-disable import/extensions */ export { default as Button } from './Button/Button'; diff --git a/client/src/configureStore.ts b/client/src/configureStore.ts deleted file mode 100644 index a41d214..0000000 --- a/client/src/configureStore.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'; -import rootReducer from './store'; -import loggerMiddleware from './middlewares/logger'; -import monitorReducerEnhancer from './enhancers/monitorReducers'; -import { AppState } from './store/store.types'; - -export type AppDispatch = ReturnType['dispatch']; - -const configureAppStore = (preloadedState = {}) => { - const store = configureStore({ - reducer: rootReducer, - preloadedState, - middleware: [loggerMiddleware, ...getDefaultMiddleware()], - enhancers: [monitorReducerEnhancer], - }); - - if (process.env.NODE_ENV === 'development' && module.hot) { - module.hot.accept('./store', () => { - const newRootReducer = require('./store').default; - store.replaceReducer(newRootReducer); - }); - } - - return store; -}; - -export default configureAppStore; diff --git a/client/src/index.tsx b/client/src/index.tsx index 286eeee..ab583cb 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -4,7 +4,7 @@ import { Provider } from 'react-redux'; import App from './pages/App'; import './index.css'; import * as serviceWorker from './serviceWorker'; -import configureStore from './configureStore'; +import { configureAppStore as configureStore } from './store/index'; const store = configureStore(); diff --git a/client/src/serviceWorker.ts b/client/src/serviceWorker.js similarity index 89% rename from client/src/serviceWorker.ts rename to client/src/serviceWorker.js index b04b771..af47ec3 100644 --- a/client/src/serviceWorker.ts +++ b/client/src/serviceWorker.js @@ -15,9 +15,7 @@ const isLocalhost = Boolean( // [::1] is the IPv6 localhost address. window.location.hostname === '[::1]' || // 127.0.0.0/8 are considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/), ); export function register(config) { @@ -43,7 +41,7 @@ export function register(config) { navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' + 'worker. To learn more, visit https://bit.ly/CRA-PWA', ); }); } else { @@ -57,7 +55,7 @@ export function register(config) { function registerValidSW(swUrl, config) { navigator.serviceWorker .register(swUrl) - .then(registration => { + .then((registration) => { registration.onupdatefound = () => { const installingWorker = registration.installing; if (installingWorker == null) { @@ -71,7 +69,7 @@ function registerValidSW(swUrl, config) { // content until all client tabs are closed. console.log( 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.', ); // Execute callback @@ -93,7 +91,7 @@ function registerValidSW(swUrl, config) { }; }; }) - .catch(error => { + .catch((error) => { console.error('Error during service worker registration:', error); }); } @@ -103,15 +101,12 @@ function checkValidServiceWorker(swUrl, config) { fetch(swUrl, { headers: { 'Service-Worker': 'script' }, }) - .then(response => { + .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { + if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) { // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { + navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { window.location.reload(); }); @@ -122,19 +117,17 @@ function checkValidServiceWorker(swUrl, config) { } }) .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); + console.log('No internet connection found. App is running in offline mode.'); }); } export function unregister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.ready - .then(registration => { + .then((registration) => { registration.unregister(); }) - .catch(error => { + .catch((error) => { console.error(error.message); }); } diff --git a/client/src/store/ReduxSample/ReduxSample.actions.ts b/client/src/store/ReduxSample/ReduxSample.actions.ts index 307c69e..2263369 100644 --- a/client/src/store/ReduxSample/ReduxSample.actions.ts +++ b/client/src/store/ReduxSample/ReduxSample.actions.ts @@ -3,7 +3,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { createStandardAction } from '../action-helpers'; import { UserSampleState } from './ReduxSample.types'; import { AppState } from '../store.types'; -import { AppDispatch } from '../../configureStore'; +import { AppDispatch } from '../index'; interface ThunkAPI { state: AppState; diff --git a/client/src/store/ReduxSample/ReduxSample.reducers.ts b/client/src/store/ReduxSample/ReduxSample.reducers.ts index 99f398f..0d5d4ff 100644 --- a/client/src/store/ReduxSample/ReduxSample.reducers.ts +++ b/client/src/store/ReduxSample/ReduxSample.reducers.ts @@ -1,5 +1,5 @@ import { createReducer } from '@reduxjs/toolkit'; -import ReduxSampleActions, { GET_SAMPLE_USERS, TRIGGER_BTN_CLICK, RESET } from './ReduxSample.actions'; +import { ReduxSampleActions } from '../actions'; import { ReduxSampleState } from './ReduxSample.types'; export const initialState: ReduxSampleState = { @@ -14,17 +14,16 @@ export const initialState: ReduxSampleState = { // TODO createReducer 타입 추가 const reduxSampleReducer = createReducer(initialState, { // TODO createAsyncAction helper 생성 후 fulfilled 상태 타입 확인 - [ReduxSampleActions.getSampleUsers.fulfilled]: (state, { payload: userData }) => { - console.log(userData); + [ReduxSampleActions.getSampleUsers.fulfilled.type]: (state: ReduxSampleState, { payload: userData }) => { state.sampleUsers = userData; }, - [TRIGGER_BTN_CLICK]: (state, action) => { + [ReduxSampleActions.triggerBtnClick.type]: (state: ReduxSampleState, action) => { const { message } = action.payload; state.sampleButton.message = message; state.sampleButton.loading = true; }, - [RESET]: () => initialState, + [ReduxSampleActions.reset.type]: () => initialState, }); export default reduxSampleReducer; diff --git a/client/src/store/ReduxSample/ReduxSample.types.ts b/client/src/store/ReduxSample/ReduxSample.types.ts index 959c719..d72bdcb 100644 --- a/client/src/store/ReduxSample/ReduxSample.types.ts +++ b/client/src/store/ReduxSample/ReduxSample.types.ts @@ -1,8 +1,8 @@ export interface UserSampleState { id: number; email: string; - first_name: string; - last_name: string; + firstName: string; + lastName: string; avatar: string; } diff --git a/client/src/store/action-helpers.ts b/client/src/store/action-helpers.ts index 047856a..c374e0f 100644 --- a/client/src/store/action-helpers.ts +++ b/client/src/store/action-helpers.ts @@ -1,7 +1,7 @@ import { createAction } from '@reduxjs/toolkit'; function actionWithPayload() { - return (payload?: T) => ({ payload }); + return (payload?: T): { payload: T | undefined } => ({ payload }); } export const createStandardAction = (action: string) => createAction(action, actionWithPayload()); diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 39ebe8c..a645aaf 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -1,8 +1,25 @@ -import { combineReducers, Reducer } from '@reduxjs/toolkit'; -import reduxSampleReducer from './ReduxSample/ReduxSample.reducers'; +import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'; +import loggerMiddleware from '../middlewares/logger'; +import monitorReducerEnhancer from '../enhancers/monitorReducers'; +import { AppState } from './store.types'; +import rootReducer from './reducers'; -const rootReducer: Reducer = combineReducers({ - sample: reduxSampleReducer, -}); +export const configureAppStore = (preloadedState = {}) => { + const store = configureStore({ + reducer: rootReducer, + preloadedState, + middleware: [loggerMiddleware, ...getDefaultMiddleware()], + enhancers: [monitorReducerEnhancer], + }); -export default rootReducer; + if (process.env.NODE_ENV === 'development' && module.hot) { + module.hot.accept('./reducers.ts', () => { + const newRootReducer = require('./reducers.ts').default; + store.replaceReducer(newRootReducer); + }); + } + + return store; +}; + +export type AppDispatch = ReturnType['dispatch']; diff --git a/client/src/store/reducers.ts b/client/src/store/reducers.ts new file mode 100644 index 0000000..39ebe8c --- /dev/null +++ b/client/src/store/reducers.ts @@ -0,0 +1,8 @@ +import { combineReducers, Reducer } from '@reduxjs/toolkit'; +import reduxSampleReducer from './ReduxSample/ReduxSample.reducers'; + +const rootReducer: Reducer = combineReducers({ + sample: reduxSampleReducer, +}); + +export default rootReducer; diff --git a/client/src/store/store.types.ts b/client/src/store/store.types.ts index 0bcacef..0ccd789 100644 --- a/client/src/store/store.types.ts +++ b/client/src/store/store.types.ts @@ -1,3 +1,5 @@ -import rootReducer from './index'; +import { ReduxSampleState } from './ReduxSample/ReduxSample.types'; -export type AppState = ReturnType; +export interface AppState { + sample: ReduxSampleState; +} diff --git a/client/src/styleguide/components/StoreProvider.tsx b/client/src/styleguide/components/StoreProvider.tsx index ce1abfd..98d8ea4 100644 --- a/client/src/styleguide/components/StoreProvider.tsx +++ b/client/src/styleguide/components/StoreProvider.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Provider } from 'react-redux'; -import configureStore from '../../configureStore'; +import { configureAppStore as configureStore } from '../../store'; const store = configureStore(); -const StoreProvider = (props) => ; +const StoreProvider: React.FC = ({ ...rest }) => ; export default StoreProvider; diff --git a/client/src/types/common.types.ts b/client/src/types/common.types.ts deleted file mode 100644 index e69de29..0000000 diff --git a/client/tsconfig.json b/client/tsconfig.json index 48b672c..1b632e8 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -13,8 +13,10 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, + "jsx": "react" }, + "interface-name": [true, "always-prefix"], "include": ["src"], "exclude": ["./node_modules", "./public", "./build"] } diff --git a/client/yarn.lock b/client/yarn.lock index 8393951..ddce726 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1521,6 +1521,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/history@*": + version "4.7.6" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.6.tgz#ed8fc802c45b8e8f54419c2d054e55c9ea344356" + integrity sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w== + "@types/hoist-non-react-statics@^3.3.0": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" @@ -1616,6 +1621,23 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-router-dom@^5.1.5": + version "5.1.5" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090" + integrity sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.7" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.7.tgz#e9d12ed7dcfc79187e4d36667745b69a5aa11556" + integrity sha512-2ouP76VQafKjtuc0ShpwUebhHwJo0G6rhahW9Pb8au3tQTjYXd2jta4wv6U2tGLR/I42yuG00+UXjNYY0dTzbg== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react@*": version "16.9.34" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.34.tgz#f7d5e331c468f53affed17a8a4d488cd44ea9349" @@ -1737,6 +1759,17 @@ regexpp "^3.0.0" tsutils "^3.17.1" +"@typescript-eslint/eslint-plugin@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.3.0.tgz#89518e5c5209a349bde161c3489b0ec187ae5d37" + integrity sha512-Ybx/wU75Tazz6nU2d7nN6ll0B98odoiYLXwcuwS5WSttGzK46t0n7TPRQ4ozwcTv82UY6TQoIvI+sJfTzqK9dQ== + dependencies: + "@typescript-eslint/experimental-utils" "3.3.0" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + "@typescript-eslint/experimental-utils@2.30.0": version "2.30.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.30.0.tgz#9845e868c01f3aed66472c561d4b6bac44809dd0" @@ -1747,6 +1780,26 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" +"@typescript-eslint/experimental-utils@3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.3.0.tgz#d72a946e056a83d4edf97f3411cceb639b0b8c87" + integrity sha512-d4pGIAbu/tYsrPrdHCQ5xfadJGvlkUxbeBB56nO/VGmEDi/sKmfa5fGty5t5veL1OyJBrUmSiRn1R1qfVDydrg== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "3.3.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/experimental-utils@^2.5.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" + integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + "@typescript-eslint/parser@^2.10.0": version "2.30.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.30.0.tgz#7681c305a6f4341ae2579f5e3a75846c29eee9ce" @@ -1757,6 +1810,16 @@ "@typescript-eslint/typescript-estree" "2.30.0" eslint-visitor-keys "^1.1.0" +"@typescript-eslint/parser@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.3.0.tgz#fcae40012ded822aa8b2739a1a03a4e3c5bbb7bb" + integrity sha512-a7S0Sqn/+RpOOWTcaLw6RD4obsharzxmgMfdK24l364VxuBODXjuJM7ImCkSXEN7oz52aiZbXSbc76+2EsE91w== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "3.3.0" + "@typescript-eslint/typescript-estree" "3.3.0" + eslint-visitor-keys "^1.1.0" + "@typescript-eslint/typescript-estree@2.30.0": version "2.30.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.30.0.tgz#1b8e848b55144270255ffbfe4c63291f8f766615" @@ -1770,6 +1833,32 @@ semver "^6.3.0" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" + integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/typescript-estree@3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.3.0.tgz#841ffed25c29b0049ebffb4c2071268a34558a2a" + integrity sha512-3SqxylENltEvJsjjMSDCUx/edZNSC7wAqifUU1Ywp//0OWEZwMZJfecJud9XxJ/40rAKEbJMKBOQzeOjrLJFzQ== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + "@vxna/mini-html-webpack-template@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@vxna/mini-html-webpack-template/-/mini-html-webpack-template-1.0.0.tgz#66ce883b6e6678e1242ab51da04be21d3f1001bc" @@ -4658,6 +4747,13 @@ eslint-plugin-import@^2.20.2: read-pkg-up "^2.0.0" resolve "^1.12.0" +eslint-plugin-jest@^23.16.0: + version "23.16.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.16.0.tgz#fada00f765b5e32a1fb10a7490f75ebf673133b3" + integrity sha512-51KcQup31S2NBm+Yqg3rxZIPETd+wZ/gU2rb034RpdXLcZYsa2+uwubqbbDAYIpQw3m+wywF/A56OMEouBY/wA== + dependencies: + "@typescript-eslint/experimental-utils" "^2.5.0" + eslint-plugin-jsx-a11y@6.2.3: version "6.2.3" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa" @@ -4685,7 +4781,7 @@ eslint-plugin-react-hooks@^1.6.1: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" integrity sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA== -eslint-plugin-react@7.19.0, eslint-plugin-react@^7.19.0: +eslint-plugin-react@7.19.0: version "7.19.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666" integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ== @@ -4703,10 +4799,22 @@ eslint-plugin-react@7.19.0, eslint-plugin-react@^7.19.0: string.prototype.matchall "^4.0.2" xregexp "^4.3.0" -eslint-plugin-standard@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" - integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== +eslint-plugin-react@^7.19.0: + version "7.20.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.0.tgz#f98712f0a5e57dfd3e5542ef0604b8739cd47be3" + integrity sha512-rqe1abd0vxMjmbPngo4NaYxTcR3Y4Hrmc/jg4T+sYz63yqlmJRknpEQfmWY+eDWPuMmix6iUIK+mv0zExjeLgA== + dependencies: + array-includes "^3.1.1" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.2.3" + object.entries "^1.1.1" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.15.1" + string.prototype.matchall "^4.0.2" + xregexp "^4.3.0" eslint-scope@^4.0.3: version "4.0.3" @@ -11318,6 +11426,11 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"