React

[React] 리액트 개념 정리 #3

https.. 2025. 10. 26. 18:20

6장

1. useState 훅

함수 컴포넌트에서 상태를 이용하기 위해 사용

기본적인 사용 방법

const [getter, setter] = useState<StateType>(initialValue);
// getter: 읽기 전용 속성
// setter: 상태를 변경할 때 사용하는 함수
// StateType: 상태 데이터의 타입
// initialValue: 상태 초기값

 

예시

import { ChangeEvent, useState } from 'react'

const App = () => {
	const [msg, setMsg] = useState<string>("");
    
    return (
   		<div>
        	<input type="text" value={msg}
            	onChange={(e: ChangeEvent<HTMLInputElement>)=>setMsg(e.target.value)} />
            <br />
            <span>입력 메시지: {msg}</span>
        </div>
    );
};

export default App;

 

2. useEffect 훅

함수 컴포넌트에서 부수효과(side effect)를 처리할 때 사용

side effect는 기본적으로 UI 그리는 게 목적

-> useEffect는 렌더링 후 어떤 작업을 할지 알려주는 방법.

즉, 컴포넌트가 마운트된 후 실행되는 것이다...(렌더링 이후. 렌더링이 화면에 반영된 후)

useEffect(effectCallback[, depsList]);
// effectCallback: 필수로 작성해야 하는 함수로, 클린업 함수를 리턴할 수 있다.
// depsList: 선택적으로 전달하는 의존 객체 배열 값

depsList: 의존 객체 배열. depsList에 지정된 상태나 속성이 변경되면 effectCallback을 호출함

이 값이 없으면, 빈 배열 [ ] 이면 컴포넌트가 처음 마운트될 때 1번만 실행.

depsList 지정되지 않았거나 속성이 변할 때는 호출하지 않음

 

장점

1. 한 컴포넌트 내부에 useEffect 여러개 사용 가능

상태와 상태 관련 로직을 중심으로 useEffect 훅을 작성할 수 있어서 관련 코드들이 모여있으므로 코드 이해하기 편함

 

3. 클린업 함수

() => void 의 형태

effectCallback 함수 내부에서 클린업 함수를 리턴하도록 작성할 수 있음.

컴포넌트가 언마운트될 때 실행된다.

import { useEffect, useState } from 'react'
import DateAndTime from 'date-and'time'

type Props = {
	formatString: string;
}

const Clock2 = (props: Props) => {
	const [currentTime, setCurrentTime] = useState<Date>(new Date());
    useEffect(() => {
    	const handle = setInterval(() => {
        	console.log("## tick!");
            setCurrentTime(new Date());
        }, 1000);
        
        return () => {
        	clearInterval(handle);
        };
    }, []);
    
    return (
    	<div className="boxStyle">
        	<h3>{DateAndTime.format(currentTime, props.formatString)}</h3>
        </div>
    );  
};

export default Clock2;

 

useEffect 훅을 이용해 컴포넌트가 마운트될 때 setInterval() 을 이요해 1초마다 currentTime 상태를 현재 시각으로 변경한다.

depsList 인자로 빈 배열을 전달했으므로 컴포넌트가 마운트될 때만 effectCallback 함수가 실행된다.

 

컴포넌트가 언마운트될 때는 effectCallback 함수가 리턴하는 클린업 함수에서 clearInterval() 함수를 이용해 1초 간격으로 실행하고 있던 함수와의 연결을 해제한다.

cleanInterval: 반복 작업을 중지시키는 것

 

한 컴포넌트 내부에 useEffect 훅을 여러개 사용한 예제

import { ChangeEvenet, useEffect, useState } from 'react'

const App = () => {
	const [count, setCount] = useState<number>(0);
    const [name, setName] = useState<string>("아이유");
    
    useEffect(() => {
    	console.log(`이름: ${name}`);
    }, [name]);
    
    useEffect(() => {
    	console.log(`카운트: ${count}`);
    }, [count]);
    
    .... (생략)
};

export default App;

 

리액트 훅의 생명 주기

- 컴포넌트가 마운트될 때

