React 2

j0yy00n0·2025년 5월 10일
post-thumbnail

2025.04.09, 04.14

React

Hooks

함수 컴포넌트에서 사용 불가능한 생명주기 메소드의 한계점으로 인해 상태 관리 및 렌더링 이후 시점 컨트롤 등 다양한 문제를 해결하기 위해 만든 함수 집합을 의미

  • useState는 가장 기본적인 hook : 함수 컴포넌트에서도 상태를 관리할 수 있게 해준다
  • 함수형 컴포넌트에서는 생명주기 API 사용이 불가능
  • use라는 접두어를 가진 함수들로 제공

useEffect

컴포넌트가 렌더링 된 이후에 특정 작업을 수행할 내용이 필요한 경우 사용할 수 있는 기능

  • 주로 API 호출, 서버 통신, 이벤트 등록/해제 등 비동기 작업을 처리하는 데 사용됨
  • 마운트, 업데이트, 언마운트 타이밍을 모두 처리 가능
  • 클래스형의 componentDidMount, componentDidUpdate, componentWillUnmount 기능을 하나로 통합
<div id="root"></div>
<script type="text/babel">
        
    const {useEffect} = React;

    function MessagePrinter({message}) {
        console.log('렌더링...');

		/* 
        	return 이후 실행 
            비동기 작업(API 호출하는 구문을 실행)
        */
        useEffect(() => {
            console.log('렌더링 이후 동작...');
            console.log(message);
        });

        return (
            <h1>{console.log('렌더링 시 출력')}{message}</h1> 
        );

        /*
            return 이후의 코드(렌더링 이후 시점)에서는 일반 코드가 실행되지 않기 때문에, 
            렌더링 후 작업은 useEffect에서 처리해야 한다
        */
        // 이 코드는 실행되지 않음. return 이후이기 때문에 무시됨
        // console.log('렌더링 이후 동작...');
    }

    const message = "안녕하세요";

    ReactDOM.createRoot(document.getElementById('root')).render(<MessagePrinter message = {message}/>);
</script>

useEffect - Mount 시점

useEffect는 기본적으로 마운트 된 시점과 업데이트 된 시점 두 가지 모두 동작

  • 마운트 될 때만 동작하고 업데이트 시에는 동작하지 않게 컨트롤 할 수도 있다
  • useEffect의 두 번째 인자로 [] (빈 배열)을 전달
  • 컴포넌트가 다시 렌더링되더라도 useEffect는 처음 마운트 시점에 단 한 번만 실행되고 멈춘다.
  • 의존성 배열을 비워두면, 이후 상태(state)나 props가 변경되더라도 useEffect는 다시 실행되지 않는다
  • 실시간 검색처럼 상태값 변화에 따라 동작이 필요하거나, 서버에 지속적으로 데이터를 요청해야 하는 경우에는 절대 빈 배열을 사용하면 안 된다.
<div id="root"></div>

