Typescript 에서 Hook 사용하기
포스트
취소

Typescript 에서 Hook 사용하기

안녕하세요. NarviS2 입니다.
지난 포스팅에서는 TypeScript에 관하여 기초적인 문법을 알아보았습니다.
지난 포스팅을 안보신 분들은 👉 Typescript에 관하여를 통해 참고하시길 추천드립니다.
또한 이번 포스팅에서는 TypeScriptHook을 사용하는 방법을 예제를 통하여 알아보도록할텐데 Hook에 관하여 모르시는 분들은 👉 React-Native Hook을 참고하시길 권장합니다.

🚩 React Component를 TypeScript로 작상하기


  • propstype이 붙는 것 말고는 달라질 것이 없습니다. (props 가 없는 ComponentJavaScript로 작성할 때와 똑같습니다.)

    주의❗️ React Component를 작성할 때는 tsx확장자, JSX를 사용하지 않는 코드는 ts확장자를 사용합니다.

1. Props 사용하기

  • Props가 필요해지면 Props를 위한 type을 선언해줘야 합니다.
  • Props를 위한 typeinterface를 사용하여 선언합니다.
  • 예제👇
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import {View, Text} from 'react-native';

interface Props {
    name: string;
}

function Profile({name}: Props) {
    return (
        <View>
            <Text>{name}</Text>
        </View>
    );
}

export default Profile;

2. 옵셔널 Props 사용하기

  • interface에서 특정 필드에 ?를 붙여서 타입을 선언합니다.

    참고 : 옵셔널 Props란 생략가능한 Props라는 뜻입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react';
import {View, Text, StyleSheet} from 'react-native';

interface Props {
    name: string;
    isActive?: boolean;
}

function Profile({name, isActive}: Props) {
    return (
        <View style={isActive && styles.activeStyle}>
            <Text>{name}</Text>
        </View>
    );
}

const styles = StyleSheet.create({
    activeStyle: {
        backgroundColor: 'yellow',
    },
});

export default Profile;
  • Profile Component를 사용할 때 isActive Props를 생략해도 TypeScript오류가 발생하지 않습니다.

3. 기본값 Props 사용하기

  • defaultProps 사용 👇
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React from 'react';
import {View, Text, StyleSheet, Image} from 'react-native';

interface Props {
    name: string;
    isActive?: boolean;
    image: string;
}

function Profile({name, isActive, image}: Props) {
    return (
        <View style={isActive && styles.activeStyle}>
            <Image source= />
            <Text>{name}</Text>
        </View>
    );
}

Profile.defaultProps = {
    image: ''.
};

const styles = StyleSheet.create({
    activeStyle: {
        backgroundColor: 'yellow',
    },
});

export default Profile;
  • JavaScript 기본값 매개변수 문법 사용
    • Props 파라미터가 구조분해되는 과정에서 특정 값의 기본값을 지정할 수 있습니다.
    • 해당 필드에 옵셔널 ?를 붙혀주셔야 합니다. 예제 👇
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';
import {View, Text, StyleSheet, Image} from 'react-native';

interface Props {
    name: string;
    isActive?: boolean;
    image?: string;
}

function Profile({name, isActive, image='이미지 주소 넣기'}: Props) {
    return (
        <View style={isActive && styles.activeStyle}>
            <Image source= />
            <Text>{name}</Text>
        </View>
    );
}

const styles = StyleSheet.create({
    activeStyle: {
        backgroundColor: 'yellow',
    },
});

export default Profile;

4. children Props 사용하기 (React.ReactNode)

  • JSX 타입의 Props를 받아오는 상황에서는 React.ReactNode 타입을 사용

    ✅ 참고 : 만약 children Props가 반드시 필요하지 않다면 children ?: React.ReactNode 라고 입력하여 이 Props를 생략해도 된다는 것을 명시해야함 예제 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from 'react';
import {View, Text, StyleSheet, Image} from 'react-native';

interface Props {
    name: string;
    isActive?: boolean;
    image?: string;
    children: React.ReactNode;
}

function Profile({
    name,
    isActive,
    image='이미지 주소 넣기',
    children,
}: Props) {
    return (
        <View style={isActive && styles.activeStyle}>
            <Image source= />
            <Text>{name}</Text>
            <View>{children}</View>
        </View>
    );
}

const styles = StyleSheet.create({
    activeStyle: {
        backgroundColor: 'yellow',
    },
});

export default Profile;
  • App.tsx 에서 사용 👇
1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';
import {Text} from 'react-native';
import Profile from './profile/';

function App() {
    return (
        <Profile name="NarviS2">
            <Text>Hello World</Text>
        </Profile>
    );
}

export default App;

🚩 TypeScript 에서 useState 사용하기


  • Generic을 사용하여 상태의 타입을 지정해줄 수 있습니다.

    ✅ 참고 : useState에 넣은 인자의 타입을 유추할 수 없거나 여러 타입을 지닌 경우에는 Generic을 꼭 설정해줘야 합니다.
    예제 👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React, {useState} from 'react';
import {Button, Text, TextInput, View} from 'react-native';

