Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure TypeScript (DL-17) #53

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
15 changes: 0 additions & 15 deletions client/src/components/Button/Button.jsx

This file was deleted.

2 changes: 1 addition & 1 deletion client/src/components/Button/Button.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Button example:

```js
<Button text="Button" />
<Button>Button Example</Button>
```
11 changes: 11 additions & 0 deletions client/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { Props } from './Button.types';

// TODO 버튼 컴포넌트 작성
const Button: React.FC<Props> = ({ children, type = 'button', ...rest }) => (
<button {...rest} type={type}>
{children}
</button>
);

export default Button;
7 changes: 7 additions & 0 deletions client/src/components/Button/Button.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type AttributeProps = React.ButtonHTMLAttributes<HTMLButtonElement>;

type CustomProps = {
theme?: 'primary' | 'secondary';
};

export type Props = AttributeProps & CustomProps;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interface 대신 type 키워드로 컴포넌트 prop 스펙을 정의해도 상관없긴 한데,
확장성 같은 몇가지 기능 차이가 있어욤 ㅎㅎ

차후 다른 형태의 버튼 인터페이스가 필요하다면
기존 인터페이스를 extends 해서 확장 가능해요 type 은 안되구요ㅠㅠ

그래서 아래와 같이 쓰는건 어떨까요 ? :))

export enum THEME_VALUE {
  PRIMARY ='primary',
  SECONDARY= 'secondary'
}

export interface IButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  theme?: THEME_VALUE
}

Copy link
Collaborator Author

@r0o0 r0o0 Jun 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래 문서 보고 정한것인데 컴포넌트 내에서 사용하는 Props나 State는 type으로 정의하고 API 관련된 타입들은 interface를 사용하고자 합니다. 타입스크립트에서도 권장하는 ruleset에서 interface-over-type-literal을 제거 했더라구요.

4 changes: 2 additions & 2 deletions client/src/components/Sample/ReduxSample.md
Original file line number Diff line number Diff line change
@@ -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';

<StoreProvider>
<StyleContainer style={{ height: 300 }} direction="vertical">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,38 @@ 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());

