본문 바로가기
React

[React / typescript] typescript 예제 - 떽떽대는 개발공부

by 떽이 2021. 3. 17.

 

 

2021.03.15 - [React] - [React] typescript 시작하기 - 떽떽대는 개발공부

 

[React] typescript 시작하기 - 떽떽대는 개발공부

지금까지 포스팅은 javascript 로 react 프로젝트를 구성하는 것이었다. 오늘은 typescrypt 를 이용하여 react 프로젝트를 시작하는 법을 포스팅 해보겠다. 먼저 디렉토리를 하나 생성한다. mkdir study_typesc

ddeck.tistory.com

이전 글에서 typescript 로 react 프로젝트를 만들어 보았다.

이번 글에선 typescript 를 이용하여 예제를 한번 만들어 보았다.

아래의 블로그에서 예제를 참조하였다.

bkjeon1614.tistory.com/422

먼저 App.tsx 컴포넌트를 아래와 같이 구성할 것이다.

src/components/App.tsx

import * as React from 'react';

import { Profile } from '@components/study/Profile';
import { Counter } from '@components/study/Counter';
import { TodoList } from '@components/study/TodoList';

const App = () => {
  return (
    <div className='App'>
      <Profile name='name' job='job' />
      <Counter />
      <TodoList />
    </div>
  );
};

export default App;

총 3개의 컴포넌트로 각각 다른 작업을 구성할 것이다. 이 중 Profile 컴포넌트는 props 로 값을 전달한다.

먼저, Profile 컴포넌트는 아래와 같이 작성한다.

src/components/study/Profile.tsx

import * as React from 'react';

// props 받아올 값의 type 을 선언
interface Iprops {
    name: string,
    job: string,
}

		// props type 설정
export function Profile(props: Iprops){
    return(
        <div>
            <h2>프로필</h2>
            <div>
                <b>이름 : { props.name }</b>
            </div>
            <div>
                <b>직업 : { props.job }</b>
            </div>
        </div>
    )
}

위와 같이 간단하게 작성하면 아래와 같은 결과가 나온다.

부모 컴포넌트에서 props 값을 주고, 자식 컴포넌트에서는 prop 받을 값의 type 을 설정한 게 전부다.

다음은 Counter 를 구현 해보자.

src/components/study/Counter.tsx

import * as React from 'react';
import { useState } from 'react';

// state type 선언
interface Istate {
    counter: number
}

export function Counter() {
	// useState로 관리해 줄 state 의 type 설정
    const [state, setState] = useState<Istate>({
        counter: 0
    })

    const onIncrement = () => {
        setState({
            counter: state.counter+1
        })
    }

    const onDecrement = () => {
        setState({
            counter: state.counter-1
        })
    }

    return(
        <div>
            <h2>카운터</h2>
            <div>
                {state.counter}
            </div>
            <div>
                <button onClick={onIncrement}>+</button>
                <button onClick={onDecrement}>-</button>
            </div>
        </div>
    )
}

이 부분도 아주 간단하게 구성 하였다.

내가 작성한 컴포넌트는 함수형 컴포넌트 이므로 useState 를 사용하였다.

이제 내가 좀 애를 먹었던 TodoList 부분이다.

src/components/study/TodoList.tsx

import * as React from 'react';
import { useState, useEffect } from 'react';
import { TodoItem } from './TodoItem';

// 타입을 인터페이스로 선언
interface todoItem{
    item:string
}

interface ItodoItems {
    idx: number,
    item: string,
    isDelete: boolean
    onDelete?: Function,
}

interface ItodoList {
    todoItems: ItodoItems[],
}

export function TodoList() {
    const [todoItem, setTodoItem] = useState<todoItem>({
        item:''
    })
    const [todoList, setTodoList] = useState<ItodoItems>({
        idx: 0,
        item: '',
        isDelete: false,
    })
    const [todoData, setTodoData] = useState<ItodoList>({
        todoItems: [todoList],
    })
    

    const onSubmit = (e:React.FormEvent<HTMLFormElement>):void => {
        e.preventDefault() // 페이지 전환 막기
        setTodoList({
            idx: todoList.idx+1,
            item: todoItem.item,
            isDelete: false,
        })
    }

    useEffect(() => {
        setTodoItem({// input 창 초기화
            item: ''
        })

        setTodoData({
            todoItems: todoData.todoItems.concat(todoList),
        })
    }, [todoList])

    useEffect(() => {
        //console.log('todoData 변경 :: ', todoData )
    }, [todoData])

    const handleInput = (e:React.ChangeEvent<HTMLInputElement>) => {
        const {name, value} = e.target
        setTodoItem({
            item: value
        })
    }

    const onDelete = (idx: number) => {
        let temp2:Array<ItodoItems> = []
        const deleteTodoList = todoData.todoItems.map(
            data => {
                let temp1: ItodoItems = {
                    idx: data.idx,
                    item: data.item,
                    isDelete: data.idx === idx ? true : false
                }
                if(temp2.length < 1){
                    temp2 = [temp1]
                } else {
                    temp2 = temp2.concat(temp1)
                }
                setTodoData({
                    todoItems: temp3
                })
            }
        )
    }

    const TodoList = todoData.todoItems.map( 
        (data, idx) => (
            <React.Fragment key={idx}>
                <TodoItem
                    idx={data.idx}
                    item={data.item}
                    isDelete={data.isDelete}
                    onDelete={onDelete}
                />
            </React.Fragment>
        )
    )

    return(
        <div>
            <h2>할일</h2>
            <div>
                <form onSubmit={onSubmit}>
                    <input type='text' name='content' value={todoItem.item} onChange={handleInput} />
                    <button type='submit'>추가</button>
                </form>
            </div>
            <div>
                {TodoList}
            </div>
        </div>
    )
}

delete 부분에서 배열 때문에 애를 먹었다.

새 배열을 만들어 사용하는 것으로 정리하였다.

src/components/study/TodoItem.tsx

import * as React from 'react';

interface IList {
    idx: number,
    item: string,
    isDelete: boolean,
    onDelete: Function,
}

export function TodoItem(props: IList){
    const handleDelete = () => {
        props.onDelete(props.idx)
    }

    return(
        <div>
            {props.idx !== 0 && !props.isDelete && (
                <div>
                    {props.item}
                    <span onClick={handleDelete}>
                        삭제
                    </span>
                </div>
            )}
        </div>
    )
}

 

 

댓글