function MessageForm() {
    // 해당 제네릭은 생략가능
    const [text, setText] = useState<String>('');
    // 여러 타입을 지닌 경우
    const [lastMessage, setLastMessage] = useState<{
        message: string;
        date: Date;
    } | null>(null);

    const onPress = () => {
        setLastMessage({
            message: text,
            date: new Date(),
        });
        setText('');
    };

    return (
        <View>
            <TextInput value={text} onChangeText={setText} />
            <Button title="PRESS ME" onPress={onPress} />
            {lastMessage && (
                <View>
                    <Text>
                        마지막 메시지: {lastMessage.message} (
                            {lastMessage.date.toLocaleString()}
                        )
                    </Text>
                </View>
            )}
        </View>
    );
}

export default MessageForm;

🚩 TypeScript 에서 useRef 사용하기


  • useRef에 담을 값의 타입을 Generic으로 설정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import React, {useState, useRef, useEffect} from 'react';
import {Button, Text, TextInput, View} from 'react-native';

function MessageForm() {
    // 해당 제네릭은 생략가능
    const [text, setText] = useState<String>('');
    // 여러 타입을 지닌 경우
    const [lastMessage, setLastMessage] = useState<{
        id: number;
        message: string;
        date: Date;
    } | null>(null);

    // useRef 사용
    const nextId = useRef<Number>(1);
    // 처음 렌더링될 때는 null이고 한번 렌더링된 뒤에는 TextInput의 인스턴스가 담깁니다.
    const inputRef = useRef<TextInput | null>(null);

    const onPress = () => {
        setLastMessage({
            id: nextId.current,
            message: text,
            date: new Date(),
        });
        setText('');
        nextId.current += 1;
    };

    // useEffect를 통해 이 컴포넌트가 보여지는 순간 TextInput에 포커스가 잡히도록 설정
    useEffect(() => {
        if (!inputRef.current) {
            retrun;
        }

        inputRef.current.focus();
    }, []);

    return (
        <View>
            <TextInput value={text} onChangeText={setText} ref={inputRef}/>
            <Button title="PRESS ME" onPress={onPress} />
            {lastMessage && (
                <View>
                    <Text>
                        마지막 메시지: {lastMessage.message} (
                            {lastMessage.date.toLocaleString()}
                        )
                    </Text>
                </View>
            )}
        </View>
    );
}

export default MessageForm;

🚩 TypeScript 에서 useReducer 사용하기


  • useReducer를 사용할 때는 리듀서에서 관리할 stateaction타입을 선언해야 합니다.
  • state 선언 (interface 사용)

    예제 👇

    1
    2
    3
    4
    5
    6
    
    interface CounterState {
      value: number;
    }
    const initialState: CounterState = {
      value: 1,
    };
    
  • type 선언 (Type Alias 사용)

    예제 👇

    1
    
    type CounterAction = {type: 'increment'} | {type: 'decrement'; by: number};
    
  • 만약 action의 갯수가 많아질 경우 (여러줄로 작성)

    예제 👇

    1
    2
    3
    4
    5
    
    type CounterAction = 
    | {type: 'increment'}
    | {type: 'decrement'; by: number}
    | {type: 'random'}
    | {type: 'nothing'};
    

    또는 각 action 마다 interface 타입을 선언한 뒤 Type Alias로 합칠 수도 있음

    1
    2
    3
    4
    5
    6
    7
    8
    
    interface IncrementAction {
      type: 'increment';
    }
    interface DecrementAction {
      type: 'decrement';
      by: number;
    }
    type CounterAction = IncrementAction | DecrementAction;
    
  • reducer() 함수 선언

    stateaction을 파라미터로 받아와 그다음 상태를 반환하는 함수입니다.
    예제 👇

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    function reducer(state: CounterState, action: CounterAction) {
      switch (action.type) {
          case 'increment':
              return {
                  value: state.value + 1
              };
          case 'decrement':
              return {
                  value: state.value - action.by
              };
          default:
              throw new Error('Unhandled action type');
      }
    }
    
  • 사용 및 전체 코드
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    interface CounterState {
      value: number;
    }
    const initialState: CounterState = {
      value: 1,
    };
    type CounterAction = {type: 'increment'} | {type: 'decrement'; by: number};
    function reducer(state: CounterState, action: CounterAction) {
      switch (action.type) {
          case 'increment':
              return {
                  value: state.value + 1
              };
          case 'decrement':
              return {
                  value: state.value - action.by
              };
          default:
              throw new Error('Unhandled action type');
      }
    }
    function Counter() {
      const [state, dispatch] = useReducer(reducer, initialState);
      return (
          <View>
              <Text>{state.value}</Text>
              <Button
                  title:"+1"
                  onPress={() => dispatch({type: 'increment'})}
              />
              <Button
                  title:"-1"
                  onPress={() => dispatch({type: 'decrement'})}
              />
          </View>
      );
    }
    export default Counter;
    

마치며


이번 포스팅에서는 React Component에서 TypeScript를 작성하는법과 TypeScriptHook을 작성하는 법에 대하여 알아보았습니다.
다음 시간에는 TypeScriptContext API를 사용하는 법에 대하여 알아보도록 하겠습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.