return (
<>
<div style={{ display: 'flex' }}>
<Button type="button" onClick={handleClick} text={message} />
<Button type="button" onClick={handleReset} text="Reset" />
<Button type="button" onClick={handleClick}>
{message}
</Button>
<Button type="button" onClick={handleReset}>
Reset
</Button>
</div>
{sampleUsers.length > 0 && (
<ul>
{sampleUsers.map((user) => (
{sampleUsers.map((user: UserSampleState) => (
<li key={user.id}>{user.email}</li>
))}
</ul>
Expand Down
7 changes: 7 additions & 0 deletions client/src/components/Sample/ReduxSample.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface UserSampleState {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터페이스 정의할 때는 앞에 I 붙여 주는게 좋아욤 ㅎㅎ
export interface IUserSampleState { ...

tslint 로 자동으로 체크해 줄 수도 있구요
https://palantir.github.io/tslint/rules/interface-name/

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 naming convention이라 정하기 나름일것 같은데 I prefix로 interface라는걸 명시해 주는 이유가 있을까요? I prefix가 모든 interface에 들어간다면 I 로 시작하는 interface 명이나 interface 명이 앞에있는 I로 인해 잘 안보일까 걱정이네요

id: number;
email: string;
first_name: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필드이름은 camel case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 제가 잘 몰라서 그러는데 데이터가 first_name으로 들어와도 camelCase로 쓰는건가요?

last_name: string;
avatar: string;
}
File renamed without changes.
12 changes: 8 additions & 4 deletions client/src/configureStore.js → client/src/configureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof configureAppStore>['dispatch'];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export type TAppDispatch = ... 처럼 type 은 T prefix~

https://luckyyowu.tistory.com/401


const configureAppStore = (preloadedState = {}) => {
const store = configureStore({
reducer: rootReducer,
preloadedState,
middleware: [loggerMiddleware, ...getDefaultMiddleware()],
middleware: [loggerMiddleware, ...getDefaultMiddleware<AppState>()],
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;
File renamed without changes.
File renamed without changes.
13 changes: 0 additions & 13 deletions client/src/pages/Home/Home.jsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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';

Expand Down
15 changes: 15 additions & 0 deletions client/src/pages/Home/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

import './Home.css';

function Home() {
return (
<div className="home">
<header className="home__header">
<h1>d.log</h1>
</header>
</div>
);
}

export default Home;
4 changes: 2 additions & 2 deletions client/src/pages/Router.jsx → client/src/pages/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ export const PAGE_URL = {

const ROUTES = [{ path: PAGE_URL.HOME, exact: true, component: Home }];

const Router = () => (
const Router: React.FC = () => (
<BrowserRouter>
<Switch>
<Redirect exact path={PAGE_URL.ROOT} to={PAGE_URL.HOME} />
{ROUTES.map(route => (
{ROUTES.map((route) => (
<Route {...route} key={route.path} />
))}
</Switch>
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions client/src/react-app-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="react-scripts" />
File renamed without changes.
File renamed without changes.
21 changes: 0 additions & 21 deletions client/src/store/ReduxSample/ReduxSample.actions.js

This file was deleted.

32 changes: 32 additions & 0 deletions client/src/store/ReduxSample/ReduxSample.actions.ts
Original file line number Diff line number Diff line change
@@ -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<UserSampleState[], void, ThunkAPI>(GET_SAMPLE_USERS, fetchSampleUsers),
triggerBtnClick: createStandardAction<{ message: string }>(TRIGGER_BTN_CLICK),
reset: createStandardAction(RESET),
};

export default ReduxSampleActions;
Original file line number Diff line number Diff line change
@@ -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 }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ReduxSampleActions.getSampleUsers.fulfilled.type]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 type을 넣으면 됐었네요! ㅎㅎ

console.log(userData);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.log 빼주세욤 :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 넵ㅎㅎ

state.sampleUsers = userData;
},
[ReduxSampleActions.triggerBtnClick]: (state, action) => {
[TRIGGER_BTN_CLICK]: (state, action) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ReduxSampleActions.triggerBtnClick.type]:
...
[ReduxSampleActions.reset.type]: 

이렇게 써도 될 것 같아욤ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵~!

const { message } = action.payload;

state.sampleButton.message = message;
state.sampleButton.loading = true;
},
[ReduxSampleActions.reset]: () => initialState,
[RESET]: () => initialState,
});

export default reduxSampleReducer;
16 changes: 16 additions & 0 deletions client/src/store/ReduxSample/ReduxSample.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface UserSampleState {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IUserSampleState :))

id: number;
email: string;
first_name: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 camel case :))

last_name: string;
avatar: string;
}

export interface ReduxSampleState {
sampleUsers: UserSampleState[];
sampleButton: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISampleButton 도 따로 정의해주세욤

export interface ISampleButton {
  message: string;
  isClicked: boolean;
  loading: boolean;
}

message: string;
isClicked: boolean;
loading: boolean;
};
}
9 changes: 9 additions & 0 deletions client/src/store/action-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createAction } from '@reduxjs/toolkit';

function actionWithPayload<T>() {
return (payload?: T) => ({ payload });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

payload 가 옵셔널이라, undefined 인 경우 처리도 필요할것 같아욤 !!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function actionWithPayload<T>() {
  return (payload?: T): { payload: T | undefined } => ({ payload });
}

이렇게 처리했는데 말씀하시는게 맞는지 모르겠네용 ㅎㅎ

}

export const createStandardAction = <T>(action: string) => createAction(action, actionWithPayload<T>());

// TODO createStandardAction와 같이 createThunkAction 용으로 createAsyncAction helper 생성 필요 (thunkApi를 이용하려면 generic을 받아야함)
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions client/src/store/index.js → client/src/store/index.ts
Original file line number Diff line number Diff line change
@@ -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,
});

Expand Down
3 changes: 3 additions & 0 deletions client/src/store/store.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import rootReducer from './index';

export type AppState = ReturnType<typeof rootReducer>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TAppState 라고 명시해주는건 어떨까요?
AppState 라고만 하면 타입이라기보단 클래스명 같아서 헷갈리기도하고
가독성이 떨어질 것 같아서욤 :))

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약 T자로 시작해야하면 곤란하기도 하고 그렇게되면 가독성이 더 떨어질 우려가 있어 naming 앞에 prefix 붙이는건 가급적이면 안했으면 합니다. (이름 짓는게 제일 어려운것 같아요 ㅠㅠ). Redux에서 쓰는 state들의 interface 명들 엔딩으로 <InterfaceName>State들어가서 이들의 모음 state로 AppState라고 지었는데 명확하지 않다면 고민해본 이름이 GlobalState, RootState, StoreState 이 정도가 좋을것 같은데 의견 부탁드립니당 :) ㅎㅎ
클래스 명은 나중에 api 나오면 따로 클래스명 naming convention을 짓는게 좋을것 같아요. 예: PostsClass, MembersClass 이런식으로 하면 어떨까 생각해봤는데 어떤가요?

22 changes: 0 additions & 22 deletions client/src/styleguide/components/StyleContainer.jsx

This file was deleted.

23 changes: 23 additions & 0 deletions client/src/styleguide/components/StyleContainer.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>;

type Props = AttributeProps & {
direction: DirectionType;
posX: PosXType;
posY: PosYType;
};

const StyleContainer: React.FC<Props> = ({ direction = 'horizontal', posX = 'left', posY = 'top', ...rest }) => (
<div
{...rest}
className={classNames('style-container', `position-${posX}-${posY}`, {
column: direction === 'horizontal',
})}
/>
);

export default StyleContainer;
File renamed without changes.
Loading