타입스크립트를 이용해서 리액트 프로젝트를 만들어보자.
리액트에서 type스크립트를 지원하기에 별도의 설정은 필요없지만 프로젝트를 실행할때 typescript를 사용한다고 표시만 하면 된다.
npx create-react-app my-app --template typescript
리액트 프로젝트를 만들때 뒤에 --typescript를 붙이면 된다.
이미 만든 프로젝트에 typescript를 적용하고 싶다면 아래 링크를 참조하자
https://create-react-app.dev/docs/adding-typescript/
typescript 파일이기때문에
App.tsx 파일로 생성되는것을 볼 수 있다.
지난 글들에서는 App을 function으로 선언해서 사용했는데 화살표함수를 써서 아래와 같이 작성했을때의 장단점을 알아보자.
import React from 'react';
import Greetings from './Greetings';
const App: React.FC =() => { //화살표함수로 선언
return (
<Greetings name = "정민규"/>
);
}
export default App;
아래 코드에서 볼 수 잇듯이 childeren 값을 따로 넣지 않아도 들어있다는 장점이 있다.
//Greetings.tsx
import React from 'react';
type GreetingsProps = {
name : string;
}
const Greetings : React.FC<GreetingsProps> = ({name}) => { //children이란 값을 따로 선언하지 않아도 들어있다.
return <div>나는 {name}!!!</div>;
};
export default Greetings;
단점으로는 defaultProps가 제대로 작동하지 않는다는 치명적 단점이 작용한다.
//Greetings.tsx
import React from 'react';
type GreetingsProps = {
name : string;
//mark : string; //error
mark? : string; // 그래도 문제가 존재 -> string or undefind로 존재하게 됨
}
const Greetings : React.FC<GreetingsProps> = ({name,mark}) => { //children이란 값을 따로 선언하지 않아도 들어있다.
return <div>나는 {name}!!!</div>;
};
Greetings.defaultProps = {
mark : '!',
}
export default Greetings;
위같은 코드인 경우에도 제대로 작동하지 않는다.
제대로 작동시키기 위해서는 아래와 같이 작성해야 한다.
//Greetings.tsx
import React from 'react';
type GreetingsProps = {
name : string;
//mark : string; //error
mark? : string; // 그래도 문제가 존재 -> string or undefind로 존재하게 됨
}
const Greetings : React.FC<GreetingsProps> = ({name,mark = '!'}) => { //children이란 값을 따로 선언하지 않아도 들어있다.
return <div>나는 {name}!!!</div>;
};
export default Greetings;
위 코드들을 일반 function 를 사용해서 작성하면 아래와 같다.
import React from 'react';
type GreetingsProps = {
name: string;
mark: string;
};
function Greetings({ name, mark }: GreetingsProps) {
return (
<div>
Hello, {name} {mark}
</div>
);
}
Greetings.defaultProps = {
mark: '!'
};
export default Greetings;
이전에 했었던 것처럼 defaultProps가 제대로 작동한다! mark? 를 쓰지않아도 잘 작동함을 알 수 있다.
Props가 있을때만 쓰고싶으면 ?를 사용해도 무방하다.
함수를 Props로 받아올수도 있다.
//Greetings.tsx
import React from 'react';
type GreetingsProps = {
name: string;
mark: string;
optional?: string;
onClick : (name : string) => void; //함수타입을 가져오고 싶을때
};
function Greetings({ name, mark, optional, onClick }: GreetingsProps) {
const handleClick = () => onClick(name);
return (
<div>
Hello, {name} {mark}
{optional && <p>{optional}</p>}
<div>
<button onClick={handleClick}>버튼이지롱</button>
</div>
</div>
);
}
Greetings.defaultProps = {
mark: '!'
};
export default Greetings;
//App.tsx
import React from 'react';
import Greetings from './Greetings';
const App: React.FC =() => { //화살표함수로 선언
const onClick = (name : string) => {
console.log(name);
}
return (
<Greetings name = "정민규" onClick={onClick}/>
);
}
export default App;
위처럼 작성하고 실행해보면
실행도 잘되고 버튼을 누르면 console에도 잘 출력되는것을 확인할 수 있다.
useState
useState를 사용해서 변화한는 값을 다루어보자.
아주간단한 counter예제를 만들어보자. counter예제는 이제 많이 만들어봐서 이해하기 편할 것이다.
import React, { useState } from 'react';
function Counter() {
const [count,setCount] = useState<number>(0); //counter의상태를 0으로 설정 number를 생략하면 자동으로 추출해줌
const onIncrease = () => setCount(count + 1);
const onDecrease = () => setCount(count - 1);
return (
<>
<div>
<h1>{count}</h1>
</div>
<button onClick={onIncrease}>증가</button>
<button onClick={onDecrease}>감소</button>
</>
)
}
export default Counter;
이후, App.js에서 불러오기만 하면 된다. 사실 counter같은 간단한 예제에서는 JS와 코드내용이 크게 다를것이 없다. 파일의 확장자가 tsx인점만 다르다.
이번엔 조금더 복잡한 input으로 값을 넣어주는 Form예제를 다뤄보자.
import React, { useState } from "react";
// type Params = {
// name : string;
// description : string;
// }
//이부분을 onSubmit : (form : Params ) => void 와 동일
type MyFormProps = {
onSubmit: ( form : { name : string; description : string }) => void;
};
function MyForm( {onSubmit } : MyFormProps) {
const [form,setForm] = useState({
name : '',
description : ''
});
const { name, description } = form;
const onChange = (e : React.ChangeEvent<HTMLInputElement>) => {
const { name, value} = e.target;
setForm({
...form,
[name] : value
})
}
const handleSubmit = (e : React.FormEvent<HTMLFormElement> ) =>{
e.preventDefault(); //submit시 새로고침 방지
onSubmit(form);
setForm({
name : '',
description : ''
})
}
return (
<form onSubmit={handleSubmit}>
<input name="name" value = {name} onChange={onChange} />
<input name="description" value = {description} onChange={onChange} />
<button type="submit">등록</button>
</form>
)
}
export default MyForm;
위 코드역시 Js로 만든것과 크게 다르지는 않다.
App.js에서 Counter밑에 MyForm을 불러오고 실행하면
잘 작동하고, 밑에 값도 잘 저장되는것을 확인할 수 있다.
이번엔 useReducer를 사용해보자.
좀전에 만든 Counter예제를 Reducer를 사용해서 바꾸어보자.
import React, { useReducer } from 'react';
type Action = { type : 'INCREASE' } | {type : 'DECRESE' };
function reducer(state : number, action : Action) : number {
switch (action.type) {
case 'INCREASE':
return state +1
case 'DECRESE':
return state -1
default :
throw new Error ('에러 발생!!!')
}
}
function Counter() {
const [count,dispatch] = useReducer(reducer,0);
const onIncrease = () => dispatch({type : 'INCREASE'});
const onDecrease = () => dispatch({type : 'DECRESE'});
return (
<>
<div>
<h1>{count}</h1>
</div>
<button onClick={onIncrease}>증가</button>
<button onClick={onDecrease}>감소</button>
</>
)
}
export default Counter;
확실히 타입을 정해두니 일일히 찾지 않아도 되고 헷갈리는 일이 적어진거 같다.
조금 더 복잡한 예제를 보자.
import React, {useReducer } from "react";
type Color = 'red' | 'orange' | 'yellow';
type State = {
count : number;
text : string;
color : Color;
isGood : boolean;
}
type Action = //Action Type 정의
| { type : 'SET_COUNT'; count : number }
| { type : 'SET_TEXT'; text : string}
| { type : 'SET_COLOR'; color : Color}
| { type : 'TOGGLE_GOOD'};
function reducer(state : State, action : Action) : State {
switch (action.type) {
case 'SET_COUNT':
return{
...state,
count: action.count
}
case 'SET_TEXT':
return {
...state,
text : action.text
}
case 'SET_COLOR':
return {
...state,
color : action.color
}
case 'TOGGLE_GOOD':
return{
...state,
isGood : !state.isGood
}
default:
throw new Error("에러 발생!!");
}
}
function ReducerSample() {
const [state,dispatch] = useReducer(reducer,{
count : 0,
text : '정민규',
color : 'red',
isGood : true
});
const setCount = () => dispatch({ type : 'SET_COUNT', count : 5});
const setText = () => dispatch ({ type : 'SET_TEXT', text : '나는 정민규'});
const setColor = () => dispatch ({ type : 'SET_COLOR', color : 'orange'});
const toggleGood = () => dispatch ({ type : 'TOGGLE_GOOD'});
return (
<div>
<p>
<code>count : </code> {state.count}
</p>
<p>
<code>text : </code> {state.text}
</p>
<p>
<code>color : </code> {state.color}
</p>
<p>
<code>isGood : </code> {state.isGood ? 'true' : 'false'}
</p>
<div>
<button onClick={setCount}>SET_COUNT</button>
<button onClick={setText}>SET_TEXT</button>
<button onClick={setColor}>SET_COLOR</button>
<button onClick={toggleGood}>TOGGLE_GOOD</button>
</div>
</div>
)
}
export default ReducerSample;
크게 어렵다기보단, 값을 4개를 두어서 내가 설정한 값으로 바꾸는 간단한 코드이다. reducer에 대해서 공부했었다면 어렵지 않게 볼 수 있다. 코드를 직접 작성하면서 든 생각은 자동완성기능이 잘되어있어서 정말 편하다는 생각이었다.
버튼을 누르면 설정해둔 값으로 바뀐다! TOGGLE_GOOD같은 경우 값이 반전이 되게 된다.
Context API 활용
typescript에서 Context API는 아래와 같은 형식으로 사용된다.
type FooValue = {
foo : number;
};
const FooContext = createContext<FooValue | null>(null) //기본값으로 null을 넣음
방금 만들었던 리듀서를 옮겨보자. SampleContext.tsx파일을 하나 만들고 Reducersample.tsx에서 가져온다.
//SampleContext.tsx
import React, { createContext, Dispatch, useContext, useReducer } from "react";
type Color = "red" | "orange" | "yellow";
type State = {
count: number;
text: string;
color: Color;
isGood: boolean;
};
type Action = //Action Type 정의
| { type: "SET_COUNT"; count: number }
| { type: "SET_TEXT"; text: string }
| { type: "SET_COLOR"; color: Color }
| { type: "TOGGLE_GOOD" };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "SET_COUNT":
return {
...state,
count: action.count,
};
case "SET_TEXT":
return {
...state,
text: action.text,
};
case "SET_COLOR":
return {
...state,
color: action.color,
};
case "TOGGLE_GOOD":
return {
...state,
isGood: !state.isGood,
};
default:
throw new Error("에러 발생!!");
}
}
const SampleStateContext = createContext<State | null>(null);
const SampleDispatchContext = createContext<Dispatch<Action> | null>(null); //리액트안에 Dispatch가 있기에 불러내서 사용
type SampleProviderProps = {
children: React.ReactNode;
};
export function SampleProvider({ children }: SampleProviderProps) {
const [state, dispatch] = useReducer(reducer, {
count: 0,
text: "정민규",
color: "red",
isGood: true,
});
return (
<SampleStateContext.Provider value={state}>
<SampleDispatchContext.Provider value={dispatch}>
{children}
</SampleDispatchContext.Provider>
</SampleStateContext.Provider>
);
}
export function useSampleState() {
const state = useContext(SampleStateContext);
if (!state) throw new Error("cannot find SampleProvider");
return state;
}
export function useSampleDispatch() {
const dispatch = useContext(SampleDispatchContext);
if (!dispatch) throw new Error("cannot find SampleProvider");
return dispatch;
}
그리고 밑에 createContext를 통해 useContext를 만들어주고, 훅 2개를 만들어줘서 밖에서도 쉽게 사용할 수 있게 해준다.
//ReducerSample.tsx
import React, { useReducer } from "react";
import { useSampleDispatch, useSampleState } from "./SampleContext";
function ReducerSample() {
const state = useSampleState(); //useContext 사용
const dispatch = useSampleDispatch();
const setCount = () => dispatch({ type: "SET_COUNT", count: 5 });
const setText = () => dispatch({ type: "SET_TEXT", text: "나는 정민규" });
const setColor = () => dispatch({ type: "SET_COLOR", color: "orange" });
const toggleGood = () => dispatch({ type: "TOGGLE_GOOD" });
return (
<div>
<p>
<code>count : </code> {state.count}
</p>
<p>
<code>text : </code> {state.text}
</p>
<p>
<code>color : </code> {state.color}
</p>
<p>
<code>isGood : </code> {state.isGood ? "true" : "false"}
</p>
<div>
<button onClick={setCount}>SET_COUNT</button>
<button onClick={setText}>SET_TEXT</button>
<button onClick={setColor}>SET_COLOR</button>
<button onClick={toggleGood}>TOGGLE_GOOD</button>
</div>
</div>
);
}
export default ReducerSample;
2줄을 추가해줘서 context를 불러온 후에, App.tsx에서
return (
<>
{/* <Counter/>
<MyForm onSubmit={onSubmit}/> */}
<SampleProvider>
<ReducerSample/>
</SampleProvider>
</>
);
SampleProvider로 감싸주면 이전과 같은 기능이 동작한다.
'FrontEnd > React' 카테고리의 다른 글
27_타입스크립트 & 리덕스 미들웨어 (0) | 2022.01.10 |
---|---|
26_타입스크립트 & 리덕스 (0) | 2022.01.07 |
23_리액트 리덕스 미들웨어(2) (0) | 2022.01.05 |
22_리덕스 미들웨어 (0) | 2022.01.05 |
21_리액트_리덕스 (0) | 2022.01.02 |