INTRO

7일차 오후 수업엔 react의 훅 함수에 대해 학습했습니다. react가 vanila JS에 비해 편하다라고 느껴지려면 어느 정도 경지에 올라야 할 것 같습니다. ㅠㅠ react에 대해 눈에 익기도 전에 프론트엔드 수업이 거의 끝나간다는 게 믿기지 않지만, 빠른 일정 속에서 오늘 배운 내용을 정리해보았습니다.

1. Hooks

클래스형 컴포넌트에서 제공하는 기능을 함수형 컴포넌트에서 사용할 수 있도록 도입된 함수.
(state, ref, lifecycle method, ... 등) . 훅 함수의 이름은 use 접두어를 사용한다.

- Info.js 생성

import { useState } from "react";

// 사용자 이름과 별명을 관리
function Info() {
    const [name, setName] = useState("");
    const [nickname, setNickname] = useState("");

    const changeName = e => setName(e.target.value);
    const changeNickname = e => setNickname(e.target.value);

    return (
        <>
            <div>
                <p>이름: {name}</p>
                <p>별명: {nickname}</p>
            </div>
            <div>
                <p>이름: <input type="text" name="name" value={name} onChange={changeName} /></p>
                <p>별명: <input type="text" name="nickname" value={nickname} onChange={changeNickname} /></p>
            </div >
        </>
    );
}
export default Info;

- App.js 파일에 Info 컴포넌트 추가

import Info from "./Info";

const App = () => {
  return <Info />;
};
export default App;

다른 방식의 코드

import Info from "./Info";


export default () => <Info />;

2. useEffect

클래스형 컴포넌트의 componentDidMount, componentDidUpdate, comdponentWillUnmount를 합친 형태.

- Info 컴포넌트에 useEffect 훅을 추가

순서 : 마운트(ComponentDidMount) -> name 업데이트(ComponentDidUpdate) -> nickname 업데이트

// 의존성 배열을 생략 => 마운트, 업데이트 모두 이펙트 함수를 실행
    useEffect(() => {
        console.log("렌더링이 완료되었습니다.");
        console.log({ name, nickname });
    });

- 마운트될 때만 이펙트 함수를 실행하도록 수정 ⇒ 의존성 배열의 값으로 빈 배열([ ])을 설정

    useEffect(() => {
        console.log("렌더링이 완료되었습니다.");
        console.log({ name, nickname });
    }, []);

- 마운트 될 때와 특정 상태변수가 변경될 때 이펙트 함수가 실행되도록 수정 ⇒ 의존성 배열에 변경을 검사할 상태변수를 추가

 // 마운트될 때와 name 상태변수의 값이 변경될 때 이펙트 함수를 실행
    useEffect(() => {
        console.log("렌더링이 완료되었습니다.");
        console.log({ name, nickname });
    }, [name]);

>>> 후처리(cleanup) 함수 추가

컴포넌트가 언마운트 또는 리렌더링되기 전에 어떤 작업을 수행하고 싶은 경우, 이펙트 함수에서 후처리 작업을 수행하는 함수를 반환

    useEffect(() => {
        console.log("렌더링이 완료되었습니다.");
        console.log({ name, nickname });

        return () => console.log("cleanup", name);		⇐ name 상태변수가 변경되거나 컴포넌트가 언마운트될 때 실행
    }, [name]);						   

cleanup 함수 동작을 테스트하기 위해 App 컴포넌트에 Info 컴포넌트 보이기/숨기기 기능을 추가

[App.js] 수정

import { useState } from "react";
import Info from "./Info";

export default () => {
  const [isVisible, setIsVisible] = useState(false);

  const changeIsVisible = () => setIsVisible(!isVisible);

  return (
    <>
      <button onClick={changeIsVisible}>{isVisible ? "숨기기" : "보이기"}</button>
      <br />
      {isVisible && <Info />}
    </>
  );
};

컴포넌트가 언마운트될 때만 cleanup 함수가 실행되도록 하고 싶으면 의존성 배열에 빈 배열을 설정

    useEffect(() => {
        console.log("렌더링이 완료되었습니다.");
        console.log({ name, nickname });

        return () => console.log("cleanup", name);		⇐ 컴포넌트가 언마운트될 때만 호출출
    }, []);

