To-Do List 작업 시 컴포넌트를 총 3개로 나누었다.
제목과 내용을 작성할 Form
// useInput.ts
type InputState = { [key: string]: string };
export const useInput = (initialState: InputState) => {
const [values, setValues] = useState<InputState>(initialState);
const onChangeHandler = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
}, []);
const reset = useCallback(() => setValues(initialState), [initialState]);
return { values, onChangeHandler, reset };
};
// TodoForm.tsx
const { values, onChangeHandler, reset } = useInput({ title: '', content: '' });
우선 2개의 인풋이 필요해서 initialState로 title과 content을 매개변수로 받고 useState의 타입을 지정해주기 위해 타입스크립트 문법인 인덱스 시그니처(Index Signatures)
문법을 사용하여 제네릭으로 선언했다.
아울러 과도한 이벤트 핸들러 호출을 방지하기 위해 useCallback
을 사용하여 onChangeHandler 메모이제이션을 함.
인덱스 시그니처란?
특정 타입의 속성 이름은 알 수 없지만 속성값의 타입을 알고 있을 때 사용하는 문법이다.
만약 위 인덱스 시그니처 문법에서isVaild: number
이라는 타입을 추가로 지정해주면 에러가 발생된다.
왜냐하면 type 내부에[key: K]: T
꼴로 타입을 명시해 주어야 한다. 이는 해당 타입의 속성 키는 모두 K 타입이어야 하고 속성값은 모두 T 타입가져야 한다는 의미이기 때문이다.
그러면 이를 방지 하기 위해서는유니온 타입
으로&
또는|
을 string 뒤에 붙여서 표기해주면 된다.[key: string]: string | number
또한, 핸들러의 매개변수 e
에 타입을 부여해주어야 하는데 불필요하게 any
를 줄 순 없으니 타입스크립트에서 이벤트 핸들러 함수에 대해 이벤트 객체의 지정하는 타입이 따로 있었다. 위에서 지정한 ChangeEvent<HTMLInputElement>
이 타입이다.
ChangeEvent란?
ChangeEvent
는 onChange 이벤트와 관련이 있으며,HTMLInputElement
는<input>
요소에 대한 DOM 객체의 타입을 의미한다.
여기서ChangeEvent
의 제네릭 타입<HTMLInputElement>
은 변경 이벤트가 발생하는 대상이<input>
요소임을 명시해주고<HTMLInputElement>
는 특정 타입의<input>
요소를 나타내며, 다른 유형의 HTML 요소에 따라 다른 제네릭 타입을 사용할 수 있다.