단계 설명
레이지 초기화 레이지 초기화(lazy initalizer)를 실행한다. 레이지 초기화는 useState()나 useReducer에 전달하는 함수이다. 이 함수는 비동기로 지연되어 호출된다. 예를 들면,
- 일반적인 초기화: const count = useState<number>(0);
- 레이지 초기화: const count2 = useState<number>(()=>{ return 0; })
이 단계는 컴포넌트가 마운트될 때만 실행된다.
특히, 레이지 초기화를 실행할 때 인자로 전달되는 함수 내부에서 실행되는 코드는 마운트될 때만 실행된다.
상태로 사용할 데이터를 도출하기 위해 복잡한 로직이 필요한 경우에 레이지 초기화가 유용하다.
렌더링 함수 컴포넌트의 내부 코드가 실행된다.
이때 가상 DOM 에 대한 쓰기 작업을 수행한다.
가상 DOM 업데이트 가상 DOM 트리를 업데이트한다.
LayoutEffects 실행 useLayoutEffect 훅에 지정한 함수를 실행한다.  
브라우저 DOM 업데이트 브라우저 DOM을 업데이트한다. 이 단계가 완료되면 브라우저 화면 갱신이 완료된 상태가 된다.
Effects 실행 useEffect 훅에 지정한 함수가 호출된다.

 

- 컴포넌트가 업데이트될 때

 

- 컴포넌트가 언마운트될 때

 

4. useReducer 훅

상태 변경 로직(useState) 같은 건데 복잡한 상태를 관리하고 변경할 때는 컴포넌트 상태 변경 로직이 포함되어야 하므로 컴포넌트 내부가 복잡해짐 -> useReducer 훅으로 상태 관련 로직을 컴포넌트 밖으로 분리시킨다.

 

useReducer 훅 정의!!

리액트에서 컴포넌트 상태(state)를 관리하기 위한 훅으로, 상태 관련 로직을 reducer 함수로 컴포넌트 밖으로 분리시킬 수 있다.

 

useReducer 훅을 이해하려면 리듀서를 알아야 함.

reduce 메서드는 배열의 데이터를 이용해 합계 구할 때 쓸 수 있음

~~.reduce(reducer[, initialValue])
// reducer: 리듀서 함수
// initialValue: 합계를 구할 때 초기값으로 선택적으로 사용할 수 있는 인자

 

리듀서 reducer 함수는 순수 함수임.

순수 함수의 조건

1. 입력 인자가 동일하면 리턴값도 동일하다.

2. 부수효과가 없어야됨. (함수에 전달되는 인자 이외의 외부 리소스에 영향을 주거나, 외부 리소스로부터 받는 영향 없어야)

3. 함수에 전달되는 인자는 불변성을 가져야됨. 따라서 인자 변경 불가능

 

useReducer 훅이 사용하는 함수가 배열이 reduce 메서드에 전달되는 reducer 함수와 동일한 형태이고, 순수함수이다. 이러한 이유로 리듀서 라고 부름.

 

리듀서 함수 형태

(state, action) => {
	// state와 action을 이용해 연산을 수행한 후 새로운 상태를 리턴한다.
    return newState
}

순수 함수여야 하기 때문에 state, action을 변경하면 안 되고, 반드시 새로운 상태를 만들어 리턴해야 한다.

 

// reduce 사용.. 반환 값이 최종 누적값
const reducer = (totalPoint: number, member: MemberType) => {
	totalPoint += member.point;
    return totalPoint; // state와 action 이용해 연산을 수행한 후 새로운 상태를 리턴
}

const totalPoint = familyMembers.reduce(reducer, initialPoint); 
console.log(`가족 합게 포인트: ${totalPoint}`);

reduce는 타입이 배열 메서드임

상태 관리를 변수 const totalPoint

사용 용도는 일반 JS 연산

-> 연산 결과를 그대로 변수에 할당

 

// useReducer 사용.. 반환값이 [state, dispatch)
const [state, dispatch] = useReducer(reducer, initialState);
// state는 상태, dispatch는 상태 변경 메서드
// reducer는 새로운 상태를 리턴할 리듀서 함수
// initialState는 초기상태로 지정할 객체