(참고) 컴포넌트가 언마운트 될 때 cleanup 함수에서 현재 상태변수를 참조하기 위해선 useRef 훅을 사용해야함.

3. useRef

함수형 컴포넌트에서 DOM 요소를 직접 제어하기 위해서 사용한다. ref 속성(attribute)와 useRef 훅을 사용해 HTML DOM 노드와 JavaScript 코드를 연결한다.
useRef 훅은 current 프로퍼티를 가지는 객체를 반환한다.

- DOM 요소 직접 제어

  • 상태변수 (화면에 나타나야하는 값 정하고 useState 활용), 변경되는 case(event 발생 시 상태변수 바꾸는 핸들러 만들기), 변경되는 부분에 핸들러 넣어주기!

[App.js]

import Average from "./Average"

export default () => {
  return <Average />;
};

[Average.js]

import React, { useRef, useState } from "react";

export default function Average() {
    const [number, setNumber] = useState("");
    const [list, setList] = useState([]);

    const changeNumber = e => setNumber(e.target.value);
    const changeList = () => {
        const newList = list.concat(number);    // [...list, number]
        setList(newList);

        setNumber("");
    **    refNumber.current.focus();**
    };

  **  // const refNumber = React.createRef();
    const refNumber = useRef();**

    return (
        <>
            <div>
               ** <input ref={refNumber} type="number" value={number} onChange={changeNumber} />**
                <button onClick={changeList}>등록</button>
            </div>
            <div>
                <p>입력값: {number}</p>
            </div>
            <div>
                등록된 숫자
                <ul>
                    {
                        list.map((n, i) => <li key={i}>{n}</li>)
                    }
                </ul>
            </div>
        </>
    );
}

- 컴포넌트의 로컬 변수로 사용 ⇒ ref 변수는 상태변수 처럼 컴포넌트의 생명주기 동안 값을 유지하지만 값이 변경되어도 리렌더링을 하지 않음

예1) 렌더링 회수를 출력
예2) 타이머 조작

화면에서 특정 element 직접 제어할 때, 타이머, 렌더링과 무관하게 값을 유지해야 할때 : 상태변수나 ref 변수로 조작. local 변수는 X.

[App.js ]

import "./App.css";
import { useState, useRef } from "react";

const CounterWithLocalVariable = () => {
  const [count, setCount] = useState(0);
  let intervalId = 0;
  console.log(`렌더링... count: ${count}, intervalId: ${intervalId}`);

  const startCounter = () => {
    intervalId = setInterval(() => { setCount(count => count + 1) }, 1000);
    console.log(`카운터 시작... intervalId: ${intervalId}`);
  };

  const stopCounter = () => {
    clearInterval(intervalId);
    console.log(`카운터 정지... intervalId: ${intervalId}`);
  };

  return (
    <>
      <p>카운트: {count}</p>
      <button onClick={startCounter}>시작</button>
      <button onClick={stopCounter}>정지</button>
    </>
  );
};

const CounterWithStateVariable = () => {
  const [count, setCount] = useState(0);
  const [intervalId, setInterId] = useState(0);

  console.log(`렌더링... count: ${count}, intervalId: ${intervalId}`);

  const startCounter = () => {
    const id = setInterval(() => { setCount(count => count + 1) }, 1000);
    setInterId(id);
    console.log(`카운터 시작... intervalId: ${intervalId}`);
  };

  const stopCounter = () => {
    clearInterval(intervalId);
    console.log(`카운터 정지... intervalId: ${intervalId}`);
  };

  return (
    <>
      <p>카운트: {count}</p>
      <button onClick={startCounter}>시작</button>
      <button onClick={stopCounter}>정지</button>
    </>
  );
};

