25_타입스크립트 & 리액트
FrontEnd/React

25_타입스크립트 & 리액트

728x90

타입스크립트를 이용해서 리액트 프로젝트를 만들어보자.

 

리액트에서 type스크립트를 지원하기에 별도의 설정은 필요없지만 프로젝트를 실행할때 typescript를 사용한다고 표시만 하면 된다.

 

npx create-react-app my-app --template typescript

리액트 프로젝트를 만들때 뒤에 --typescript를 붙이면 된다.

 

이미 만든 프로젝트에 typescript를 적용하고 싶다면 아래 링크를 참조하자

https://create-react-app.dev/docs/adding-typescript/

 

Adding TypeScript | Create React App

Note: this feature is available with react-scripts@2.1.0 and higher.

create-react-app.dev

 

 

 

typescript 파일이기때문에

App.tsx 파일로 생성되는것을 볼 수 있다.

 

 

 

지난 글들에서는 App을 function으로 선언해서 사용했는데  화살표함수를 써서 아래와 같이 작성했을때의 장단점을 알아보자.

import React from 'react';
import Greetings from './Greetings';


const App: React.FC =() => {    //화살표함수로 선언
  return (
    <Greetings name = "정민규"/>
  );
}

export default App;

 

 

아래 코드에서 볼 수 잇듯이 childeren 값을 따로 넣지 않아도 들어있다는 장점이 있다. 

 

//Greetings.tsx
import React from 'react';

type GreetingsProps = {
    name : string;
}

const Greetings : React.FC<GreetingsProps> = ({name}) => {   //children이란 값을 따로 선언하지 않아도 들어있다.
    return <div>나는 {name}!!!</div>;
};

export default Greetings;

 

 

단점으로는 defaultProps가 제대로 작동하지 않는다는 치명적 단점이 작용한다.

 

//Greetings.tsx
import React from 'react';

type GreetingsProps = {
    name : string;
    //mark : string;   //error
    mark? : string;   // 그래도 문제가 존재 -> string or undefind로 존재하게 됨 
}

const Greetings : React.FC<GreetingsProps> = ({name,mark}) => {   //children이란 값을 따로 선언하지 않아도 들어있다.
    return <div>나는 {name}!!!</div>;
};

Greetings.defaultProps = {
	mark : '!',
}

export default Greetings;

위같은 코드인 경우에도 제대로 작동하지 않는다.

 

 

제대로 작동시키기 위해서는 아래와 같이 작성해야 한다.

 

//Greetings.tsx
import React from 'react';

type GreetingsProps = {
    name : string;
    //mark : string;   //error
    mark? : string;   // 그래도 문제가 존재 -> string or undefind로 존재하게 됨 
}

const Greetings : React.FC<GreetingsProps> = ({name,mark = '!'}) => {   //children이란 값을 따로 선언하지 않아도 들어있다.
    return <div>나는 {name}!!!</div>;
};

export default Greetings;

 

위 코드들을 일반 function 를 사용해서 작성하면 아래와 같다.

 

import React from 'react';

type GreetingsProps = {
  name: string;
  mark: string;
};

function Greetings({ name, mark }: GreetingsProps) {
  return (
    <div>
      Hello, {name} {mark}
    </div>
  );
}

Greetings.defaultProps = {
  mark: '!'
};

export default Greetings;

이전에 했었던 것처럼 defaultProps가 제대로 작동한다! mark? 를 쓰지않아도 잘 작동함을 알 수 있다.

Props가 있을때만 쓰고싶으면 ?를 사용해도 무방하다.

 

 

 

함수를 Props로 받아올수도 있다.

//Greetings.tsx
import React from 'react';

type GreetingsProps = {
  name: string;
  mark: string;
  optional?: string;
  onClick : (name : string) => void; //함수타입을 가져오고 싶을때
};

function Greetings({ name, mark, optional, onClick }: GreetingsProps) {
    const handleClick = () => onClick(name);
  return (
    <div>
      Hello, {name} {mark}
      {optional && <p>{optional}</p>}
      <div>
          <button onClick={handleClick}>버튼이지롱</button>
      </div>
    </div>
  );
}

Greetings.defaultProps = {
  mark: '!'
};

export default Greetings;

 

//App.tsx
import React from 'react';
import Greetings from './Greetings';


const App: React.FC =() => {    //화살표함수로 선언
  const onClick = (name : string) => {
    console.log(name);
  }
  return (
    <Greetings name = "정민규" onClick={onClick}/>
  );
}

export default App;

 

위처럼 작성하고 실행해보면

 

실행도 잘되고 버튼을 누르면 console에도 잘 출력되는것을 확인할 수 있다.

 

 

useState

useState를 사용해서 변화한는 값을 다루어보자.

 

아주간단한 counter예제를 만들어보자. counter예제는 이제 많이 만들어봐서 이해하기 편할 것이다.

 

import React, { useState } from 'react';

