개인 학습 과정에서 작성한 markdown 문서로 잘못된 내용이 존재할 수 있습니다.
잘못된 내용 또는 개선 방안을 말씀해주시면 수정하겠습니다.
📝 React에 TypeScript 적용시키기 (1)
본 글은 자세한 개념 정리가 아닌, 공식문서 및 여러 강의를 듣고 개인 학습 및 복습용으로 작성하였습니다.
리액트에 타입스크립트 추가하기
CRA (create-react-app) 을 통해서 간단하게 적용시킬 수 있습니다.
초기 새로운 프로젝트를 생성할때 적용시키기 위해서는 아래의 명령어를 사용하면 됩니다.
npx create-react-app my-app --template typescript
리액트 프로젝트 생성을 위해서는 --template 옵션이 들어가지 않아서 모르다가 타입스크립트를 추가하면서 처음 접하게 되었는데, 간단하게 생각하면 typescript 라는 이름을 가진 누군가 미리 잘 만들어놓은 기본 템플릿을 가져다 쓰게 도와주는 옵션이라고 생각하면 됩니다.
또는, 기존에 이미 존재하는 리액트 프로젝트에 타입스크립트를 추가하기 위해서는 아래의 명령어를 사용하여야 합니다.
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
기본적으로 리액트에서 타입스크립트 파일은 .tsx 확장자를 사용합니다.
간단한 예제를 통한 기초 사용법
함수형 컴포넌트로 만든 간단한 더하기 예제
- useRef 타입 명시
- 변수 값이 null 이여서 오류 발생하는 경우, 해결 방법
- 메서드를 위에 정의하여 사용시 타입 명시 방법
- 엘리먼트에 바로 함수 사용시 타입 추론으로 명시할 필요 없음
- null 로 추론되는 경우 회피 방법 (비권장)
import * as React from 'react'; // react 파일에는 export default 값이 없으므로 import * as ??? from 'react' 를 사용해야 합니다.
function App() {
const [first, setFirst] = React.useState(0); // state 초기화
const [second, setSecond] = React.useState(0);
const [result, setResult] = React.useState<number>(0); // 제네릭으로 타입을 명시해줄수도 있음
const inputRef = React.useRef<HTMLInputElement>(null); // 초기 useRef null 값을 넣으나 어떠한 값을 가질지 모르기에 타입 추론이 불가능하여
// input 엘리멘트 값이 들어갈 것을 제네릭으로 타입을 지정해줍니다.
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => { // 아래 엘리멘트에서 함수를 부르고, 위에서 정의하면 파라미터 값의 타입 추론이 안되어 지정해주어야 합니다.
e.preventDefault();
const input = inputRef.current;
setResult((first + second)); // 결과값을 저장함
setSecond(0); // 입력한 값을 저장함
if (input) { // Ref가 렌더링 되어야 값을 잡는데, 초기값이 null 이기에 오류가 발생하여
input.focus(); // input에 null 이 아닌 엘리먼트가 들어왔을때 포커싱을 해주도록 설정합니다.
} // 다른 방식으로 null 이 여도 실행가능한것은 변수명 뒤에 ! 붙일 수 있으나 엄청난 확신이 있을때만 사용해야합니다. (위험)
}
return (
<>
<h1>{first} + {second}</h1>
<form onSubmit={onSubmit}>
<input
ref={inputRef}
type="text"
value={second}
onChange={e => setSecond(parseInt(e.target.value))} // 인라인으로 함수 내용을 작성하면 이벤트 객체가 타입이 추론되어 타입을 따로 지정해주지 않아도 됩니다.
/>
</form>
<h2>결과 : {result}</h2>
</>
);
}
export default App;
클래스형 컴포넌트로 만든 더하기 예제
- 타입스크립트 인터페이스 사용법
- React.Component 상속받을때 제네릭 이용한 props와 state 타입 명시
- 클래스형에서 Ref 선언 및 사용법
import * as React from 'react'; // react 파일에는 export default 값이 없으므로 import * as ??? from 'react' 를 사용해야 합니다.
interface State { // 타입스크립트 인터페이스 이용하여 객체의 여러값을 한번에 타입 지정을 해둠
first: number,
second: number,
result: number,
}
class App extends React.Component<{}, State> { // 제네릭 <props, state, ...> 아래에 setState 에서 첫번째 파라미터인 이전 state 사용을 위해 타입 명시 필요
state = {
first: 0,
second: 0,
result: 0,
}
onSubmit = (e: React.FormEvent<HTMLFormElement>): void => { // 아래 엘리멘트에서 함수를 부르고, 위에서 정의하면 파라미터 값의 타입 추론이 안되어 지정해주어야 합니다.
e.preventDefault();
this.setState(preState => {
return {
...preState,
second: 0,
result: preState.first + preState.second,
}
})
if (this.input) {
this.input.focus();
}
}
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ second: parseInt(e.target.value) });
}
input: HTMLInputElement | null = null; // 클래스에서 Ref 사용하는 방법 1, 타입이 두개 들어올 수 있으므로 | 사용하여 두개 타입을 허용시킴
inputRef = (c: HTMLInputElement) => { this.input = c };
render() {
return (
<>
<h1>{this.state.first} + {this.state.second}</h1>
<form onSubmit={this.onSubmit}>
<input
ref={this.inputRef}
type="text"
value={this.state.second}
onChange={this.onChange}
/>
</form>
<h2>결과 : {this.state.result}</h2>
</>
);
}
}
export default App;
특정 메서드 및 상황별 사용 방법
함수형 컴포넌트 props
부모 컴포넌트로부터 전달받은 데이터를 받을때도 타입을 명시할 필요가 있습니다.
// 부모 컴포넌트
const Parent = () => {
return (
<>
<Child first={{ value:123, value2:'str' }} second={{ value3:456, value4:'test' }} />
</>
);
}
// 자식 컴포넌트
// 1.
interface First {
value1: number,
value2: string,
}
interface Second {
value3: number,
value4: string,
}
const Child: FunctionComponent<{ first: First, second: Second }> = ({ first, second }) => {
return (
<>
{first.value1}
</>
);
}
// 2.
interface First {
value1: number,
value2: string,
}
interface Second {
value3: number,
value4: string,
}
const Child: FunctionComponent<{ first: First, second: Second }> = (props) => {
return (
<>
{props.first.value1}
</>
);
}
// 3.
interface Props {
first: {
value1: number,
value2: string,
},
second: {
value3: number,
value4: string,
}
}
const Child: FunctionComponent<Props> = (props) => {
return (
<>
{props.first.value1}
</>
);
}
클래스형 컴포넌트 props
함수형 컴포넌트의 props 와 조금 차이가 있습니다.
// 부모 컴포넌트
class Parent extends React.Component {
render () {
return (
<>
<Child first={{ value:123, value2:'str' }} second={{ value3:456, value4:'test' }} />
</>
);
}
}
// 자식 컴포넌트, state 값이 있어서 타입을 명시해야 한다면 props 타입 명시 뒤에 state도 타입 명시를 해주어야 합니다.
interface First {
value1: number,
value2: string,
}
interface Second {
value3: number,
value4: string,
}
class Child extends React.Component<{ first: First, second: Second }> {
render () {
return (
<>
</>
);
}
}
클래스형 컴포넌트 state에 비어있는 리스트 또는 유니온 타입일 경우
클래스형 컴포넌트에서 state 중 유니온 타입을 갖고 있거나 비어있는 리스트인 경우 추가적인 작업이 필요합니다.
// 오류 발생
interface State {
preWord: "FIRST" | "SECOND" | "THIRD",
value: String,
numbers: number[],
}
class WordRelayClass extends React.Component<{}, State> {
state = {
preWord: '첫번째',
value: '',
}
// 1. 생성자 안에서 state 초기화 하기
interface State {
preWord: "FIRST" | "SECOND" | "THIRD",
value: String,
numbers: number[],
}
class WordRelayClass extends React.Component<{}, State> {
constructor(props: {}) {
super(props);
this.state = {
preWord: "FIRST",
value: '',
numbers: []
}
}
// 2. state 옆에 타입 정의 한번 더 하기
interface State {
preWord: "FIRST" | "SECOND" | "THIRD",
value: String,
numbers: number[],
}
class WordRelayClass extends React.Component<{}, State> {
state: State = {
preWord: 'FIRST',
value: '',
numbers: []
}
고차함수
고차함수란 함수를 파라미터로 받거나 함수를 리턴하는 함수를 의미합니다.
그래서 함수 안에 함수를 사용하게 되는데, 아래 코드를 확인해보면 클릭스 number1 이라는 문자열을 파라미터로 넘기고 가장 위의 함수 파라미터로 전달 받고 그 안에 함수를 사용합니다.
그래서 화살표 함수가 두개가 연달아 사용이 됩니다.
const onClicked = (parameter: "number1" | "number2" | "number3") => () => {
...
}
return (
<>
<button onClick={onClicked('number1')}>number1</button>
<button onClick={onClicked('number2')}>number2</button>
<button onClick={onClicked('number3')}>number3</button>
</>
)
useCallback
useCallback 은 함수를 기억하는 Hook 이라고 생각하면 된다. 특정 변수가 변경이 될때만 렌더링이 되도록 할 수 있습니다.
- 자바스크립트
useCallback 메서드의 첫번째 파라미터는 실행될 함수, 두번째 파라미터로는 배열이 들어오는데, 특정 변수를 집어 넣으면 그 변수의 값이 변할때만 렌더링을 시킵니다.
useCallback((e) => {}, []);
- 타입스크립트
T 부분에 정의하는 타입을 넣으면 됩니다.
// 1.
useCallback<(e: T) => T>((e) => {}, []);
// 2.
useCallback((e: T): T {}, []);
useRef
useRef 는 정의한 파일을 확인해보면 같은 이름의 3개의 메서드가 나옵니다.
3개의 메서드는 각 파라미터와 반환값이 다르게 되는데 이거를 확인하여 나의 상황에 맞는 타입을 지정해 주어야 합니다.
// 1
// 초기값이 데이터 타입 목록중 하나에 포함된다면, 속성값 중 current 를 변경 가능하다
// 제네릭 데이터 타입 > 초기 입력 값
function useRef<T>(initialValue: T): MutableRefObject<T>;
// 2
// 데이터 타입 목록 중 초기값이 포함되거나 초기값이 null 인데, 데이터 타입에 없는 경우, 속성값 중 current 가 readonly 로 변경이 불가능하다.
function useRef<T>(initialValue: T|null): RefObject<T>;
// 3
데이터 타입이 정해지지 않았고, 초기값이 없을때, 1번 사항과 같음
function useRef<T = undefined>(): MutableRefObject<T | undefined>;
createRef
React.createRef 는 클래스형 컴포넌트에서 Ref 를 이용하기 위해 사용합니다.
inputRef = createRef<T>();
useReducer
Reducer는 여러 컴포넌트에 흩어진 state를 하나로 모아서 관리하기 쉽게 해줍니다.
Redux 에서 자주 사용하여 비슷하다고 생각하면 됩니다.
// Reducer State 데이터 인터페이스 정의
interface IReducerState {
first: 'one' | 'two',
second: string,
third: number,
forth: string[][],
fifth: [number, number],
}
// 초기 State
const initialState: IReducerState = {
first: '',
second: '',
third: 123,
forth: [['123'], ['456']],
fifth: [1, 2],
}
// State 값을 변경할 Action 생성
export const SET_FIRST = 'SET_FIRST' as const;
export const CHANGE_THIRD = 'CHANGE_THIRD' as const;
export const UP_FIFTH = 'UP_FIFTH' as const;
// Action 데이터 타입 지정 (액션은 객체)
interface SetFirstAction {
type: typeof SET_FIRST;
First: 'one' | 'two';
}
// Action 행동(메서드) 정의
const setFirst = (first: 'one' | 'two'): SetFirstAction => {
return { type: SET_FIRST, first }; // 바로 위에 정의한 타입으로 반환
}
interface ChangeThirdAction {
type: typeof CHANGE_THIRD;
third: number;
}
const changeThird = (third: number): ChangeThirdAction => {
return { type: CHANGE_THIRD, third }; // 바로 위에 정의한 타입으로 반환
}
// 사용자로부터 어떠한 값을 입력 받는게 아니라면 값을 받는 함수가 필요없음
interface UpFifthAction {
type: typeof UP_FIFTH;
fifth: [number, number];
}
// Action들 데이터 타입으로 모두 지정
type ReducerActions = SetFirstAction | ChangeThirdAction | UpFifthAction;
const reducer = (state: IReducerState, action: ReducerActions): IReducerState => {
switch (action.type) {
case SET_FIRST:
return {
...state,
first: action.first,
};
case CHANGE_THIRD: {
... // 어떤 다른 행동의 코드
return {
...state,
third: action.third
}
};
case UP_FIFTH: {
return {
...state,
fifth: [state.fifth[0] + 1, state.fifth[1] + 1]
}
};
default:
return state;
}
}
const App = () => {
const [state, dispatch] = useReducer<React.Reducer<IReducerState, ReducerActions>>(reducer, initialState);
const Clicked1 = useCallback(() => {
dispatch(setFirst('one')); // 버튼 클릭이되면 dispatch로 액션 실행
}, []);
return (
<>
<button onClick={Clicked1}>1</button>
</>
);
}
dispatch
useReducer 를 사용하여 자식 컴포넌트에 props 로 dispatch를 넘겨주는 경우에 props 에서 Dispath 타입을 명시해 주어야 합니다.
// 자식 컴포넌트
interface Props {
first: string,
dispatch: React.Dispatch<any>, // any 대신에 부모컴포넌트에서 사용하던 action 들을 집어 넣으면 되는데, any도 크게 상관 없음
onClick: () => void,
}
const Child: FC<Props> = ({ first, dispatch }) => {
...
}
setTimeout
Node.js 와 브라우저(인터넷)에서 사용하는 두가지 종류가 있어서, 반환값의 타입이 다른 경우가 있어 어디서 사용할지 명시할 필요가 있습니다.
//브라우저에서 사용하는 경우
window.setTimeout(() => {}, ??)
useState 에서 빈 배열을 사용하는 경우
useState 초기값으로 빈 배열을 사용하는 경우 변수는 타입 추론이 제대로 안됩니다.
never 로 타이핑이 되는데, never 는 일반저그로 함수의 리턴 타입으로 사용되는데 함수에서 항상 오류를 출력하거나 리턴값을 절대로 내보내지 않음을 의미합니다.
그래서 제네릭으로 타입을 명시해 줄 필요가 있습니다.
// 1.
const [value, setValue] = useState<string[]>([]);
// 2.
interface TypeInfo {
first: number,
second: string,
}
const [values, setValues] = useState<TypeInfo[]>([]);
그 외 기타
객체에 명시한 타입을 그대로 가져와 사용하고 싶을때
const values = {
value1: '100',
value2: '200',
} as const // 위의 값들은 하나로 고정되어 타입을 하나로 지정하고 싶을때, as const 를 사용하면 그 값들만 사용할수있게 바뀐다
type val = typeof values; // type val = { readonly value1: '100'; readonly value2: '200'; }
type val = keyof typeof values; // type val = 'value1' | 'value2'
type val = typeof values[keyof typeof values]; // type val = '100' | '200'
특정 메서드 (Object.keys) 반환 타입에 원하는 타입이 없을때
특정 메서드의 반환하는 타입이 원하는게 없는 경우가 있으면 강제로 형 변환이 필요합니다.
const values = {
value1: '100',
value2: '200',
value3: '300',
} as const // as const 를 사용하여 데이터 타입을 지정해준다
// 아래의 경우 Object.keys() 의 반환 데이터 타입이 string[] 인데, 위의 키는 value1, value2, value3 세개만 나와야 하므로 형을 자세히 지정할 필요가 있음
(Object.keys(values) as ['value1', 'value2', 'value3']).map(() => {});
참고 자료
'공부 및 정리 > 프론트엔드' 카테고리의 다른 글
[Next.js] 기본 개념과 프로젝트에 사용한 이유 (0) | 2022.05.12 |
---|---|
함수형 프로그래밍 (0) | 2022.03.14 |
[CSS] CSS-in-CSS vs CSS-in-JS (0) | 2022.01.06 |
[TypeScript] 기초 학습 (1) (0) | 2022.01.01 |
[React] React Router v6 기초 학습 (0) | 2021.12.30 |
댓글