const CounterWithRefVariable = () => {
  const [count, setCount] = useState(0);
  const intervalId = useRef(0);

  console.log(`렌더링... count: ${count}, intervalId: ${intervalId.current}`);

  const startCounter = () => {
    intervalId.current = setInterval(() => { setCount(count => count + 1) }, 1000);
    console.log(`카운터 시작... intervalId: ${intervalId.current}`);
  };

  const stopCounter = () => {
    clearInterval(intervalId.current);
    console.log(`카운터 정지... intervalId: ${intervalId.current}`);
  };

  return (
    <>
      <p>카운트: {count}</p>
      <button onClick={startCounter}>시작</button>
      <button onClick={stopCounter}>정지</button>
    </>
  );
};

export default () => {
  return (
    <>
      <CounterWithLocalVariable />
      <CounterWithStateVariable />
      <CounterWithRefVariable />
    </>
  );
};

3. useReducer

상태 관리 로직이 복잡할 때 useState 대체품으로 사용.
현재 상태와 액션을 받아 새로운 상태를 반환하는 리듀서(reducer) 함수를 통해 상태를 관리.
useReducer는 리듀스 함수와 초기 상태를 인자로 받아 상태와 디스패치 함수를 반환.

  • 리듀스 함수 : 현재 상태와 액션을 받아 새로운 상태를 반환하는 순수함수
  • 초기 상태 : 상태의 초기값
  • 액션 : 상태를 변경하기 위한 정보가 담긴 객체로, 일반적으로 type 프로퍼티를 가지고 있으며, 필요에 따라 데이터를 포함할 수 있음
  • 디스패치 함수 : 액션을 리듀서로 전달해 상태를 업데이트하는 함수

사용 예시)

  • 상태 로직이 복잡하거나, 여러 하위 값으로 구성된 상태를 관리해야 할 때
  • 상태 업데이트 로직이 여러 종류의 액션에 의해 다르게 동작해야 할 때
  • 상태와 그 업데이트 로직을 컴포넌트에서 분리하고 싶을 때

- useState 이용해서 Counter 컴포넌트 구현

[App.js]

import "./App.css";
import { useState } from "react";

const Counter = () => {
  const [count, setCount] = useState(0);
  const changeCount = e => setCount(count + Number(e.target.innerText));		
⇐ 버튼의 내용(contents)이 숫자로 변환 가능하기 때문에 하나의 핸들러 함수로 구현이 가능
  return (
    <>
      <div>현재 카운터 값은 <b>{count}</b>입니다.</div>
      <div>
        <button onClick={changeCount}>+1</button>
        <button onClick={changeCount}>-1</button>
      </div>
    </>
  );
};
export default () => <Counter />;

- 버튼의 내용이 숫자로 변환할 수 없는 경우 = 문자열인 경우 ⇒ 핸들러 함수를 따로 작성해야 함

import "./App.css";
import { useState } from "react";

const Counter = () => {
  const [count, setCount] = useState(0);
  const changeCountPlus = () => setCount(count + 1);
  const changeCountMinus = () => setCount(count - 1);
  return (
    <>
      <div>현재 카운터 값은 <b>{count}</b>입니다.</div>
      <div>
        <button onClick={changeCountPlus}>하나 더하기</button>
        <button onClick={changeCountMinus}>하나 빼기</button>
      </div>
    </>
  );
};
export default () => <Counter />;

- useReducer를 이용해서 Counter 컴포넌트를 작성

import "./App.css";
import { useReducer, useState } from "react";

