TS-REDUX-TUTORIAL
npm install redux-thunk
// 또는 yarn add redux-thunk
npm install axios
* github로부터 내 정보 받아오기 (server로 사용할 것임)
https://api.github.com/users/(githun닉넴)
Instantly parse JSON in any language | quicktype
app.quicktype.io
JSON 포맷으로 만들어주는 사이트 ~
index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { applyMiddleware, createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';
import Thunk from 'redux-thunk';
const store = createStore(rootReducer, applyMiddleware(Thunk))
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
api/github.ts
import axios from "axios";
export const getUserProfile = async (username: string) => {
const response = await axios.get<GithubProfile>(`https://api.github.com/users/${username}`)
return response.data; // 데이터 값을 바로 반환
}
export interface GithubProfile {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
name: string;
company: string;
blog: string;
location: null;
email: null;
hireable: null;
bio: string;
twitter_username: null;
public_repos: number;
public_gists: number;
followers: number;
following: number;
created_at: Date;
updated_at: Date;
}
modules/github/actions.ts
import { AxiosError } from "axios";
import { GithubProfile } from "../../api/github";
import { createAsyncAction } from "typesafe-actions";
// const { createStandardAction } = deprecated;
export const GET_USER_PROFILE = 'github/GET_USER_PROFILE';
export const GET_USER_PROFILE_SUCCESS = 'github/GET_USER_PROFILE_SUCCESS';
export const GET_USER_PROFILE_ERROR = 'github/GET_USER_PROFILE_ERROR';
// 액션 생성함수
// export const getUserProfile = createStandardAction(GET_USER_PROFILE)();
// export const getUserProfileSuccess = createStandardAction(GET_USER_PROFILE_SUCCESS)<GithubProfile>();
// export const getUserProfileError = createStandardAction(GET_USER_PROFILE_ERROR)<AxiosError>();
export const getUserProfileAsync = createAsyncAction(
GET_USER_PROFILE,
GET_USER_PROFILE_SUCCESS,
GET_USER_PROFILE_ERROR
)<undefined, GithubProfile, AxiosError>();
modules/github/thunk.ts
import { getUserProfileAsync } from './actions';
import { GithubAction } from './types';
import { getUserProfile } from '../../api/github';
import { RootState } from '..';
import { ThunkAction } from 'redux-thunk';
// ThunkAction 제네릭
// 1. TReturnType: thunk 함수에서 반환하는 값의 타입을 설정
// 2. TState: 스토어의 상태에 대한 타입
// 3. TEXTRAThunkArg: redux-thunk 미들웨어의 Extran Argument의 타입을 지정
// 4. TBasicAction: dispatch 할 수 있는 액션들의 타입을 설정
export function getUserProfileThunk(username: string) : ThunkAction<void, RootState, null, GithubAction> {
return async dispatch => {
const { request, success, failure} = getUserProfileAsync;
dispatch(request());
try {
const userProfile = await getUserProfile(username)
dispatch(success(userProfile))
} catch(e:any) {
dispatch(failure(e))
}
};
}
modules/github/types.ts
import * as actions from './actions';
import { ActionType } from 'typesafe-actions'
import { GithubProfile } from '../../api/github';
export type GithubAction = ActionType<typeof actions>
export type GithubState = {
userProfile: {
loading: boolean;
error: Error | null;
data: GithubProfile | null;
}
}
modules/github/reducer.ts
import { action, createReducer } from "typesafe-actions";
import { GithubState, GithubAction } from "./types";
import { GET_USER_PROFILE, GET_USER_PROFILE_SUCCESS, GET_USER_PROFILE_ERROR } from "./actions";
// 초기값 설정
const initialState: GithubState = {
userProfile: {
loading: false,
error: null,
data: null
}
}
const github = createReducer<GithubState, GithubAction>(initialState, {
[GET_USER_PROFILE]: state => ({
...state,
userProfile: {
loading: true,
error: null,
data: null
}
}),
[GET_USER_PROFILE_SUCCESS]: (state, action) => ({
...state,
userProfile: {
loading: false,
error: null,
data: action.payload
}
}),
[GET_USER_PROFILE_ERROR]: (state, action) => ({
...state,
userProfile: {
loading: false,
error: action.payload,
data: null
}
})
})
export default github;
modules/github/index.ts
export { default } from './reducer';
export * from './actions'
export * from './thunk'
export * from './types'
modules/index.ts
import { combineReducers } from "redux";
import counter from "./counter"
import todos from "./todos"
import github from "./github"
const rootReducer = combineReducers({ counter, todos, github });
export default rootReducer;
// 나중에 이 타입을 컨테이너 컴포넌트에서 불러와서 사용해야하므로
// 내보내줍니다.
export type RootState = ReturnType<typeof rootReducer>
components/GithubusernameForm.tsx
import React, { ChangeEvent, FormEvent, useState } from 'react';
type GithubUsernameFormprops = {
onSubmitUsername: (username: string) => void
}
const GithubusernameForm = ({ onSubmitUsername }: GithubUsernameFormprops) => {
const [ input, setInput ] = useState('');
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmitUsername(input);
}
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value);
}
return (
<form onSubmit={onSubmit}>
<input onChange={onChange} value={input} placeholder="github 계정명을 입력하세요"/>
<button>조회</button>
</form>
);
};
export default GithubusernameForm;
components/GithubProfileInfo.tsx
import React from 'react';
type GithubProfileInfoProps = {
name: string;
thumbnail: string;
bio: string;
blog: string;
}
const GithubProfileInfo = ({ name, thumbnail, bio, blog }: GithubProfileInfoProps) => {
return (
<div>
<div>
<img src={thumbnail} alt="img"/>
<div>{name}</div>
</div>
<p>{bio}</p>
<div>{blog !== '' && <a href={blog}>블로그</a>}</div>
</div>
);
};
export default GithubProfileInfo;
containers/GithubProfileLoader.tsx
import React from 'react';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import GithubProfileInfo from '../components/GithubProfileInfo';
import GithubusernameForm from '../components/GithubusernameForm';
import { RootState } from '../modules';
import { getUserProfileThunk } from '../modules/github';
import type {} from 'redux-thunk/extend-redux';
const GithubProfileLoader = () => {
const { data, loading, error } = useSelector((state: RootState) => state.github.userProfile);
const dispatch = useDispatch();
const onSubmitUsername = (username: string) => {
dispatch(getUserProfileThunk(username))
}
return (
<>
<GithubusernameForm onSubmitUsername={onSubmitUsername}/>
{loading && <p>로딩중 ..</p>}
{error && <p>에러발생 ..</p>}
{data && <GithubProfileInfo bio={data.bio}
blog={data.blog}
name={data.name}
thumbnail={data.avatar_url}/>}
</>
);
};
export default GithubProfileLoader;
'Stack > TypeScript' 카테고리의 다른 글
[TS / React] typesafe-actions을 이용한 refactoring (0) | 2022.07.29 |
---|---|
[TS / React] Redux로 To-do List 구현 (0) | 2022.07.29 |
[TS / React] Redux로 Counter 구현 (0) | 2022.07.28 |
[TS / React] Context API (0) | 2022.07.28 |
[TS / React] To-Do List 구현 (0) | 2022.07.27 |