function Counter() {
    const [count,setCount] = useState<number>(0);  //counter의상태를 0으로 설정 number를 생략하면 자동으로 추출해줌
    const onIncrease = () => setCount(count + 1);
    const onDecrease = () => setCount(count - 1);

    return (
        <>
            <div>
                <h1>{count}</h1>
            </div>
            <button onClick={onIncrease}>증가</button>
            <button onClick={onDecrease}>감소</button>
        </>
    )

}

export default Counter;

이후, App.js에서 불러오기만 하면 된다. 사실 counter같은 간단한 예제에서는 JS와 코드내용이 크게 다를것이 없다. 파일의 확장자가 tsx인점만 다르다.

 

 

 

이번엔 조금더 복잡한 input으로 값을 넣어주는 Form예제를 다뤄보자.

 

import React, { useState } from "react";


// type Params = {
//     name : string;
//     description : string;
// }     
//이부분을 onSubmit : (form : Params ) => void 와 동일

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 : React.ChangeEvent<HTMLInputElement>) => {
       const { name, value} = e.target;
       setForm({
           ...form,
           [name] : value
       })
    }
    const handleSubmit = (e : React.FormEvent<HTMLFormElement> ) =>{
       e.preventDefault(); //submit시 새로고침 방지
       onSubmit(form);
       setForm({
           name : '',
           description : ''
       }) 
    }

    return (
        <form onSubmit={handleSubmit}>
            <input name="name" value = {name} onChange={onChange} />
            <input name="description" value = {description} onChange={onChange} />
            <button type="submit">등록</button>
        </form>
    )

}

export default MyForm;

위 코드역시 Js로 만든것과 크게 다르지는 않다. 

 

App.js에서 Counter밑에 MyForm을 불러오고 실행하면

 

잘 작동하고, 밑에 값도 잘 저장되는것을 확인할 수 있다.

 

 

이번엔 useReducer를 사용해보자.

 

좀전에 만든 Counter예제를 Reducer를 사용해서 바꾸어보자.

 

더보기
import React, { useReducer } from 'react';

type Action = { type : 'INCREASE' } | {type : 'DECRESE' };

function reducer(state : number, action : Action) : number {
    switch (action.type) {
        case 'INCREASE':
            return state +1
        case 'DECRESE':
            return state -1
        default : 
            throw new Error ('에러 발생!!!')
    }
}

function Counter() {
    
    const [count,dispatch] = useReducer(reducer,0);
    

    const onIncrease = () => dispatch({type : 'INCREASE'});
    const onDecrease = () => dispatch({type : 'DECRESE'});

    return (
        <>
            <div>
                <h1>{count}</h1>
            </div>
            <button onClick={onIncrease}>증가</button>
            <button onClick={onDecrease}>감소</button>
        </>
    )

}

export default Counter;

확실히 타입을 정해두니 일일히 찾지 않아도 되고 헷갈리는 일이 적어진거 같다.

 

조금 더 복잡한 예제를 보자.

 

import React, {useReducer } from "react";

type Color = 'red' | 'orange' | 'yellow';


type State = {
    count : number;
    text : string;
    color : Color;
    isGood : boolean;
}

type Action =     //Action Type 정의
    | { type : 'SET_COUNT'; count : number }
    | { type : 'SET_TEXT'; text : string}
    | { type : 'SET_COLOR'; color : Color}
    | { type : 'TOGGLE_GOOD'};



function reducer(state : State, action : Action) : State {
    switch (action.type) {
        case 'SET_COUNT':
            return{
                ...state,
                count: action.count
            }
        case 'SET_TEXT':
            return {
                ...state,
                text : action.text
            }
        case 'SET_COLOR':
            return {
                ...state,
                color : action.color
            }
        case 'TOGGLE_GOOD':
            return{
                ...state,
                isGood : !state.isGood
            }
        default:
            throw new Error("에러 발생!!");
            
    }
}


function ReducerSample() {
    const [state,dispatch] = useReducer(reducer,{
        count : 0,
        text : '정민규',
        color : 'red',
        isGood : true
    });

    const setCount = () => dispatch({ type : 'SET_COUNT', count : 5});
    const setText = () => dispatch ({ type : 'SET_TEXT', text : '나는 정민규'});
    const setColor = () => dispatch ({ type : 'SET_COLOR', color : 'orange'});
    const toggleGood = () => dispatch ({ type : 'TOGGLE_GOOD'});

    return (

        <div>
            <p>
                <code>count : </code> {state.count}
            </p>
            <p>
                <code>text : </code> {state.text}
            </p>
            <p>
                <code>color : </code> {state.color}
            </p>
            <p>
                <code>isGood : </code> {state.isGood ? 'true' : 'false'}
            </p>
            <div>
                <button onClick={setCount}>SET_COUNT</button>
                <button onClick={setText}>SET_TEXT</button>
                <button onClick={setColor}>SET_COLOR</button>
                <button onClick={toggleGood}>TOGGLE_GOOD</button>
            </div>
        </div>
    )
}