// state: 현재 상태 변수의 값
// action: 상태 변수 변경에 필요한 조건과 값 (호출 시 전달되는 값)
function reducer(state, action) {
  switch (action.type) {
    case "INCREMENT":
      return { count: state.count + 1 };
    case "DECREMENT":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

const Counter = () => {
  /*
  const [count, setCount] = useState(0);
  const changeCountPlus = () => setCount(count + 1);
  const changeCountMinus = () => setCount(count - 1);
  */
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      <div>현재 카운터 값은 <b>{state.count}</b>입니다.</div>
      <div>
        <button onClick={() => dispatch({ type: "INCREMENT" })}>하나 더하기</button>
        <button onClick={() => dispatch({ type: "DECREMENT" })}>하나 빼기</button>
      </div >
    </>
  );
};
export default () => <Counter />;

- useState를 사용해서 여러 입력창의 상태를 관리

import "./App.css";
import { useState } from "react";

const Info = () => {
  const [name, setName] = useState('');
  const [nickName, setNickName] = useState('');

  const changeName = e => setName(e.target.value);
  const changeNickName = e => setNickName(e.target.value);

  return (
    <>
      <div>
        <p>이름: {name}</p>
        <p>별명: {nickName}</p>
      </div>
      <div>
        <p>이름: <input type="text" value={name} onChange={changeName} /></p>
        <p>별명: <input type="text" value={nickName} onChange={changeNickName} /></p>
      </div>
    </>
  );
};

export default () => <Info />;

- useReducer를 사용하는 것으로 변경

- 상태 변수를 객체 비구조화해서 표현

- 입력창에 name 속성을 추가해서 리듀서 함수와 이벤트 핸들러 함수를 단순화

import "./App.css";
import { useReducer } from "react";

const reducer = (state, action) => {
  return { ...state, [action.type]: action.value };
};

const Info = () => {
  const [state, dispatch] = useReducer(reducer, { name: "", nickName: "" });
  const { name, nickName } = state;
  const changeValue = e => dispatch({ type: e.target.name, value: e.target.value });
  return (
    <>
      <div>
        <p>이름: {name}</p>
        <p>별명: {nickName}</p>
      </div>
      <div>
        <p>이름: <input type="text" name="name" value={name} onChange={changeValue} /></p>
        <p>별명: <input type="text" name="nickName" value={nickName} onChange={changeValue} /></p>
      </div>
    </>
  );
};

export default () => <Info />;

4. useMemo

성능 최적화를 위해 특정 값이 변경될 때만 메모이제이션된 값을 재계산하도록 하여 불필요한 계산을 방지한다.
계산 비용이 높은 작업이나 렌더링 중에 자주 호출되는 작업에 유용하다.

useMemo로 전달된 함수는 렌더링이 일어나는 동안 실행된다. 일반적으로 렌더링이 일어나는 동안 실행해서는 안될 작업을 useMemo() 함수에 넣으면 안됨.
의존성 배열을 넣지 않을 경우, 렌더링이 일어날 때마다 매번 함수를 실행한다.

- Average 컴포넌트에 저장된 숫자들의 평균을 구해서 출력하는 기능을 추가

[App.js]

import "./App.css";
import Average from "./Average";

export default () => <Average />;

[Average.js]

Average 컴포넌트에 저장된 숫자들의 평균을 구해서 출력하는 기능을 추가 ⇒ 숫자를 입력하는 도중에도 불필요하게 평균값을 계산 ⇒ 불필요한 리소스 사용 및 렌더링 지연 문제가 발생.
useMemo 훅을 이용해서 리스트에 숫자가 등록된 경우에만 평균값을 계산하도록 수정.

import React, { useMemo, useRef, useState } from "react";

const getAverage = (numbers) => {
    console.log("평균값 계산 중 ...");

    // 빈 배열인 경우 0을 반환
    if (numbers.length === 0)
        return 0;

    // 평균을 계산 => 총합을 계산해서 배열의 길이로 나눈 값을 반환
    const total = numbers.reduce((prev, curr) => prev + curr);
    console.log(total);
    return total / numbers.length;
};

export default function Average() {
    const [number, setNumber] = useState("");
    const [list, setList] = useState([]);

    const changeNumber = e => setNumber(e.target.value);
    const changeList = () => {
        const newList = list.concat(Number(number));    // [...list, number]
        setList(newList);

        setNumber("");
        refNumber.current.focus();
    };

    const refNumber = useRef();

    const avg = useMemo(() => getAverage(list), [list]);

    return (
        <>
            <div>
                <input ref={refNumber} type="number" value={number} onChange={changeNumber} />
                <button onClick={changeList}>등록</button>
            </div>
            <div>
                <p>입력값: {number}</p>
                <p>평균값: {avg}</p>
            </div>
            <div>
                등록된 숫자
                <ul>
                    {
                        list.map((n, i) => <li key={i}>{n}</li>)
                    }
                </ul>
            </div>
        </>
    );
}

5. useCallback

useMemo 훅과 유사하게 성능 최적화를 위해 사용.
useCallback은 콜백 함수가 불필요하게 다시 생성되는 걸 방지.
컴포넌트가 리렌더링될 때 동일한 콜백 함수가 사용되도록 함.
콜백 함수가 자식 컴포넌트의 props로 전달되는 경우 유용.

- props로 전달되는 함수가 재정의되어 자식 컴포넌트가 리렌더링 되는 걸 확인

import { useState, memo } from "react";
import "./App.css";

// React.memo()로 컴포넌트를 래핑하면, 
// 리액트를 컴포넌트를 렌더링하고 그 결과를 메모이징(Memoizing)해
// 다음 렌더링이 일어날 때 props가 같으면 메모이징된 내용을 재사용
const Todos = memo(({ todos, addTodo }) => {
  console.log(addTodo);
  console.log("Child component is rendering...");
  return (
    <div>
      <button onClick={addTodo}>Add Todo</button>
      <h2>Todos</h2>
      {todos.map((todo, index) => <p key={index}>{todo}</p>)}
    </div>
  );
});

export default function App() {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  const increment = () => setCount(count + 1);
  // addTodo 함수의 내용이 변경되지 않았음에도 불구하고, <Todos> 컴포넌트를 리렌더링 함 
  // Todos 컴포넌트에 addTodo props만 남겨두고, todos props를 제거해서 확인해 볼 수 있음
  const addTodo = () => {
    setTodos([...todos, "할일"]);
  };

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        <button onClick={increment}>카운트 증가</button>
        <h2>Count: {count}</h2>
      </div>
    </>
  );
}

