dwook.record
Published on

타입스크립트 리액트 Hooks

Authors
  • avatar
    Name
    dwook

Table of Contents

useState

interface Payload {
    text: string;
}

function App() {
    const [payload, setPayload] = useState<Payload | null>(null); // payload의 타입을 제네릭으로 확인
}
import { useState } from 'react';

interface Product {
  name: string;
  price: number;
}

function App() {
  const [count, setCount] = useState<number>(0); // 값의 사용
  const [products, setProducts] = useState<Product[]>([]); // 배열 사용
  const [product, setProduct] = useState<Product>({} as Product); // 타입 캐스팅 방식
  const [product, setProduct] = useState<Product>({ name: '', price: 0 }); // 초기값 설정 방식

  return (
    <div className="App">
      {count}
      {products.map((product: Product, productIdx: number) => (
        <div>
          <h1>{product.name}</h1>
          <span>{product.price}</span>
        </div>
      ))}
      <h1>{ product.name}</h1>
      <span>{ product.price }</span>
    </div>
  );
}

export default App;

useEffect

function App() {
    const [payload, setPayload] = useState<Payload | null>(null);

    useEffect(() => {
        fetch("/data.json")
            .then(res => res.json())
            .then(data => {
                setPayload(data);
            });
    }, [])

    return (
        <div>{JSON.stringify(payload)}</div>
    )
}
  • useEffect를 사용할 때 function 또는 undefined 이외의 것을 반환하지 않아야한다.
function DelayedEffect({ timerMs }: { timerMs: number }) {

  useEffect(
    () =>
      setTimeout(() => {
        /* do stuff */
      }, timerMs),
    [timerMs]
  );
  // 나쁜 사례!
    // 화살표함수 본문이 중괄호로 감싸지지 않았기 때문에 setTimeout은 암묵적으로 숫자를 리턴.
  return null;
}
function DelayedEffect({ timerMs }: { timerMs: number }) {

  useEffect(() => {
    setTimeout(() => {
      /* do stuff */
    }, timerMs);
  }, [timerMs]);
  // better; void 키워드를 사용하여 undefined를 반환하는지 확인.
  return null;
}

useReducer

interface Todo {
    id: number;
    done: boolean;
    text: string;
}

type ActionType =
    | { type: "ADD"; text: strinf }
    | { type: "REMOVE"; id: number };

function App() {
    const [todos, dispatch] = useReducer((state: Todo[], action: ActionType) => {
            switch (action.type) {
                case "ADD":
                    return [
                        ...state,
                        {
                            id: state.length,
                            text: action.text,
                            done: false,
                        },
                    ];
                case "REMOVE":
                    return state.filter(({ id }) => id !== action.id);
                default:
                    throw new Error();
            }
        }, []);

    return <>
        {todos.map((todo) => (
            <div key={todo.id}>
                {todo.text}
                <button
                    onClick={() =>
                        dispatch({
                            type: "REMOVE",
                            id: todo.id,
                        })
                    }
                >
                    Remove
                </button>
            </div>
        ))}
        </>
}
import { useReducer } from 'react';

const initialState = { count: 0 };

type ACTION =
  | { type: "increment"; payload: number }
  | { type: "decrement"; payload: string };

function reducer(state: typeof initialState, action: ACTION) {
switch (action.type) { case "increment": return { count: state.count + action.payload }; case "decrement": return { count: state.count - action.payload }; default: throw new Error(); } } function App() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <div> <button onClick={() => dispatch({ type: "decrement", payload: 1 })}>-</button> <button onClick={() => dispatch({ type: "increment", payload: 1 })}>+</button> </div> </> ); } export default App;

useRef

  • useRef는 DOM 또는 값을 가짐
function App() {
    const newTodoRef = useRef<HTMLInputElement>(null);
  const onAddTodo = useCallback(() => {
    if (newTodoRef.current) {
      dispatch({
        type: "ADD",
        text: newTodoRef.current.value,
      });
      newTodoRef.current.value = "";
    }
  }, []);

    return <>
        <div>
            <input type="text" ref={newTodoRef} />
            <button onClick={onAddTodo}>Add Todo</button>
        </div>
    <>
}
import { useRef } from 'react';

function App() {
  const domRef = useRef<HTMLDivElement | null>(null);
  const valRef = useRef<Number | null>(1);

  return (
    <>
      <div ref={domRef}>etc</div>
    </>
  );
}

export default App;

Advanced Properties

커스텀 hook 사용하지 않을 때(useState)

const Incrementer: React.FunctionComponent<{
value: number;
setValue: React.Dispatch<React.SetStateAction<number>>;
}> = ({ value, setValue }) => ( <Button onClick={() => setValue(value + 1)} title={`Add - ${value}`} /> ); function App() {
const [value, setValue] = useState(0);
return <> <Incrementer value={value} setValue={setValue} /> </> }

커스텀 hook 사용할 때(useNumber)

const useNumber = (initialValue: number) => useState<number>(initialValue);

type UseNumberValue = ReturnType<typeof useNumber>[0]; // number
type UseNumberSetValue = ReturnType<typeof useNumber>[1]; // React.Dispatch<React.SetStateAction<number>>
const Incrementer: React.FunctionComponent<{
value: UseNumberValue;
setValue: UseNumberSetValue;
}> = ({ value, setValue }) => ( <Button onClick={() => setValue(value + 1)} title={`Add - ${value}`} /> ); function App() {
const [value, setValue] = useNumber(0);
return <> <Incrementer value={value} setValue={setValue} /> </> }

Custom Hooks

  • 함수를 만들고 내보낼 때, useCallback 사용 권장
useTodos.ts
import { useCallback, useReducer } from "react";

type ActionType =
  | { type: "ADD"; text: string }
  | { type: "REMOVE"; id: number };

interface Todo {
  id: number;
  done: boolean;
  text: string;
}

export function useTodos(initialTodos: Todo[]): {
todos: Todo[];
addTodo: (text: string) => void;
removeTodo: (id: number) => void;
} { const [todos, dispatch] = useReducer((state: Todo[], action: ActionType) => { switch (action.type) { case "ADD": return [ ...state, { id: state.length, text: action.text, done: false, }, ]; case "REMOVE": return state.filter(({ id }) => id !== action.id); default: throw new Error(); } }, initialTodos);
const addTodo = useCallback((text: string) => {
dispatch({ type: "ADD", text, }); }, []);
const removeTodo = useCallback((id: number) => {
dispatch({ type: "REMOVE", id, }); }, []);
return { todos, addTodo, removeTodo };
}
App.tsx
import { useTodos } from "./useTodos";

function App() {
  const { todos, addTodo, removeTodo } = useTodos([
    { id: 0, text: "Hey there", done: false },
  ]);

    const newTodoRef = useRef<HTMLInputElement>(null);

  const onAddTodo = useCallback(() => {
    if (newTodoRef.current) {
      addTodo(newTodoRef.current.value);
      newTodoRef.current.value = "";
    }
  }, [addTodo]);

    return <>
        {todos.map((todo) => (
            <div key={todo.id}>
                {todo.text}
                <button onClick={() => removeTodo(todo.id)}>Remove</button>
            </div>
        ))}
        <div>
            <input type="text" ref={newTodoRef} />
            <Button onClick={onAddTodo}>Add Todo</Button>
        </div>
    </>

}