useReducer은 타입이 리액트 훅임

상태 관리를 리액트 상태state로 함

사용 용도는 리액트 컴포넌트 상태 관리

-> 반환값을 [state, dispatch]로 구조 분해하고 리액트 상태로 사용.

 

useState 와 같이 useReducer 도 상태를 관리하고 변경하는건데

useReducer 리듀서 훅은

상태 관리 기능을 컴포넌트로부터 분리, 유사한 상태 관리 기능을 사용하는 여러 컴포넌트가 상태 변경과 관리 기능을 공유

불변성을 가지는 상태 변경을 강제하게 되므로 상태 변경을 추적하기가 용이하다.

 

리듀서 사용하면 과거 시점의 상태가 그대로 유지되므로 언제든 과거 시점의 상태 데이터를 확인할 수 있음.

useState 간단한 상태 관리 상태 단순, 업데이트 로직 간단할 때
useReducer 복잡한 상태 변화 로직 관리 상태가 여러 종류의 업데이트를 가지거나, 로직이 복잡할 때

 

 

5. useRef 훅

컴포넌트 내부에 직접 정의한 변수들은 컴포넌트가 리렌더링되면 모두 초기화되어 버린다.

그러나 useRef 훅을 호출한 뒤 리턴받은  ref 객체는 컴포넌트의 모든 생명주기 동안에 유지되므로 리렌더링되더라도 기존 참조 데이터를 유지한다.

대신 ref 객체가 참조하는 데이터가 변경되더라도 리렌더링이 일어나지는 않는다.

또한 useRef를 이용하면서 동시에 브라우저 DOM 요소의 태스에서 ref 특성을 사용하면 브라우저 DOM의 요소에 직접 접근할 수 있다.

 

useRef는 DOM 요소나 변수 값을 저장하기 위한 훅

리렌더링 시 값이 유지되지만, 값이 바뀌어도 컴포넌트를 리렌더링 시키지 않는다. p231~232 그림

=> 렌더링과 상관 없이 유지되는 값을 저장하는 훅. 

값 변경 시 렌더링 발생 X, DOM 요소 접근, 렌더링과 무관한 값 저장.

 

값이 바뀌어도 컴포넌트는 리렌더링되지 않으며, 렌더링 간 데이터를 기억하거나 DOM에 직접 접근할 때 사용

 

리턴값은 값에 대한 참조를 포함하는 객체임

참조데이터에 접근하려면 .current 속성을 사용해야 함.. ex. {refTel.current}

const refObject = useRef(initialValue);
// initialValue는 참조 객체로 주어질 초기값

 

상태가 아닌 데이터 관리 (useRef 훅을 이용한 예제)

import { useRef, useState } from 'react'

const App = () => {
	const [name, setName] = useState<string>("홍경");
    const refTel = useRef("010-1234-2222"); 
    return (
    	<div className="boxStyle">
        	<h2>상태 데이터</h2>
            <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
            <br />
            <div> 상태(name): {name}</div>
            <hr />
            <input type="text" onChange={(e) => (refTel.current = e.target.value)} />
            <br />
            <div> refTel 값: {refTel.current}</div>
        </div>
    );
};

export default App;

useRef 훅의 입력 필드를 변경해도 값이 그대로임...

 

import { useRef } from 'react'

const App = () => {
	const elName: React.RefObject<HTMLInputElement> = useRef<HTMLInputElement>(null);
    const goFirstInputElement = () => { // useRef 훅 이용해 참조 객체를 생성, 초기값 null 부여헤야 함.
    // null 부여: 이 코드가 실행될 때는 렌더링과 브라우저 DOM에 업데이트 되기 전이므로 아직 참조 객체가 연결할 수 있는 DOM이 만들어지기 전이기 때문임.
    	if (elName.current) elName.current.focus(); // 브라우저 DOM 요소에 접근할 때 참조 객체의 current 속성 이용
    };
    
    return (
    	<div className="boxStyle">
        	 이름: <input ref={elName} type="text" defaultValue="홍경" /> // 젒근하려는 요소의 ref 특성에 참조 객체를 바인딩
             <br />
             전화: <input type="text" defaultValue="010-1234-2222" />
             <br />
             <button onClick={goFirstInputElement}>첫 번째 필드로 포커스 이동</button>
        </div>
    );
};