- useCallback을 이용해서 props 변수로 전달되는 함수가 매번 재정의 되지 않도록 수정

import { useState, memo, useCallback } from "react";
import "./App.css";

const Todos = memo(({ todos, addTodo }) => {
  console.log(addTodo);
  console.log("Child component is rendering...");
  return (
    <div>
      <button onClick={addTodo}>Add Todo</button>
      <h2>Todos</h2>
      {todos.map((todo, index) => <p key={index}>{todo}</p>)}
    </div>
  );
});

export default function App() {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);

  const increment = () => setCount(count + 1);
  const addTodo = useCallback(() => {
    setTodos([...todos, "할일"]);
  }, [todos]);

  return (
    <>
      <Todos todos={todos} addTodo={addTodo} />
      <hr />
      <div>
        <button onClick={increment}>카운트 증가</button>
        <h2>Count: {count}</h2>
      </div>
    </>
  );
}

6. useContext

함수형 컴포넌트에서 Context API를 쉽게 사용할 수 있도록 도와주는 훅

- Context API

  • 컴포넌트 트리에서 전역적으로 데이터를 공유할 수 있는 방법을 제공
  • 이를 통해 부모 컴포넌트에서 자식 컴포넌트로 데이터를 반복적으로 전달하는 props drilling 문제를 해결 할 수 있다.

- useContext 의 역할

  • Context를 구독(subscribe)해 해당 context의 현재 값을 가져오는 데 사용
  • 컴포넌트가 Context 값을 사용하면, Context의 값이 변경될 때 자동으로 해당 컴포넌트가 다시 렌더링된다.

- 사용법

  • Context 생성 : React.createContext()를 사용해서 context 생성
  • Provider 정의 : Context.Provider를 사용해서context값을 하위 컴포넌트로 전달
  • Context 소비 : useContext 훅을 사용해서 context 값을 가져와서 사용

- 테마가 적용된 페이지 생성


[ThemedButton, Blog, new 컴포넌트 생성]

  • 테마 변경 버튼을 클릭하면 버튼의 배경색과 글자색을 변경한다.
  • 테마가 각 컴포넌트(블로그, 뉴스)에 적용될 수 있도록 변경 ⇒ 상태변수와 상태변수를 변경하는 함수를 부모 컴포넌트에 정의하고 각각 자식 컴포넌트의 props 변수로 전달
  • Blog와 News를 포함하는 Contents 컴포넌트를 추가 ⇒ Contents 컴포넌트는 theme 변수를 필요로 하지 않지만, 하위 컴포넌트에게 전달하기 위해서 props 변수로 받아서 처리
  • useContext 훅을 이용해서 수정
