March 16, 2020
익숙해질것 같으면서도 그렇지 않은 타입스크립트 부수기
타입스크립트 + 리액트 적용 정리
프로젝트 생성
npx create-react-app app-name --typescript
타입스크립트를 사용하는 리액트 컴포넌트는 *.tsx 확장자를 사용한다.
App.tsx
import React from 'react'
import logo from './logo.svg'
import './App.css'
const App: React.FC = () => {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)
}
export default App
import React from 'react'
type GreetingsProps = {
name: string
}
const Greetings: React.FC<GreetingsProps> = ({ name }) => (
<div>Hello, {name}</div>
)
export default Greeting
React.FC 에서는 props의 타입을 generics로 넣어서 사용한다.
단점으로는 children이 들어가있다보니 컴포넌트의 props의 타입이 명백하지 않다.
또한 defaultProps가 제대로 작동하지 않는다.
보통 React.FC의 사용은 권장되지 않는다.
...
type GreetingsProps = {
name: string;
mark: string;
optional?: string;
onClick: (name: string) => void; // void는 아무것도 리턴하지 않는다.
}
function Greetings({name, mark, optional, onClick}: GreetingsProps) {
const handleClick = () => onClick(name)
return (
<div>
하이, {name} {mark}
{optional && <p>{optional}</p>}
<div>
<button onClick={handleClick}>나를 눌러라</button>
</div>
</div>
)
}
Greetings.defaultProps = {
mark: '!'
};
...
부모 컴포넌트
...
const App: React.FC = () => {
const onClick = (name: string) => {
console.log(`${name} says hello`);
};
return <Greetings name="Hello" onClick={onClick} />;
}
...
컴포넌트를 렌더링 할 때 필요한 props를 빼먹으면 에디터에서 오류가 난다.
만약 컴포넌트에서 무엇이 필요한지 깜빡했을때에는???
커서를 컴포넌트 위에 올리거나, 컴포넌트의 props를 설정하는 부분에서 Ctrl + Space를 눌러본다.
...
function Counter() {
const [count, setCount] = useState<number>(0);
const onIncrease = () => setCount(count + 1);
const onDecrease = () => setCount(count - 1);
return (
<div>
<h1>{count}</h1>
<div>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
</div>
)
}
useState
type Information = { name: string; description: string }
const [info, setInformation] = useState<Information | null>(null)
상태의 타입이 까다로운 구조를 가진 객체이거나 배열일 때는 제너릭을 명시하는 것이 좋다
type Todo = {id: number: text: string; done: boolean};
const [todos, setTodos] = useState<Todo[]>([]);
// .... 아니라면?
const [todos, setTodos] = useState([] as Todo[]);
배열인 경우 위와 같이 빈 배열만 넣었을 때 해당 배열이 어떤 타입으로 이루어진 배열인지 추론 할 수 없기에 제너릭을 명시한다.
아래있는 as는 Type Assertion 이라는 문법인데, 특정 값이 특정 타입이다라는 정보를 덮어 쓸 수 있는 문법.
...
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: any) => {
//일단 모르니깐 any
}
const handleSubmit = (e:any) => {
}
return (
<form onSubmit={handleSubmit}>
<input name="name" value={name} onChange={onChange} />
<input name="description" value={description} onChange={onChange} />
<button type="submit">등록</button>
</form>
)
}
...
여기서 e 이벤트 객체의 타입이 무엇인지 모르겠다면… 커서를 onChange에 올려본다
e객체의 타입을 React.ChangeEvent
App.tsx
const App: React.FC = () => {
const onSubmit = (form: { name: string; description: string }) => {
console.log(form)
}
return <MyForm onSubmit={onSubmit} />
}
type Action = { type: 'INCREASE' } | { type: 'DECREASE' } // |를 사용하여 쭉 나열
function reducer(state: number, action: Action): number {
switch (action.type) {
case 'INCREASE':
return state + 1
case 'DECREASE':
return state - 1
default:
throw new Error('unhandled action')
}
function Counter() {
const [count, dispatch] = useReducer(reducer, 0)
const onIncrease = () => dispatch({ type: 'INCREASE' })
const onDecrease = () => dispatch({ type: 'DECREASE' })
}
return (
<div>
<h1>{count}</h1>
<div>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
</div>
)
}
리듀서 함수의 state의 타입과 리턴 타입이 동일하다. 리듀서를 만들 땐 이렇게 타입을 동일하게 하는것이 매우 중요하다,