export default App;

이 예제에서는  elName.current로 <input> 요소에 접근하고, focus() 메서드를 호출해 마우스 포커스를 해당 위치로 이동시킴.

 

6. 메모이제이션 훅 p234

 

메모이제이션

기존에 연산된 결과값을 메모리에 캐싱하고, 동일한 입력과 환경에서 재사용하는 기법

이 기법을 적절히 사용하면 중복 처리를 피할 수 있어 애플리케이션의 성능 최적화에 종종 사용된다.

이 기능을 제공하는 리액트 훅은 useMemo, useCallback 2가지가 있다.

 

메모이제이션 훅의 기능을 확인할 수 있는 예제

import { useState } from 'react'

type TodoListItemType = {
	id: number;
    todo: string;
}

// 배열 받아서 처리하는 일반 함수
// 매개변수로 todoList를 받음. 이 매개변수의 타입이 배열 ArrayType<TodoListItemType>임
const getTodoListCount = (todoList: ArrayType<TodoListItemType>) => {
	console.log("## TodoList 카운트: ", todoList.length);
    return todoList.length;
}

const App () => {
	const [todoList, setTodoList] = useState<Array<TodoListItemType>>([]);
    const [todo, setTodo] = useState<string>(""); // todo 입력창 부분..
    
    // addTodo 변수의 매개변수가 todo.. 이 todo는 문자열 타입
    const addTodo = (todo: string) => { 
    	const newTodoList = [...todoList, { id: new Date().getTime(), todo: todo }];
        // ... 전개(스프레드)연산자로 todoList 복사해옴. 배열에 새로운 값을 할당
        setTodoList(newTodoList);
        setTodo(""); // 새로운 할 일을 추가했으면 입력창을 초기화시켜줌
    };
    
    const deleteTodo = (id: number) => {
    	// 배열에서 id가 일치하는 항목의 인덱스를 찾음
        // 배열에서 item 하나씩 꺼내고 id가 내가 삭제하려는 id와 같은지 비교
    	const index = todoList.findIndex((item) => item.id === id);
        const newTodoList = [...todoList]; // 불변성 유지 위해 직접 수정 ㄴㄴ 복사해옴
        newTodoList.splice(index, 1); // 찾은 인덱스 요소를 1개 제거
        setTodoList(newTodoList);
        // findIndex 배열에서 조건에 맞는 요소의 위치(인덱스) 찾음
        // splice 배열에서 요소를 자르기 (삭제/교체)
    };
    
    return (
    	<div className="boxStyle">
        	<input type="text" value={todo} onChange={(e) => setTodo(e.target.value)} />
            <button onClick={() => addTodo(todo)}>Add Todo</button>
            <br />
            <ul>
            	{todoList.map((item) => (
                	<li key={item.id}>
                    	{item.todo}&nbsp;&nbsp;
                        <button onClick={() => deleteTodo(item.id)}>삭제</button>
                        // () => 함수를 감싸는 함수.. 클릭하면 실행되라고 지연시켜둠
                    </li>
				))}
            </ul>
            <div>todo 개수: {getTodoListCount(todoList)}</div>
            // todoList를 getTodoListCount 함수에 전달
            // 함수 내부에서 todoList.length를 계산, 길이 반환
            // JSX에서 { }로 감싸서 화면에 출력
        </div>
    );
};

export default App;

 


1. useMemo 훅

연산 결과를 기억(캐싱) 해두고, 의존성 값이 바뀔 때만 다시 계산하도록 하는 훅이다.

const memoizedValue = useMemo<T>(factory: ( ) => T, depsList)
// factory: 캐싱할 값을 만들어내는 함수
// depsList: 의존 객체 배열로, 이 배열의 값이 바뀌기 전까지는 캐시를 유지
// 캐싱할 값은 제네릭으로 T에 타입을 지정함

 

 useMemo 사용 부분

