React 에서 Type script 시작하기
프로젝트 세팅
설치 명령어
npx create-react-app my-app --template redux-typescript
- my-app 이 프로젝트 폴더 이름- 이렇게 하니까 프로젝트 세팅이 다 된다 필요한건 거의 설치해주는 것같다.
- package.json 확인

json-server 세팅
server를 대체하여 json-server를 사용해 보았다.
설치 명령어
npm install json-server
실행 명령어
json-server ./db.json --port 4000
Typescript 사용해보기
개요
- 타입을 어떻게 지정하는지, 어디에 어떤타입을 써야하는지 등이 익숙치 않아서 좀 많이 해멨다.
- 아래 3가지 관점에서 방법이 정리되어 있지 않아 방법을 찾는데 시간을 많이 썼다.
- React에서 state,props 사용시에 type 지정하는 방법
- Redux toolkit에서 payload, selector등을 사용할때 type 지정하는 방법
- Axios 에서 data 주고 받을때 type 지정하는 방법
React에서 state,props 등을 사용할때 type 지정하는 방법
state
- state를 생성하면 type은 추론해서 따로 지정해줄 필요는 없다.
- 그러나 changeEvent의 value와 type이 맞지 않다거나 하는 경우가 발생하면 따로 지정해 주어야 한다.
state type 지정방법
// useState 값을 무엇을 넣는지에 따라 자동으로 추론을 하기도 한다.
const [greetingId, setGreetingId] = useState<number | string>(0);
changeEvent 사용하는 경우의 type 지정 방법
// parameter에 받는 event의 type을 모를 수 있는데 그때는 event에 마우스 커서를 올리면 친절하게 설명해준다.
// 똑같이 쓰기만 하면 된다.
const onChangeId = (event: React.ChangeEvent<HTMLInputElement>) => {
setGreetingId(event.target.value);
};
props
부모 컴포넌트
- 부모 컴포넌트에서는 type 지정이 따로 필요하지 않은것 같다.
- 내가 따로 조치한 것은 없다.
자식 컴포넌트
- 자식 컴포넌트에서 props를 받아 사용하려면 type 지정이 필수이다.
- interface로 만들어 객체 타입을 지정하여 사용하였다.
import React from "react";
import Button from "./Button";
import { greetingsType } from "../redux/modules/greetingSlice";
// interface 생성
// greetings 라는 props는 id, greeting을 property로 갖는 객체({})로 이루어진 배열([])이다.
// 이때 id는 number 혹은 string을 type으로 받을 수 있다.
// greeting은 string만을 type으로 받는다.
// 특히나 id의 경우에는 숫자가 입력되지만, input 으로 입력받는 event.target.value가 string만 지정이 되어서 어쩔수 없지 union type을 지정했다.
// 더 좋은 방법이 있을것이다...
export interface IProps {
greetings: greetingsType[];
}
// props를 받는 곳에 IProps를 type으로 붙혀주었다.
// 주의해야 할 점은 map을 사용하려면 type이 배열([])임을 명시해주어야 한다.
const Greetings = ({ greetings }: IProps) => {
const greetingList = () => {
return greetings.map((greeting) => {
return (
<div key={greeting.id}>
<li>
{greeting.greeting}
{greeting.id}
</li>
<Button id={greeting.id} />
</div>
);
});
};
return <ul>{greetingList()}</ul>;
};
export default Greetings;
Redux toolkit에서 payload, selector등을 사용할때 type 지정하는 방법 (+axios)
/src/redux/configStore/configStore.ts
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import greetingReducer from "../modules/greetingSlice";
export const store = configureStore({
reducer: {
greeting: greetingReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
// Ignore these action types
ignoredActions: ["your/action/type"],
// Ignore these field paths in all actions
ignoredActionPaths: ["meta.arg", "payload"],
// Ignore these paths in the state
ignoredPaths: ["items.dates"],
},
}),
});
// 상태를 조회할때(state를 불러올때)state 의 타입을 지정해야할 때 RootState를 사용한다.
export type RootState = ReturnType<typeof store.getState>;
// dispatch의 type을 지정해주어야 할때는 AppDispatch를 사용한다.
export type AppDispatch = typeof store.dispatch;
// thunk의 type을 지정할때 AppThunk를 사용하게 할 수 있다. 그러나 정확하게 어떻게 쓰는지는 잘 모른다.
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
/src/hooks/hooks.ts
Dispatch와 useSelector의 type을 따로 지정해주어야 한다.
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "../redux/configStore/configStore";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
/src/redux/modules/slice.ts
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios from "axios";
// data 객체의 type을 interface로 지정해주었다.
export interface greetingsType {
id: number | string;
greeting?: string;
}
// initail state 의 type을 interface로 지정해주었다.
export interface stateType {
// isLoading을 이런 식으로 지정하면 3가지 중에서만 값을 입력할수 있다.
isLoading: "idle" | "loading" | "failed";
greetings: greetingsType[];
}
// initailState의 type을 받았다.
const initialState: stateType = {
isLoading: "idle",
greetings: [],
};
export const __getGreetings = createAsyncThunk(
"getGreetings",
async (payload, thunkAPI) => {
try {
const { data } = await axios.get("http://localhost:4000/greetings");
return thunkAPI.fulfillWithValue(data);
} catch (error) {
return thunkAPI.rejectWithValue(error);
}
}
);
// payload의 type을 지정해주어야 한다.
// component에서 dispatch에 붙혀주는 payload와 type이 일치해야 한다.
export const __postGreetings = createAsyncThunk(
"postGreetings",
async (payload: greetingsType, thunkAPI) => {
const { id, greeting } = payload;
try {
const response = await axios.post("http://localhost:4000/greetings", {
id,
greeting,
});
window.location.reload();
return thunkAPI.fulfillWithValue(response);
} catch (error) {
return thunkAPI.rejectWithValue(error);
}
}
);
export const __updateGreetings = createAsyncThunk(
"updateGreetings",
async (payload: greetingsType, thunkAPI) => {
const { id, greeting }: greetingsType = payload;
try {
const response = await axios.put(
`http://localhost:4000/greetings/${id}`,
{ greeting }
);
window.location.reload();
return thunkAPI.fulfillWithValue(response);
} catch (error) {
return thunkAPI.rejectWithValue(error);
}
}
);
// 중략
export const greetingSlice = createSlice({
name: "greetings",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(__getGreetings.pending, (state) => {
state.isLoading = "loading";
})
.addCase(__getGreetings.fulfilled, (state, action) => {
state.isLoading = "idle";
state.greetings = action.payload;
})
.addCase(__getGreetings.rejected, (state) => {
state.isLoading = "failed";
});
// 중략
},
});
export default greetingSlice.reducer;
/src/App.tsx
import React, { useEffect } from "react";
// hooks에서 만들어준 dispatch와 selector를 불러온다.
import { useAppDispatch, useAppSelector } from "./hooks/hooks";
import Greetings from "./components/Greetings";
import InputGreeting from "./components/InputGreeting";
import { __getGreetings } from "./redux/modules/greetingSlice";
const App = () => {
// dispatch를 구체화해서 사용한다.
const dispatch = useAppDispatch();
const { greetings } = useAppSelector((state) => state.greeting);
useEffect(() => {
dispatch(__getGreetings());
}, [dispatch]);
return (
<div>
<InputGreeting />
<div>
// greetings는 props를 넘겨주기때문에 child component에서 type이 지정된다.
// {greetings} 는 selector로 불러오기 때문에 slice에서 type이 지정된다.
// 따라서 이 두개의 type이 같아야 한다.
<Greetings greetings={greetings} />
</div>
</div>
);
};
export default App;
/src/components/InputGreeting.tsx
// dipatch 로 payload를 넘겨줄때, interface 등으로 지정한 객체 타입과 맞는 타입의 값을 전달해야한다.
// 지정되어 있는 값이 빠져서도 안된다.
// 값이 없을수도 있는 경우에는 ? 을 붙혀주면 된다.
const handleSubmitGreetings = () => {
dispatch(__postGreetings({ id: Date.now(), greeting: greeting }));
};
const handleUpdateGreetings = () => {
dispatch(__updateGreetings({ id: greetingId, greeting: greeting }));
};
/src/components/Button.tsx
import React from "react";
import { useAppDispatch } from "../hooks/hooks";
import { __deleteGreetings } from "../redux/modules/greetingSlice";
import { greetingsType } from "../redux/modules/greetingSlice";
// greeting? 로 지정해두어서 greeting이 없어도 type 지정이 된다.
const Button = ({ id }: greetingsType) => {
const dispatch = useAppDispatch();
const handleDeleteGreeting = () => {
// 넘겨주는 것도 마찬가지이다.
dispatch(__deleteGreetings({ id: id }));
};
return <button onClick={handleDeleteGreeting}>삭제하기</button>;
};
export default Button;
내가 만난 에러
A non-serializable value was detected in an action, in the path: `payload.headers`.
직렬화란(serialize)?
javascript에서 사용하는 데이터 타입은 주로 object이다. 하지만 이 자바스크립트 오브젝트가 다른 언어나 다른환경에서도 똑같이 사용될수 있을까? 간단한 예로는 localstorage가 있다. localstorage는 값으로 string을 가질수 있지만 object는 가질수 없다. 이럴때 우리는 json.stringify를 통해서 object를 스트링화 한다. 이것이 직렬화다. 그리고 다시 꺼내쓸때는 스트링은 json.parse하여 오브젝트로 다시 변환한다 이것이 역직렬화다.이때 직렬화 전 object와 역직렬화된 object는 같아야 한다.
위와같이 보통 자바스크립트에서 직렬화를 이용하는 방법은 JSON객체의 stringify와 parse를 사용하는 것이다.
'Hanghae99' 카테고리의 다른 글
| 230115 WIL 실전 프로젝트 2주차 (0) | 2023.01.15 |
|---|---|
| 230108 WIL 실전 프로젝트 1주차 (0) | 2023.01.08 |
| 230101 WIL 클론 프로젝트 주차 (0) | 2023.01.02 |
| 221225 WIL 항해99 6주차 미니프로젝트 [먹스타그램] (0) | 2022.12.25 |
| 221222 TIL 클론코딩 프로젝트 1 (1) | 2022.12.24 |