export default ReducerSample;

 

크게 어렵다기보단, 값을 4개를 두어서 내가 설정한 값으로 바꾸는 간단한 코드이다. reducer에 대해서 공부했었다면 어렵지 않게 볼 수 있다. 코드를 직접 작성하면서 든 생각은 자동완성기능이 잘되어있어서 정말 편하다는 생각이었다.

 

버튼을 누르면 설정해둔 값으로 바뀐다! TOGGLE_GOOD같은 경우 값이 반전이 되게 된다.

 

 

 

 

Context API 활용


typescript에서 Context API는 아래와 같은 형식으로 사용된다.

type FooValue = {
    foo : number;
};

const FooContext = createContext<FooValue | null>(null)  //기본값으로 null을 넣음

 

 

방금 만들었던 리듀서를 옮겨보자. SampleContext.tsx파일을 하나 만들고  Reducersample.tsx에서 가져온다.

//SampleContext.tsx

import React, { createContext, Dispatch, useContext, useReducer } from "react";

type Color = "red" | "orange" | "yellow";

type State = {
  count: number;
  text: string;
  color: Color;
  isGood: boolean;
};

type Action =  //Action Type 정의
  | { type: "SET_COUNT"; count: number }
  | { type: "SET_TEXT"; text: string }
  | { type: "SET_COLOR"; color: Color }
  | { type: "TOGGLE_GOOD" };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "SET_COUNT":
      return {
        ...state,
        count: action.count,
      };
    case "SET_TEXT":
      return {
        ...state,
        text: action.text,
      };
    case "SET_COLOR":
      return {
        ...state,
        color: action.color,
      };
    case "TOGGLE_GOOD":
      return {
        ...state,
        isGood: !state.isGood,
      };
    default:
      throw new Error("에러 발생!!");
  }
}

const SampleStateContext = createContext<State | null>(null);
const SampleDispatchContext = createContext<Dispatch<Action> | null>(null); //리액트안에 Dispatch가 있기에 불러내서 사용

type SampleProviderProps = {
  children: React.ReactNode;
};

export function SampleProvider({ children }: SampleProviderProps) {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    text: "정민규",
    color: "red",
    isGood: true,
  });

  return (
    <SampleStateContext.Provider value={state}>
      <SampleDispatchContext.Provider value={dispatch}>
        {children}
      </SampleDispatchContext.Provider>
    </SampleStateContext.Provider>
  );
}

export function useSampleState() {
  const state = useContext(SampleStateContext);
  if (!state) throw new Error("cannot find SampleProvider");
  return state;
}

export function useSampleDispatch() {
  const dispatch = useContext(SampleDispatchContext);
  if (!dispatch) throw new Error("cannot find SampleProvider");
  return dispatch;
}

그리고 밑에 createContext를 통해 useContext를 만들어주고, 훅 2개를 만들어줘서 밖에서도 쉽게 사용할 수 있게 해준다.

 

//ReducerSample.tsx
import React, { useReducer } from "react";
import { useSampleDispatch, useSampleState } from "./SampleContext";

function ReducerSample() {
  const state = useSampleState();   //useContext 사용
  const dispatch = useSampleDispatch();

  const setCount = () => dispatch({ type: "SET_COUNT", count: 5 });
  const setText = () => dispatch({ type: "SET_TEXT", text: "나는 정민규" });
  const setColor = () => dispatch({ type: "SET_COLOR", color: "orange" });
  const toggleGood = () => dispatch({ type: "TOGGLE_GOOD" });

  return (
    <div>
      <p>
        <code>count : </code> {state.count}
      </p>
      <p>
        <code>text : </code> {state.text}
      </p>
      <p>
        <code>color : </code> {state.color}
      </p>
      <p>
        <code>isGood : </code> {state.isGood ? "true" : "false"}
      </p>
      <div>
        <button onClick={setCount}>SET_COUNT</button>
        <button onClick={setText}>SET_TEXT</button>
        <button onClick={setColor}>SET_COLOR</button>
        <button onClick={toggleGood}>TOGGLE_GOOD</button>
      </div>
    </div>
  );
}

export default ReducerSample;

 

2줄을 추가해줘서 context를 불러온 후에, App.tsx에서

 

return (
    <>
      {/* <Counter/>
      <MyForm onSubmit={onSubmit}/> */}
      <SampleProvider>
        <ReducerSample/>
      </SampleProvider>
    </>
  );

SampleProvider로 감싸주면 이전과 같은 기능이 동작한다.

728x90

'FrontEnd > React' 카테고리의 다른 글

27_타입스크립트 & 리덕스 미들웨어  (0) 2022.01.10
26_타입스크립트 & 리덕스  (0) 2022.01.07
23_리액트 리덕스 미들웨어(2)  (0) 2022.01.05
22_리덕스 미들웨어  (0) 2022.01.05
21_리액트_리덕스  (0) 2022.01.02