import { useMemo, useState } from 'react'

....
const getTodoListCount = (todoList: Array<TodoListItemTypes>) => {
	console.log("## TodoList 카운트: ", todoList.length);
    return todoList.length;
};

const App = () => {
	... (생략)
    const memoizedCount = useMemo<number>(() => getTodoListCount(todoList), [todoList]);
    // todoList가 바뀌면 getTodoListCount(todoList)를 다시 계산하고, 안 바뀌면 이전에 계산한 결과를 그대로(캐시된 값) 사용한다.
    // getTodoListCount(todoList) 계산하고 싶은 함수
    // [todoList] 이 값이 바뀔 때만 함수를 다시 실행하도록 지정
    
    ... (생략)
    
    return (
    	<div className="boxStyle">
        ... (생략)
       		<div>todo 개수: { memoizedCount }</div>
        </div>
    );
};
export default App;

getTodoListCount 함수를 호출한 결과를 캐싱함. 

그리고 의존 객체 배열 값으로 todoList 배열을 지정했으므로 todoList 배열이 변경되기 전까지는 getTodoListCount 함수를 호출하지 않고 캐시를 이용함

 

2. useCallback 훅

컴포넌트 내부의 함수를 캐싱하여 리렌더링되더라도 함수를 매번 생성되지 않도록 해줌.

useMemo 훅의 캐싱 대상은 함수의 리턴값

useCallback 훅의 캐싱 대상은 컴포넌트 내부의 함수

==> useCallback 훅은 함수를 메모이제이션(기억)하는 훅..

즉, 함수가 재생성되는 것을 방지하고, 의존성 배열이 바뀔 때만 새로 생성하도록 하는 역할

 

컴포넌트는 상태가 바뀔 때마다 함수도 새로 만들어지기 때문에 useCallback 안 쓰면 todoList의 상태가 바뀌면 App 컴포넌트 전체가 다시 렌더링됨. 그러면 addTodo 함수도 새로 생성됨. 

이때 하위 컴포넌트로 전달되는 함수가 바뀌면, 불필요한 리렌더링이 발생할 수 있어서 사용함.

 

렌더링 및 성능 최적화 ..

메모이제이션 훅 이용해 렌더링 성능 개선하고자 한다면 React.memo() 라는 고차함수를 함께 이용

import { useMemo, useState } from 'react'

...
const App = () => {
	... 
    const addTodo = useCallback(
    	(todo: string) => { // 할 일 추가하는 함수
        	const newTodoList = [...todoList, { id: new Date().getTime(), todo: todo }];
            setTodoList(newTodoList);
            setTodo("");
        }, [todoList] // 의존성 배열.. todoList가 바뀌면 새 함수로 생성, 아니면 그대로 사용
    );

	const deleteTodo = useCallback(
    	(id: number) => {
        	const index = todoList.findIndex((item) => item.id === id);
            const newTodoList = [...todoList];
            newTodoList.splice(index, 1);
            setTodoList(newTodoList);
        }, [todoList]
    );
    ....
};

export default App;

 

 

사용자 정의 훅

개발자가 직접 작성하는 리액트 훅

여러 컴포넌트에서 필요로 하는 코드와 기능 재사용하기 위해 사용자 정의 훅을 작성함

 

리액트 훅 사용 시 주의사항

- 클래스 컴포넌트에서는 리액트 훅 사용 불가

- 함수 컴포넌트 내부의 최상위 코드 영역에서만 리액트 훅 호출 가능

: 반복문이나 중첩된 함수 안에서는 리액트 훅을 호출하지 않음. 

'React' 카테고리의 다른 글

[React] 리액트 개념 정리 #6  (0) 2025.10.29
[React] 리액트 개념 정리 #5  (0) 2025.10.28
[React] 리액트 개념 정리 #4  (0) 2025.10.26
[React] 리액트 개념 정리 #2  (0) 2025.10.26
[React] 리액트 개념 정리 #1  (0) 2025.10.26