import { createContext, useContext, useState } from "react";
import "./App.css";

// #1 Context 생성
const ThemeContext = createContext();

// #2-1 Provider 정의 
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState("light");
  const changeTheme = () => setTheme(theme === "light" ? "dark" : "light");

  return (
    <ThemeContext.Provider value={{ theme, changeTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// const ThemedButton = ({ theme, changeTheme }) => {
const ThemedButton = () => {
  // #3 Context 소비
  const { theme, changeTheme } = useContext(ThemeContext);

  return (
    <button onClick={changeTheme}
      style={{
        backgroundColor: theme === "light" ? "#fff" : "#333",
        color: theme === "light" ? "#000" : "yellow"
      }}>테마 변경</button>
  );
};

// const Blog = ({ theme }) => {
const Blog = () => {
  // #3 Context 소비
  const { theme } = useContext(ThemeContext);

  return (
    <div style={{
      backgroundColor: theme === "light" ? "#fff" : "#333",
      color: theme === "light" ? "#000" : "#fff"
    }}>
      <h1>블로그</h1>
      <hr />
      <h2>블로그 제목</h2>
      <p>블로그 내용</p>
    </div>
  );
};

// const News = ({ theme }) => {
const News = () => {
  // #3 Context 소비
  const { theme } = useContext(ThemeContext);

  return (
    <div style={{
      backgroundColor: theme === "light" ? "#fff" : "#333",
      color: theme === "light" ? "#000" : "#fff"
    }}>
      <h1>뉴스</h1>
      <hr />
      <h2>뉴스 제목</h2>
      <p>뉴스 내용</p>
    </div>
  );
};

/*  하위 컴포넌트로 전달을 위한 props 변수 생성(정의)하지 않아도 됨
const Contents = ({ theme }) => {
  return (
    <>
      <Blog theme={theme} />
      <News theme={theme} />
    </>
  );
};
*/
const Contents = () => {
  return (
    <>
      <Blog />
      <News />
    </>
  );
};


// 2-2. 컨텍스트 변수를 공유할 컴포넌트를 Provider를 둘러쌈
export default function App() {
  /*  Provider에서 정의
  const [theme, setTheme] = useState("light");
  const changeTheme = () => setTheme(theme === "light" ? "dark" : "light");
  */

  /*  ThemeProvider로 감싸고, props 변수를 삭제 */
  return (
    <ThemeProvider>
      <div>
        <h1>테마가 적용된 페이지</h1>
        <ThemedButton />
        <Contents />
      </div>
    </ThemeProvider>
  );
}

  • 위 코드의 #2-1, #2-2를 아래와 같이 표현할 수도 있음
export default function App() {
  const [theme, setTheme] = useState("light");
  const changeTheme = () => setTheme(theme === "light" ? "dark" : "light");
  
  return (
    <ThemeContext.Provider value={{ theme, changeTheme }}>
      <div>
        <h1>테마가 적용된 페이지</h1>
        <ThemedButton />
        <Contents />
      </div>
    </ThemeContext.Provider>
  );
}

- 컴포넌트를 파일(모듈)로 분리

ThemeContext.js

import { createContext } from "react";

const ThemeContext = createContext();

export default ThemeContext;

ThemeProvider.js

import { useState } from "react";
import ThemeContext from "./ThemeContext";

const ThemeProvider = ({ children }) => {
    const [theme, setTheme] = useState("light");
    const changeTheme = () => setTheme(theme === "light" ? "dark" : "light");

    return (
        <ThemeContext.Provider value={{ theme, changeTheme }}>
            {children}
        </ThemeContext.Provider>
    );
};
export default ThemeProvider;

ThemedButton.js

import { useContext } from "react";
import ThemeContext from "./ThemeContext";

const ThemedButton = () => {
    const { theme, changeTheme } = useContext(ThemeContext);

    return (
        <button onClick={changeTheme}
            style={{
                backgroundColor: theme === "light" ? "#fff" : "#333",
                color: theme === "light" ? "#000" : "yellow"
            }}>테마 변경</button>
    );
};

Blog.js

import { useContext } from "react";
import ThemeContext from "./ThemeContext";

const Blog = () => {
    const { theme } = useContext(ThemeContext);

    return (
        <div style={{
            backgroundColor: theme === "light" ? "#fff" : "#333",
            color: theme === "light" ? "#000" : "#fff"
        }}>
            <h1>블로그</h1>
            <hr />
            <h2>블로그 제목</h2>
            <p>블로그 내용</p>
        </div>
    );
};
export default Blog;

News.js

import { useContext } from "react";
import ThemeContext from "./ThemeContext";

const News = () => {
    const { theme } = useContext(ThemeContext);

    return (
        <div style={{
            backgroundColor: theme === "light" ? "#fff" : "#333",
            color: theme === "light" ? "#000" : "#fff"
        }}>
            <h1>뉴스</h1>
            <hr />
            <h2>뉴스 제목</h2>
            <p>뉴스 내용</p>
        </div>
    );
};
export default News;

Contents.js

import Blog from "./Blog";
import News from "./News";

const Contents = () => {
    return (
        <>
            <Blog />
            <News />
        </>
    );
};
export default Contents;

App.js

import "./App.css";
import Contents from "./Contents";
import ThemedButton from "./ThemedButton";
import ThemeProvider from "./ThemeProvider";

export default function App() {
  return (
    <ThemeProvider>
      <div>
        <h1>테마가 적용된 페이지</h1>
        <ThemedButton />
        <Contents />
      </div>
    </ThemeProvider>
  );
}

>>> App.js를 ThemeProvider 컴포넌트를 사용하지 않고 Provider를 직접 정의하는 방식으로 변경

import "./App.css";
import { useState } from "react";
import Contents from "./Contents";
import ThemedButton from "./ThemedButton";
// import ThemeProvider from "./ThemeProvider";
import ThemeContext from "./ThemeContext";

export default function App() {
  const [theme, setTheme] = useState("light");
  const changeTheme = () => setTheme(theme === "light" ? "dark" : "light");

  return (
    <ThemeContext.Provider value={{ theme, changeTheme }}>
      <div>
        <h1>테마가 적용된 페이지</h1>
        <ThemedButton />
        <Contents />
      </div>
    </ThemeContext.Provider>
  );
}

7. 커스텀 Hook 만들기

  • userReducer 를 이용해서 여러 상태 변수를 관리하던 것을 사용자 정의 훅으로 대체
  • 상태변수와 상태변수의 값을 변경하는 핸들러 함수를 반환하는 useInputs 사용자 정의 훅 함수를 생성
import { useReducer, useState } from "react";

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

function useInputs(initState) {
  const [state, dispatch] = useReducer(reducer, initState);
  const handlerChange = e => {
    dispatch(e.target);
  };
  return [state, handlerChange];
}

function Info() {
  /*
  const [state, dispatch] = useReducer(reducer, { name: '', nickname: '' });
  const { name, nickname } = state;
  const handlerChange = (e) => {
    dispatch(e.target);
  };
  */

  const [state, handlerChange] = useInputs({ name: '', nickname: '' });
  const { name, nickname } = state;

  return (
    <>
      <div>
        <p>이름: {name}</p>
        <p>별명: {nickname}</p>
      </div>
      <div>
        <p>이름: <input type="text" value={name} name="name" onChange={handlerChange} /></p>
        <p>별명: <input type="text" value={nickname} name="nickname" onChange={handlerChange} /></p>
      </div>
    </>
  );
}

const App = () => {
  return (
    <>
      <Info />
    </>
  );
};

export default App;

OUTRO

훅 함수는 용도에 따라 종류도 다양하고, 그 용도를 이해하는 것도 어렵게 느껴집니다. 라이프사이클을 이해하고, 왜 그 시점에 이 함수가 필요한지 알아두어야합니다.

profile
지니니

0개의 댓글