개발일지

나의 늦은 typescript 적응기 본문

개발일지/React-Native

나의 늦은 typescript 적응기

Seobe95 2022. 4. 26. 01:23

Typescript를 적용해야 한다

현재 React-native를 사용하면서 사내 프로젝트를 진행중인데, typescript를 사용하지 않고 진행하다 보니 수많은 상태와, 변수 등에 둘러싸여 작업을 진행하는데 계속해서 불편함을 가지고 있었다. typescript를 도입하게 된다면, 자동완성 및 프로젝트 관리에 효율적으로 진행 할 수 있을 것 같아 타입스크립트를 공부하려 한다. 

 

이번 포스팅을 하면서, redux-toolkit과 hooks를 이용하여, TextInput에 입력한 값을 나열하는 기본적인 프로젝트를 진행했다.

React-Native에 적용하기

npx react-native init "나의 앱 이름" --template react-native-template-typescript

 

Redux-toolkit 설정하기

프로젝트 폴더/src/slices 폴더를 생성해서, contentsSlice.ts 파일과 store.ts 파일을 생성했다.

// contentsSlice.ts

import {createSlice, PayloadAction} from '@reduxjs/toolkit';

export interface UpdateContents {
  id: number;
  contents: string | undefined;
}

const initialState = [
  {
    id: 0,
    contents: '기본 데이터',
  },
] as UpdateContents[];

export const contentsSlice = createSlice({
  name: 'contents',
  initialState,
  reducers: {
    update: (
      state: UpdateContents[],
      action: PayloadAction<UpdateContents>,
    ) => {
      return [...state, action.payload];
    },
  },
});

export default contentsSlice.reducer;
export const {update} = contentsSlice.actions;

1. interface

인터페이스는 상호 간에 정의한 약속 혹은 규칙을 의미한다. TS에서의 인터페이스는 보통 다음과 같은 범주에 대해 약속을 정의할 수 있다.

  • 객체의 스펙(속성과 속성의 타입)
  • 함수의 파라미터
  • 함수의 스펙(파라미터, 반환 타입 등)
  • 배열과 객체를 접근하는 방식
  • 클래스