<script type="text/babel">

    const {useState, useEffect} = React;

    function TimePrinter() {

        console.log('랜더링 됨!!')

        const [time, setTime] = useState(new Date().toLocaleTimeString());

        // useEffect 의 기본구조는 useEffect( () => {실행할 함수} , [의존성 배열] );

        useEffect(
            () => console.log('마운트 시점에만 동작함...'),
            []  // 두 번째 인자로 빈 배열을 넣으면 업데이트 시점에는 더 이상 동작하지 않고 마운트 시점(렌더링 이후)에만 동작하게 된다.
                // 실시간 검색 같이 상태값 변화에 따라 동작이 필요한 경우에는 의존성 배열을 비워두면 안된다.
                // 서버에 지속적인 연결이 필요한 경우 두번째 인자를 비워두면 안된다.
        )

        return(
            <>
                <button onClick={() => setTime(new Date().toLocaleTimeString())}>
                    현재시간 확인하기
                </button>
                <h1>{time}</h1>
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<TimePrinter/>);
</script>

useEffect - Update 시점

useEffect는 기본적으로 마운트 시점에도 실행되지만, 특정 값의 변경 시점에만 실행되도록 제어할 수 있다

  • 변화 감지 대상에 변화가 없으면 쓸데없이 동작하지 않는다
  • 의존성 배열에 넣은 값만 감시 대상이 된다.
<div id="root"></div>

<script type="text/babel">

    const {useState, useEffect} = React;

    function LoginForm() {

        const [user, setUser] = useState({
            username: '',
            password: ''
        });

        const onChangeHandler = (e) => {
            setUser({
                ...user,
                [e.target.name]: e.target.value
            });
        };

        /*
            useEffect는 기본적으로 마운트 시점에 동작하고 또한 원하는 값의 변경 시점에만 동작하게 각각 만들 수 있다. 
            그 뿐 아니라 변화 감지 대상에 변화가 없으면 쓸데없이 동작하지 않는다.
        */
        useEffect(
            () => {console.log('username update...')},
            [user.username]                 // 동작하기 전의 변경 전 값과 변경 후 값을 비교하여 일치하면 호출을 건너뛴다.
        );

        useEffect(
            () => {console.log('password update...')},
            [user.password]
        );

        return (
            <>
                <label>username: </label>
                <input type="text" name="username" onChange={onChangeHandler}/>
                <br/>
                <label>password: </label>
                <input type="password" name="password" onChange={onChangeHandler}/>
                <h3>username: {user.username}</h3>
                <h3>password: {user.password}</h3>
            </>
        ); 
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<LoginForm/>);
</script>

useEffect - Cleanup 시점 / UnMount 처리

useEffect(() => { return () => { ... } }, [의존성 배열])

  • ()=>{} : 실행할 메인 effect (렌더링 이후 실행)
  • [] : 의존성 배열
  • {return()=>{}} : 정리(clean-up) 함수
  • 컴포넌트가 마운트 해제되기 직전이나 업데이트 되기 직전에 실행할 내용이 있다면 정리(clean-up)을 할 수 있다
  • 이전 effect 내용을 정리하고 난 뒤 새로운 effect가 동작하도록 할 때 사용
  • 이전 effect가 남아있는 상태에서 새로운 effect가 발생하게 되면 마운트 해제가 일어나고 나서도 메모리 누수나 충돌이 발생할 가능성이 있다
  • setInterval, addEventListener, 외부 라이브러리 사용 시 반드시 정리 필요
  • 정리 함수는 클래스 컴포넌트의 componentWillUnmount에서 하는 역할과 동일
<div id="root"></div>

<script type="text/babel">
    const {useState, useEffect} = React;

    function Timer() {

        useEffect(
            () => {
                // console.log('Timer 컴포넌트 마운트 될 때만 동작함...');
                console.log('타이머가 시작합니다...');
                const timer = setInterval(() => {
                                    console.log(new Date().toLocaleTimeString());
                              },1000);

                /* 함수를 반환하면 해당 컴포넌트를 언마운트 시 setInteval을 통해 Interval을 먼저 종료 시킨다. */
                return () => {
                    clearInterval(timer);
                    console.log('타이머가 종료됩니다...');
                }
            },
            []
        )

        return <h1>타이머를 시작합니다.</h1>;
    }

    function App() {
        const [isTrue, setIsTrue] = useState(false);

        return(
            <>
                <button onClick={() => {setIsTrue(!isTrue)}}>타이머 토글</button>
                { isTrue && <Timer/>}
                {/* { isTrue ? <Timer/> : null} */}
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

useReducer

컴포넌트의 상태 업데이트를 외부에 선언한 reducer 함수에 위임하여, 액션(action)에 따라 상태를 관리하는 방식

  • useState보다 복잡한 상태 관리나 액션 분기가 필요한 경우에 적합
  • const [state, dispatch] = useReducer(외부 리듀서 함수 , 초기값);
  • 컴포넌트에서 직접 state를 수정(setState개념)을 하지 않는다.
  • dispatch(action)이 호출되면 해당 reducer가 실행되어 새로운 state를 반환
  • dispatch → reducer → state 흐름으로 처리
<div id="root"></div>

<script type="text/babel">

    /* useReducer(외부 함수(리듀서)에서 컴포넌트의 state를 action에 따라 관리) */
    const {useReducer} = React;

    // state : {value: 0} / action : {type: 'INCREMENT or DECREMENT'}
    function reducer(state, action) {
        console.log(state);
        console.log(action);
        
        switch(action.type) {
            case 'DECREMENT':
                return {value: state.value - 1};
            case 'INCREMENT':
                return {value: state.value + 1};
            default:
                return state;
        }

    }

    /* 컴포넌트에서 직접 state를 수정(setState개념)을 하지 않는다. */
    function Counter() {
        console.log(useReducer(reducer, {value: 0}));

        // useReducer 의 기본 틀 useReducer(외부 리듀서 함수 , 초기값)
            
        // dispatch -> reducer / state -> {value: 0}
        // dispatch는 reducer 함수를 쫓아다닌다.
        const [state, dispatch] = useReducer(reducer, {value: 0});

        return (
            <>
                <h1>counter : {state.value}</h1>
                <button onClick={() => dispatch({type: 'DECREMENT'})}>- 1</button>
                <button onClick={() => dispatch({type: 'INCREMENT'})}>+ 1</button>
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<Counter/>);
</script>

Reducer Form Control

리듀서를 활용하면 input 태그들의 상태 관리를 하나의 state 객체로 간결하게 관리할 수 있다.

  • useReducer는 useState보다 폼 상태가 많아질수록 더 관리하기 쉬운 구조를 만든다.
  • dispatch(e.target)은 e.target.name, e.target.value 구조를 활용하는 핵심 포인트
<div id="root"></div>

<script type="text/babel">
    const {useReducer} = React;

    function reducer(state, action) {
        return {
            ...state,
            [action.name]: action.value
        };
    }

    function RegistForm() {
            
        const [state, dispatch] = useReducer(reducer, {
            name: '',
            nickname: ''
        });

        const {name, nickname} = state;

        const onChangeHandler = e => {
            console.log(e.target);
            dispatch(e.target)};

        return (
            <>
                <label>이름: </label>
                <input type="text" name="name" onChange={onChangeHandler}/>
                <br/>
                <label>닉네임: </label>
                <input type="text" name="nickname" onChange={onChangeHandler}/>
                <br/>
                <div>
                  <h3>입력한 이름: {name}</h3>
                  <h3>입력한 닉네임: {nickname}</h3>
                </div>
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<RegistForm/>);
</script>

useMemo

무거운 연산 결과를 메모이제이션(캐싱)하여, 불필요한 계산을 방지할 수 있도록 해주는 React Hook

  • useMemo( () => {실행시킬 연산 함수} , [의존성 배열] )
  • 연산 함수의 반환값 자체를 기억(캐싱)
  • 함수를 기억하는 것이 아닌 함수의 반환값을 기억!
  • 의존성 값이 변경되지 않는 한, 다시 계산하지 않고 이전 값을 재사용
  • 나온 값을 메모리 상에 저장해(캐싱) 두고, 그 값이 변경되지 않을 때는 다시 렌더링(연산)되지 않도록 한다.
  • 주로 렌더링 시 시간이 오래 걸리는 연산 최적화에 사용
<div id="root"></div>

<script type="text/babel">

    /* useMemo 훅스도 꺼내자. */
    const {useState, useMemo} = React;

    const hardCalculator = (num) => {
        console.log('어려운 계산');

        for(let i = 0; i < 1999999999; i++) {

            //blank
        }

        return num + 10000;
    }

    const easyCalculator = (num) => {
        console.log('쉬운 계산');

        return num + 1;
    }

    function App() {

        const [hardNumber, setHardNumber] = useState(1);
        const [easyNumber, setEasyNumber] = useState(1);
        
		// useMemo( () => {실행시킬 함수} , [의존성 배열] )
        // hardNumber가 바뀔 때만 재계산

        const hardSum = useMemo(() => {
            return hardCalculator(hardNumber);
        }, [hardNumber]);

		// easyNumber가 바뀔 때만 재계산
        const easySum = useMemo(() => {
            return easyCalculator(easyNumber);
        }, [easyNumber]);
           
        return (
            <>
                <h3>어려운 계산기</h3>
                <input 
                  type="number"
                  value={hardNumber}
                  onChange={e=> setHardNumber(parseInt(e.target.value))}
                />
                <span> + 10000 = {hardSum}</span>
                <h3>쉬운 계산기</h3>
                <input 
                  type="number"
                  value={easyNumber}
                  onChange={e=> setEasyNumber(parseInt(e.target.value))}
                />
                <span> + 1 = {easySum}</span>
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

useMemo 주의할 점

useMemo는 참조형 데이터(객체, 배열 등)를 지역 변수로 사용할 때 특히 유용

  • React의 의존성 비교는 값(value)이 아니라 참조(reference) 기준
  • const obj = { country: 'Korea' }처럼 객체를 지역 변수로 선언하면 매번 새로운 객체가 생성되므로 useEffect 등에서 변경된 것으로 인식
  • 이런 경우, 불필요한 effect 실행이나 렌더링 비용 증가가 발생
  • 참조형 객체의 재생성을 막고 싶을 때 useMemo로 해당 객체를 캐싱하는 것이 좋다.
  • 특히 그 객체를 useEffect, useCallback, useMemo 등의 의존성 배열에 넣는 경우, 꼭 사용해야 한다
  1. 지역 변수에 기본 자료형(문자열, 숫자 등)로 초기화
  • number state 변화 시 location에 변화가 없음(재할당X)
<div id="root"></div>

<script type="text/babel">
    const {useState, useEffect, useMemo} = React;

    function App() {
        const [isKorea, setIsKorea] = useState(true);
        const [number, setNumber] = useState(0);

        console.log('렌더링');

        /* 1. 지역 변수에 문자열로 초기화(number state 변화 시 location에 변화가 없음(재할당)-기본자료형이므로) */
        const location = isKorea? '한국':'외국';

        /* useEffect를 사용해서 콜백함수를 mount 시점 및 location값에 변화가 있을 때만 동작 */
        useEffect(() => {
            console.log('useEffect 호출됨...');
        }, [location]);

        return (
            <>
                <h2>지금 당신이 있는 위치는?</h2>
                <p>국가: {location}</p>
                <button onClick={() => setIsKorea(!isKorea)}>국가 토글 하기</button>
                <hr/>
                <h2>좋아하는 숫자를 입력해 주세요</h2>
                <input 
                  type="number"
                  value={number}
                  onChange={e => setNumber(e.target.value)}
                />
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>
  1. 지역 변수에 객체로 초기화
  • number state 변화 시 location에 변화가 있음(재할당)-객체의 주소값이므로
<div id="root"></div>

<script type="text/babel">
    const {useState, useEffect, useMemo} = React;

    function App() {
        const [isKorea, setIsKorea] = useState(true);
        const [number, setNumber] = useState(0);

        console.log('렌더링');

        /* 2. 지역 변수에 객체로 초기화(number state 변화 시 location에 변화가 있음(재할당)-객체의 주소값이므로) */
        const location = {
            country: isKorea? '한국':'외국'
        };

        /* useEffect를 사용해서 콜백함수를 mount 시점 및 location값에 변화가 있을 때만 동작 */
        useEffect(() => {
            console.log('useEffect 호출됨...');
        }, [location]);

        return (
            <>
                <h2>지금 당신이 있는 위치는?</h2>
                <p>국가: {location.country}</p>
                <button onClick={() => setIsKorea(!isKorea)}>국가 토글 하기</button>
                <hr/>
                <h2>좋아하는 숫자를 입력해 주세요</h2>
                <input 
                  type="number"
                  value={number}
                  onChange={e => setNumber(e.target.value)}
                />
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>
  1. 지역 변수에 useMemo의 반환값으로 초기화
  • number state 변화 시 location에 변화가 없음(재할당 X)-isKorea가 동일하므로)
<div id="root"></div>

<script type="text/babel">
    const {useState, useEffect, useMemo} = React;

    function App() {
        const [isKorea, setIsKorea] = useState(true);
        const [number, setNumber] = useState(0);

        console.log('렌더링');

        /* 3. 지역 변수에 useMemo의 반환값으로 초기화(number state 변화 시 location에 변화가 없음(재할당 X)-isKorea가 동일하므로) */
        const location = useMemo(() => {
            return {
                country: isKorea? '한국': '외국'
            }
        }, [isKorea]);

        /* useEffect를 사용해서 콜백함수를 mount 시점 및 location값에 변화가 있을 때만 동작 */
        useEffect(() => {
            console.log('useEffect 호출됨...');
        }, [location]);

        return (
            <>
                <h2>지금 당신이 있는 위치는?</h2>
                <p>국가: {location.country}</p>
                <button onClick={() => setIsKorea(!isKorea)}>국가 토글 하기</button>
                <hr/>
                <h2>좋아하는 숫자를 입력해 주세요</h2>
                <input 
                  type="number"
                  value={number}
                  onChange={e => setNumber(e.target.value)}
                />
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

useCallBack

useCallback은 함수형 컴포넌트 내부에서 선언된 함수를 메모이제이션(memoization)하여, 의존성이 변경되지 않는 한 같은 함수 객체를 재사용하게 만들어주는 Hook

  • React 함수 컴포넌트는 렌더링될 때 마다 전체 함수가 다시 실행된다.
  • 내부에서 선언된 함수도 매번 새로 생성되게 되는데 문제가 발생할 수 있다.
  • 자식 컴포넌트에 함수 props를 전달하면 React.memo로 감싸더라도 불필요한 리렌더링이 발생
  • useEffect, useMemo 등의 의존성 배열에 함수가 포함되면 매번 함수가 바뀌었다고 판단되어 useEffect가 실행되게 된다.
  • React.memo, useMemo와 함께 사용할 때 최적화한 캐싱이 항상 새 함수로 인해 재실행되므로 캐싱이 무의미해진다.

사용 시점

  • 함수가 useEffect, useMemo, React.memo 등의 의존성 배열에 포함될 때
  • 함수를 props로 자식 컴포넌트에 전달할 때
  • 렌더링 성능 최적화가 중요한 상황에서 함수 재사용이 필요한 경우

useCallback을 사용하지 않은 경우 문제점

  • number나 toggle이 바뀔 때마다 App() 함수가 다시 실행되므로 printNumber도 새로 생성됨
  • useEffect([printNumber])가 함수 참조가 매번 달라졌다고 인식하여 렌더링마다 실행됨 → 불필요한 effect 발생
<div id="root"></div>

<script type="text/babel">
    const {useState, useEffect} = React;

    function App() {
        const [number, setNumber] = useState(0);
        const [toggle, setToggle] = useState(false);

        const printNumber = () => {
            console.log('current number: ',  number);       // console.log는 ,로 구분해서 로그들을 찍어낼 수 있다.
            }

        /*
            number 또는 toggle state가 변경될 때 함수 생성이 다시 되는 것은 불필요하다.
            하지만 매번 함수가 다시 생성되어 반환되고 useEffect에서는 해당 지역 변수에 변화가 있다고 인지하게 된다.
        */
        useEffect(() => {
                console.log('printNumber 값 변화 인지됨');
        },[printNumber]);

        return (
            <>
                <input
                  type="number"
                  value={number}
                  onChange={e => setNumber(e.target.value)}
                />
                <button onClick={() => setToggle(!toggle)}>{String(toggle)}</button>   {/* boolean값은 문자열로 바꿔야 JSX문법으로 화면에 표현된다. */} 
                <br/>
                <button onClick={printNumber}>PrintNumberState</button>
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

useCallback을 사용하여 함수 재생성 방지

  • useCallback을 사용하면 printNumber는 number가 변경될 때에만 새로 생성
  • toggle만 변경될 경우엔 기존 함수가 재사용되어 useEffect는 실행 되지 않는다. → 불필요한 재실행 방지
<div id="root"></div>

<script type="text/babel">
const {useState, useEffect, useCallback} = React;

function App() {
    const [number, setNumber] = useState(0);
    const [toggle, setToggle] = useState(false);

    /*
        함수 생성이 불필요하게 계속 될 때 useCallback을 이용하여
        함수를 memorization해서 사용할 수 있다.
    */
    const printNumber = useCallback(
        () => {
            console.log('current number: ',  number);       
        },
        [number]    // 의존성 배열 자리에 빈 배열을 두게 되면 마운트 시점에 한번 지역변수 초기화를 위해 사용되고 나서 함수는
                    // 새로 정의 되지 않으므로 항상 number의 초기값인 0만 나오게 된다.
    );

    useEffect(() => {
            console.log('printNumber 값 변화 인지됨');
    },[printNumber]);

    return (
        <>
            <input
            type="number"
            value={number}
            onChange={e => setNumber(e.target.value)}
            />
            <button onClick={() => setToggle(!toggle)}>{String(toggle)}</button>   
            <br/>
            <button onClick={printNumber}>PrintNumberState</button>
        </>
    );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

useContext

context는 React 컴포넌트 트리 안에서 전역적으로 데이터를 공유할 수 있도록 고안된 방법

  • 복잡한 트리 구조에서 하위 컴포넌트로 props를 일일이 전달하는 props drilling 문제를 해결하기 위해 사용
  • props drilling : 컴포넌트 구조가 깊어질수록 props를 중간중간 계속 전달해야 하는 상황
  • props drilling은 코드의 가독성과 유지보수성을 심각하게 저하
  • useContext를 활용하면 중간 컴포넌트들을 거치지 않고 직접 데이터 접근 가능
  • context를 사용하면 컴포넌트를 재사용하기 어려워지기 때문에 꼭 필요할 때만 써야 한다
  • context 객체는 createContext(defaultValue)로 생성
  • defaultValue가 없는 경우에는 null로 설정
  • Provider를 통해 context의 실제 값을 하위 컴포넌트에 전달
  • useContext(Context)를 통해 현재 트리 구조 상에서 가장 가까운 Provider 값을 읽어옴
  • 적절한 provider를 찾지 못할 때 쓰이는 값이 defaultValue
  • 상태관리를 하기 위한 도구 -> 직접 사용하는 경우는 많이 없고 상태관리 라이브러리를 사용
    • 관리해야 할 상태값이 많아지면 props(drilling)하는 것 처럼 복집해질 수 있다. (가독성 떨어짐)
    • 필요없는 값들로 공유가 된다.

Props Drilling 방식

  • isDark, setIsDark를 Page → Header/Content/Footer로 계속 전달
<div id="root"></div>

<script type="text/babel">

    const {useState} = React;

    function Header({isDark}) {
        return (
            <header
                className="header"
                style={
                    {
                        backgroundColor: isDark? 'black': 'lightgray',
                        color: isDark? 'white': 'black'
                    }
                }
            >
                <h1>Welcome to Greedy World!</h1>
            </header>
        );
    }

    function Content({isDark}) {
        return (
            <div
                className="content"
                style={
                    {
                        backgroundColor: isDark? 'gray': 'white',
                        color: isDark? 'white': 'black'
                    }
                }
            >
                <p>내용입니다.</p>
            </div>
        );
    }

    function Footer({isDark, setIsDark}) {
        const toggleHandler = () => {setIsDark(!isDark)}

        return (
            <footer
                className="footer"
                style={
                    {
                        backgroundColor: isDark? 'black': 'lightgray',
                        color: isDark? 'white': 'black'
                    }
                }
            >
                <button onClick={toggleHandler}>{isDark? 'Light Mode': 'Dark Mode'}</button>
                Copyright 2025. ohgiraffers. all rights reserved.
            </footer>
        );
    }

    function Page({isDark, setIsDark}) {
        return (
            <div className="page">
                <Header isDark={isDark}/>
                <Content isDark={isDark}/>
                <Footer isDark={isDark} setIsDark={setIsDark}/>
            </div>
        );
    }

    function App() {

        const [isDark, setIsDark] = useState(false);

        return <Page isDark={isDark} setIsDark={setIsDark}/>;
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

useContext 사용 방식

  • DarkModeContext.Provider를 통해 값을 하위 전체에 제공
  • useContext(DarkModeContext) 한 줄로 어느 컴포넌트든 값 접근 가능
  • Header, Content, Footer 모두 props 없이 값 사용
<div id="root"></div>

<script type="text/babel">

    /* createContext, useContext 추가 */
    const {useState, createContext, useContext} = React;

    /*
        context 객체를 createContext를 통해 만들게 되고 defaultValue가 없는 경우에는 null로 설정할 수 있다.
        context 객체를 구독하고 있는 컴포넌트를 렌더링 할 때 React는 트리 상위에서 가장 가까이 있는
        Provider로부터 현재 값을 읽어들인다. 이 때 적절한 provider를 찾지 못할 때 쓰이는 값이 defaultValue이다.
    */
    const DarkModeContext = createContext(null);

    function Header() {
        const context = useContext(DarkModeContext);
        const {isDark} = context;

        return (
            <header
                className="header"
                style={
                    {
                        backgroundColor: isDark? 'black': 'lightgray',
                        color: isDark? 'white': 'black'
                    }
                }
            >
                <h1>Welcome to Greedy World!</h1>
            </header>
        );
    }

    function Content() {
        const context = useContext(DarkModeContext);
        const {isDark} = context;

        return (
            <div
                className="content"
                style={
                    {
                        backgroundColor: isDark? 'gray': 'white',
                        color: isDark? 'white': 'black'
                    }
                }
            >
                <p>내용입니다.</p>
            </div>
        );
    }

    function Footer() {

        const context = useContext(DarkModeContext);
        // console.log(context);
        const {isDark, setIsDark} = context;

        const toggleHandler = () => {setIsDark(!isDark)}

        return (
            <footer
                className="footer"
                style={
                    {
                        backgroundColor: isDark? 'black': 'lightgray',
                        color: isDark? 'white': 'black'
                    }
                }
            >
                <button onClick={toggleHandler}>{isDark? 'Light Mode': 'Dark Mode'}</button>
                Copyright 2025. ohgiraffers. all rights reserved.
            </footer>
        );
    }

    function Page() {
        return (
            <div className="page">
                <Header/>
                <Content/>
                <Footer/>
            </div>
        );
    }

    function App() {

        const [isDark, setIsDark] = useState(false);

        /*
            Provider는 value prop을 이용하여 하위 컴포넌트에게 값을 전달한다. 
            이 때 값을 전달받을 수 있는 컴포넌트 수에는 제한이 없다. 
            해당 Provider는의 Context 이름으로부터 useContext hooks를 활용하여 
            어느 컴포넌트든 필요한 순간 가져다 쓸 수 있게 한다.
        */
        return (
            <DarkModeContext.Provider value={{isDark, setIsDark}}>
                <Page/>
            </DarkModeContext.Provider>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>

Custom Hooks

반복되는 코드를 줄이기 위해(개선하기 위해) 사용하는 사용자 정의 훅스

  • custom hook은 중복되는 로직을 재사용 가능한 형태로 추출한 함수
  • input 값과 state를 연동할 때 반복되는 코드를 줄이는 데 유용
  • React에서는 form의 값을 DOM에서 직접 가져오기보다는, state로 관리하여 비동기적으로 처리하는 것이 일반적
  • custom hook을 사용하면 상태(value)와 이벤트 핸들러(onChange)를 한 번에 관리할 수 있다
<div id="root"></div>
    
<script type="text/babel">
        
    const {useState} = React;

	/* useInput 커스텀 훅*/
    function useInput() {

        /*
            input 태그에 입력 받은 값을 state로 관리하는 이유
            1. 사용자가 입력한 값을 선택자 없이 읽어오기 위함
            2. react는 form태그를 쓰지 않는다.(state의 값을 비동기 방식 통신을 하여 다른 곳으로 전송하게 된다.)
        */

        const [value, setValue] = useState('');
        const onChange = (e) => setValue(e.target.value);

        return {value, onChange};
    }

    function App() {

        const name = useInput();
        const password = useInput();
        const email = useInput();

        return (
            <div>
                <label>이름: </label>
                <input type="text" value={name.value} onChange={name.onChange}/>
                <br/>
                <label>비밀번호: </label>
                <input type="password" {...password}/>  {/* 바벨에서 제공되는 축약형 문법 */}
                <br/>
                <label>이메일: </label>
                <input type="email" {...email}/>
                <br/>
                <h4>name: {name.value}</h4>
                <h4>password: {password.value}</h4>
                <h4>email: {email.value}</h4>
            </div>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
</script>
profile
잔디 속 새싹 하나

0개의 댓글