[맛보기 프로젝트] react + typescript todolist

Darlene·2022년 6월 27일
1

TIL

목록 보기
10/10
post-thumbnail

목표

  • typescript 사용보기
  • 커스텀 hook 만들어서 사용해보기
  • render hooks 패턴 적용해보기

STEP 1. typescript 적용해보기

일반적으로 관심사 분리만 생각하여 props로 전달하는 방식으로 개발


// TodoPage 컴포넌트
import { useState } from 'react';

type Todo = {
	id : number,
	value : string,
	status : string,
	isChecked : boolean,
}

const TodoPage : React.FC = () => {
	const [todos, setTodos] = useState<Todo []>([]);
	const [todo, setTodo] = useState<Todo>({
		id : 0,
		value : '',
		status : 'TODO',
		isChecked : false,
	});

	const onChange = (e:React.ChangeEvent<HTMLInputElement>) => {
		setTodo({
			...todo,
			value:e.target.value,
		})
	};

	const addTodo = () => {
		setTodos([
			...todos, 
			todo
		]);

		setTodo({
			...todo,
			id: todo.id + 1,
			value: '',
		})
	};

	return (
    <div>
			<Input value={todo.value} onChange={(e) => onChange(e)} />
			<Button name={'추가'} onClick={addTodo}/>
			{todos.map((i, idx) => 
				<div key={idx}>
					{i.value}
				</div>
			)}
    </div>
	)
}

export default TodoPage;

STEP 2. custom hook 사용해서 todolist를 리팩토링

// TodoPage 컴포넌트
import useTodo from 'hooks/useTodo';

const TodoPage : React.FC = () => {
	const {todo, todos, onChange, addTodo } = useTodo({
		id : 0,
		value : '',
		status : 'TODO',
		isChecked : false,
	});

	return (
    <div>
		<Input value={todo.value} onChange={onChange} />
		<Button name={'추가'} onClick={addTodo}/>
		{todos.map((i, idx) => 
 			<div key={idx}>
 				{i.value}
 			</div>
 		)}
    </div>
	)
}
// useTodo hook 컴포넌트
import { useState } from "react";

type Todo = {
	id : number,
	value : string,
	status : string,
	isChecked : boolean,
}

const useTodo = (initialTodo: Todo) => {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [todo, setTodo] = useState<Todo>(initialTodo);

  const inputFormChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTodo({
      ...todo,
      value: e.target.value,
    })
  }

	const hadleAddTodo = () => {
		setTodos([
			...todos, 
			todo
		]);

		setTodo({
			...todo,
			id: todo.id + 1,
			value: '',
		})
	}

  return {
    todo,
    todos,
    onChange : inputFormChange,
    addTodo : hadleAddTodo,
  }
}

export default useTodo;

custom hook을 이용해서 UI와 로직을 분리했을때의 장점

기능은 동일하지만 UI가 다른 형태의 컴포넌트가 5개 있다고 가정해보겠습니다.
이러한 경우 useTodo로 커스텀 hook을 만들어서 사용하면 로직 재사용이 가능합니다.

리액트에서 재사용성에 대해 집중하고 있는데,
UI 즉, 스타일이 다르다면 결국 컴포넌트를 재사용한다라는 것은 어렵다고 생각합니다.
컴포넌트 재사용에 대한 개념을 잘못 이해하다 보면 코드가 오히려 복잡해지고, 의존성이 강한 코드가 되어 버려 유지 보수가 힘들어 지는 코드가 되어 버립니다.

커스텀 훅을 잘 사용하면 진짜 리액트에서 강조하는 재사용을 할 수 있습니다.

처음부터 큰 숲을 생각하여 설계가 잘된 코드가 나오면 좋겠지만, 설계를 잘하는 일은 쉽지 않다. 많은 경험과 리팩토링을 통해 배울 수 있다고 합니다.
그렇지만 리액트 컴포넌트 설계뿐만 아니라 재사용에 대해 충분한 고민을 통해 좋은 코드로 나아갈 수 있기를 바래봅니다.

STEP 3. render hooks을 이용해서 todolis를 리팩토링

// TodoPage 컴포넌트
import useTodo from 'hooks/useTodo';

const TodoPage : React.FC = () => {
	const {todo, onChange, addTodo, renderTodos} = useTodo({
		id : 0,
		value : '',
		status : 'TODO',
		isChecked : false,
	});

	return (
    <div>
			<Input value={todo.value} onChange={onChange} />
			<Button name={'추가'} onClick={addTodo}/>
			{renderTodos()}
    </div>
	)
}

export default TodoPage;
// useTodo hook 컴포넌트
import { useState } from "react";

type Todo = {
	id : number,
	value : string,
	status : string,
	isChecked : boolean,
}

const useTodo = (initialTodo: Todo) => {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [todo, setTodo] = useState<Todo>(initialTodo);

  const inputFormChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTodo({
      ...todo,
      value: e.target.value,
    })
  }

	const hadleAddTodo = () => {
		setTodos([
			...todos, 
			todo
		]);

		setTodo({
			...todo,
			id: todo.id + 1,
			value: '',
		})
	}

  const renderTodos = () => {
   return (
     <div>
       {todos.map((i, idx) =>
				  <div key={idx}>{i.value}</div>
			)}
     </div>
   )
  }

  return {
    todo,
    onChange : inputFormChange,
    addTodo : hadleAddTodo,
    renderTodos,
  }
}

export default useTodo;

render hooks 패턴이란?

커스텀훅에 해당 상태값들을 렌더링시켜주는 책임도 가지고 이 책임을 함수로 반환하는 패턴입니다. 자세한 내용은 아래 라인기술블로그에 나와있습니다.

https://engineering.linecorp.com/ko/blog/line-securities-frontend-3/

결국 step2에서 개발한 커스텀훅에 할일목록을 렌더링하는 책임도 useTodo훅으로 넘겨주는것 입니다. renderHooks를 사용하면 어떤 장점이 있을까요? 투두리스트 화면의 복잡도가 높아져서 page역할을 하는 컴포넌트에 여러 컴포넌트가 복잡하게 섞여있고 이를 렌더링하는 로직이 들어가게되면 페이지컴포넌트에서 적절하게 원하는 ui를 배치한다는 책임을 넘어서서 여러 로직을 처리해야하는 일이 발생합니다. 하지만, 각 state를 렌더링시켜주는 책임을 각 customHook으로 넘겨주면 페이지컴포넌트에서는 페이지에 적절한 ui컴포넌트를 배치한다는 책임만 가지므로 복잡성을 다루기 좋지 않을까 생각합니다. 예를들어 같은 상태값에 대해서 조건부 렌더링을 처리해야할경우 이를 render함수에서 처리한다면 페이지컴포넌트에서는 이런 구체적인 세부사항을 알지 못하더라도 화면에 ui를 배치한다는 역할만 충실히 할 수 있습니다.

또한, 상태값별 여러가지 케이스에 해당하는 렌더링을 다르게 처리해야한다면 로직으로써의 커스텀훅과 렌더링으로써의 훅을 또다시 조합해서 로직재사용 + ui 렌더링별 훅을 적절하게 조합해서 사용할 수 있을것 같습니다.

render hooks의 장점과 기대되는 내용만 적었지만 결국 어떻게 설계했냐에 따라서 좋은설계가 될수도 있고 이상한 설계가 될수도 있을것 같습니다.

0개의 댓글