(출처 : https://velog.io/@soulee__/TypeScript-Interface-2)

 

2. UpdateContents[]

UpdatteContents라는 객체의 스펙이 나열된 배열을 의미한다.

 

3. PayloadAction<UpdateContents>

PayloadAction은 액션의 payload 타입을 지정할 수 있게 도와주는 제네릭 이다.

제네릭이란, 단일 타입이 아닌 다양한 타입에서 작동하는 컴포넌트를 작성할 수 있고, 제네릭을 통해 여러 타입의 컴포넌트나 자신만의 타입을 사용할 수 있게 된다. Typescript에서 무언가를 반환하게 되는 경우, any타입을 사용한다면 어떤 값을 반환하더라도 에러가 나지 않지만, 반환하는 값의 타입이 어떤 값인지를 잃게 된다. 여기서 <>안에 타입 변수를 넣게 된다면 인수(반환값)의 타입을 캡쳐하고,  이 정보를 나중에 사용할 수 있도록 한다.

// store.ts

import {combineReducers, configureStore} from '@reduxjs/toolkit';
import contentsSlice from './contentsSlice';

const reducer = combineReducers({
  contents: contentsSlice,
});

const store = configureStore({
  reducer,
});

export type ReducerType = ReturnType<typeof reducer>;
export type AppDispatch = typeof store.dispatch;

export default store;

store.ts 는 다음을 참고하여 작성(https://redux-toolkit.js.org/usage/usage-with-typescript)

//App.tsx

import React from 'react';
import {Provider} from 'react-redux';
import store from './src/slices/store';
import Main from './src/screens/Main';
function App() {
  return (
    <Provider store={store}>
      <Main />
    </Provider>
  );
}

export default App;

Provider를 이용하여 상태를 주입해준다.

Hooks 이용하기

//useUpdateContents.ts

import {useDispatch, useSelector} from 'react-redux';
import {ReducerType} from '../slices/store';
import {UpdateContents, update} from '../slices/contentsSlice';
export default function useUpdateContents() {
  const dispatch = useDispatch();
  const contents = useSelector<ReducerType, UpdateContents[]>(
    store => store.contents,
  );

  const onUpdateContents = (data: UpdateContents) => {
    dispatch(update(data));
  };

  return {
    contents,
    onUpdateContents,
  };
}


//useInput.ts

import {useState} from 'react';

export default function useInput() {
  const [value, setValue] = useState<string | undefined>('');

  const onChangeText = (data: string) => {
    setValue(data);
  };

  return {
    value,
    onChangeText,
  };
}

contents의 상태를 업데이트하는 onUpdateContents, 현재 contents 상태값을 불러오는 contents를 반환하는 useUpdateContents.ts를 만들고, input값을 변경하고 input값을 가져오는 useInput.ts도 생성하였다.

 

그리고, Typescript에서의 props 연습을 위해, TextInput을 재사용 가능한 컴포넌트로 만들어 사용하였다.

 

// CustomInput.tsx

import {TextInput, StyleSheet} from 'react-native';
import React from 'react';

interface CustomInputProps {
  value: string | undefined;
  onChangeText: (data: string) => void;
  onSubmitEditing: () => void;
}

export default function CustomInput({
  value,
  onChangeText,
  onSubmitEditing,
}: CustomInputProps) {
  return (
    <TextInput
      value={value}
      onChangeText={onChangeText}
      style={styles.inputContainer}
      onSubmitEditing={onSubmitEditing}
    />
  );
}

const styles = StyleSheet.create({
  inputContainer: {
    borderWidth: 1,
    borderColor: '#333333',
    borderRadius: 5,
    width: '80%',
    paddingHorizontal: 16,
    paddingVertical: 5,
  },
});

1. Typescript에서의 함수

Typescript에서는 함수를 사용할 때 인자를 사용한다면 인자의 타입도 설정하고, return값이 존재한다면 return값에 대한 타입 또한 지정해야한다.

 

위와 같은 경우는 onChangeText에서 인자를 사용하여 인자값의 타입을 string으로 사용하고, void를 사용하여 return값이 없다는 선언을 해준다. 

 


최종 코드

// Main.tsx

import React from 'react';
import {
  View,
  SafeAreaView,
  Text,
  TouchableOpacity,
  StyleSheet,
} from 'react-native';
import CustomInput from '../components/CustomInput';
import useInput from '../hooks/useInput';
import useUpdateContents from '../hooks/useUpdateContents';
function Main() {
  const userId = useInput();
  const {contents, onUpdateContents} = useUpdateContents();
  const onPress = () => {
    const data = {
      id: contents[contents.length - 1].id + 1,
      contents: userId.value,
    };
    onUpdateContents(data);
    userId.onChangeText('');
  };

  return (
    <SafeAreaView>
      <View style={styles.inputContainer}>
        <CustomInput
          value={userId.value}
          onChangeText={userId.onChangeText}
          onSubmitEditing={onPress}
        />
        <TouchableOpacity onPress={onPress} style={styles.buttonContainer}>
          <Text>버튼</Text>
        </TouchableOpacity>
      </View>
      <View>
        {contents.map(item => {
          return (
            <View key={item.id}>
              <Text>{item.contents}</Text>
            </View>
          );
        })}
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  inputContainer: {
    flexDirection: 'row',
    width: '100%',
    justifyContent: 'space-around',
  },
  buttonContainer: {
    paddingHorizontal: 10,
    paddingVertical: 5,
    justifyContent: 'center',
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#333333',
    borderRadius: 5,
    backgroundColor: '#e3e3e3',
  },
});
export default Main;

위의 컴포넌트들과 hooks, 그리고 redux-toolkit을 이용하여 textinput에 값을 입력하고 그 값이 나열되는 프로젝트를 진행하였다.