본문 바로가기
Study/react.js

[React] React hooks 정리 part.1 - useState, useEffect, useRef, useContext + Context API

by 박히밍 2023. 2. 20.
반응형

[React] React hooks 정리 part.1 - useState, useEffect, useRef, useContext + Context API

 

[React] React hooks 정리 part.1 - useState, useEffect, useRef, useContext + Context API

매일 React hook에게 다져지는 내 자신이 안타까워

나는 오늘로서 hook을 잘게 다지기로 하였다. 뚜둔.

하지만 너무 길어서 최적화 부분은 Part.2로 넘겨야겠닫..ㅎ

 

 

 

 

🔗 useState

useState는 매우 중요하기 때문에, 별도로 정리해놓은 포스팅이 있으니 그곳으로 ㄱㄱ

 

 

https://heeeming.tistory.com/entry/React-Hook-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85%EC%9D%98-state-%EA%B4%80%EB%A6%AC-%ED%95%A8%EC%88%98-useState-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90

 

[React Hook] 리액트 훅의 state 관리 함수 useState() 함수를 사용해보자.

[React Hook] 리액트 훅의 state 관리 함수 useState() 함수를 사용해보자. 오늘은 리액트 훅 중 하나인 useState() 함수 사용법을 알아보려 한다. 오늘 글에서는 깊은 동작원리에 대해서는 다루지 않을 예

heeeming.tistory.com

 

 

 

 

 

 

 

🔗 useEffect

useEffect는 Lifecycle(생명주기)와 관련이 있고 생명주기를 이용해 특정 작업을 실행할 수 있도록 하는 hook이다.

 

 

useEffect의 실행 조건

  1. 마운트
    1. 컴포넌트가 페이지에 처음 렌더링 된 후 useEffect는 ‘무조건’ 실행된다.
  2. 업데이트
    1. useEffect의 dependency array의 state 값이 변경 될 때
    2. 부모가 리렌더링 될 때
    3. context가 바뀔 때
  3. 언마운트
    1. 컴포넌트가 페이지에서 사라질 때

 

 

기본문법

//렌더링 될 때마다 실행
useEffect(() => { 작업내용 });

//렌더링 될 때 딱 한 번만 실행
useEffect(() => { 작업내용 },[]);

//컴포넌트가 화면에 나타날 때(마운트) + dependency array의 값이 변경 될 때마다 실행
useEffect(() => { 작업내용 },[value]);

useEffect(() => { 
	
});

 

 

예제

import { useEffect, useState } from "react";

function App() {
  const [count, setCount] = useState(1);
  const [name, setName] = useState("");

  const handleCountUpdate = () => {
    setCount(count + 1);
  };

  const handleChangeName = (e) => {
    setName(e.target.value);
  };

  useEffect(() => {
    console.log("렌더링🎨");
  });

  useEffect(() => {});
  return (
    <div className="App">
      <div>
        <button onClick={handleCountUpdate}>Update</button>
        <p>count: {count}</p>
        <div>
          <input type="text" value={name} onChange={handleChangeName} />
          <p>Name: {name}</p>
        </div>
      </div>
    </div>
  );
}

export default App;

이렇게 작성하면 화면에 컴포넌트가 마운트 될 때, 버튼을 클릭해서 count 값이 하나씩 변경 될 때, input text의 value 값을 입력하면서 name의 값이 변경 될 때에도 콘솔창에 ‘렌더링🎨’ 텍스트가 중복으로 출력 된다.

 

dependency array가 존재하지 않기 때문에 각 값이 변경될 때마다 화면이 재렌더링이 일어나기 때문에 콘솔창에 텍스트가 중복으로 출력되는 것이다.

 

만약 name의 값이 변경 될 땐 ‘name 렌더링🙋‍♀️’ 출력 되도록 하고 count의 값이 변경 될 땐 ‘count 렌더링💯’이 출력 되도록 하려면

 

 

import { useEffect, useState } from "react";

function App() {
  const [count, setCount] = useState(1);
  const [name, setName] = useState("");

  const handleCountUpdate = () => {
    setCount(count + 1);
  };

  const handleChangeName = (e) => {
    setName(e.target.value);
  };

  // useEffect(() => {
  //   console.log("렌더링🎨");
  // });
  useEffect(() => {
    console.log("name 렌더링🙋‍♀️");
  }, [name]);
  useEffect(() => {
    console.log("count 렌더링💯");
  }, [count]);

  useEffect(() => {});
  return (
    <div className="App">
      <div>
        <button onClick={handleCountUpdate}>Update</button>
        <p>count: {count}</p>
        <div>
          <input type="text" value={name} onChange={handleChangeName} />
          <p>Name: {name}</p>
        </div>
      </div>
    </div>
  );
}

export default App;

이렇게 작성해주면 된다.

 

 

해석하자면,

  // 렌더링 될 때마다 실행
  useEffect(() => {
    console.log("렌더링🎨");
  });

  // 마운트 + [name]가 변화 될 때마다 실행
  useEffect(() => {
    console.log("name 렌더링🙋‍♀️");
  }, [name]);

  // 마운트 + [count]가 변화 될 때마다 실행
  useEffect(() => {
    console.log("count 렌더링💯");
  }, [count]);

만약 저렇게 재렌더링이 될때마다, 혹은 값이 변경될 때마다 실행되는 것이 아니라

화면이 렌더링 될 때 딱 한 번만 실행되도록 하려면 어떻게 해야할까?

 

 

// 마운팅 될 때 딱 한번만 실행
useEffect(() => {
  console.log("마운팅🧗‍♀️");
}, []);

위와 같이 dependency array를 빈 배열로 두면 처음 마운팅이 될 때 딱 한 번 실행 된 뒤

state값이 변경되거나, 화면이 재렌더링이 되더라도 ‘마운팅🧗‍♀️’이라는 메시지는 두번 다시 출력되지 않는다.

 

 

import { useEffect, useState } from "react";

function App() {
  const [count, setCount] = useState(1);
  const [name, setName] = useState("");

  const handleCountUpdate = () => {
    setCount(count + 1);
  };

  const handleChangeName = (e) => {
    setName(e.target.value);
  };

  // 렌더링 될 때마다 실행
  useEffect(() => {
    console.log("렌더링🎨");
  });
  // 마운트 + [name]가 변화 될 때마다 실행
  useEffect(() => {
    console.log("name 렌더링🙋‍♀️");
  }, [name]);
  // 마운드 + [count]가 변화 될 때마다 실행
  useEffect(() => {
    console.log("count 렌더링💯");
  }, [count]);

  // 마운팅 될 때 딱 한번만 실행
  useEffect(() => {
    console.log("마운팅🧗‍♀️");
  }, []);

  useEffect(() => {});
  return (
    <div className="App">
      <div>
        <button onClick={handleCountUpdate}>Update</button>
        <p>count: {count}</p>
        <div>
          <input type="text" value={name} onChange={handleChangeName} />
          <p>Name: {name}</p>
        </div>
      </div>
    </div>
  );
}

export default App;

 

 

 

 

useEffect의 Clean Up(언마운트)

 

 

기본문법

//언마운트, 업데이트 직전 마다 실행
useEffect(() => {
    // ...실행내용
    return () => { // 컴포넌트가 언마운트 될 때 실행 될 Clean-up 함수
      // Clean-up 함수 실행 내용 (초기화)
    };
});

//언마운트 될 때만 실행
useEffect(() => {
    // ...실행내용

    return () => { // 컴포넌트가 언마운트 될 때 실행 될 Clean-up 함수
      // Clean-up 함수 실행 내용 (초기화)
    };
}, []);

 

사용 이유

컴포넌트가 언마운트되거나, 업데이트 되기 직전에 작업을 수행하는 경우 useEffect 함수 내에서 Clean-up 함수를 반환해주면 되고

Event, setInterval, setTimeout 등 이펙트들을 정리해줌으로써, 메모리 누수를 막기 위해 사용한다.

정확한 이유는 아래 예제를 통해 알 수 있다.

 

 

//Timer.js
import { useEffect } from "react";

const Timer = (props) => {
  useEffect(() => {
    console.log("타이머가 호출 되었군요!");
    const timer = setInterval(() => {
      console.log("타이머가 실행중...");
    }, 1000); // timer가 1초마다 실행
  }, []);
  return (
    <>
      <div>타이머를 시작합니다. 콘솔창을 보세요.</div>
    </>
  );
};

export default Timer;
//App.js
import { useEffect, useState } from "react";
import Timer from "./Timer";

function App() {
  const [showTimer, setShowTimer] = useState(false);
  return (
    <div className="App">
      <button
        onClick={() => {
          setShowTimer(!showTimer);
        }}
      >
        Toggle Timer
      </button>
      {showTimer && <Timer />}
    </div>
  );
}

export default App;

이렇게 작성한 뒤 Toggle Timer 버튼을 클릭해보자.

그러면 화면에 타이머를 시작합니다~ … 텍스트가 렌더링 되면서

콘솔창에 ‘타이머가 실행중...’ 메시지가 1초마다 실행 될 것이다.

만약 Toggle Timer 버튼을 다시 한 번 더 클릭한다면

타이머를 시작합니다~ … 텍스트가 사라지면서 콘솔창에 ‘타이머가 실행중...’ 메시지가 출력 되는 것도 함께 멈추게 될까?

 

 

정답은 ‘아니다.

버튼을 클릭해도 화면에 표시된 텍스트만 사라질 뿐,

콘솔창에는 ‘타이머가 실행중...’ 메시지가 여전히 1초마다 실행되는 것을 볼 수 있을 것이다.

만약 Timer 컴포넌트가 화면에서 사라질 때, 즉 언마운트 될 때 해당 값을 초기화하고자 한다면

Clean-up 함수를 이용해야 한다.

만약 Timer.js에서 Clean-up 함수를 적어준다면

//Timer.js

import { useEffect } from "react";

const Timer = (props) => {
  useEffect(() => {
    console.log("타이머가 호출 되었군요!");
    const timer = setInterval(() => {
      console.log("타이머가 실행중...");
    }, 1000); // timer가 1초마다 실행

    return () => {
      clearInterval(timer);
      console.log("타이머가 종료 되었군요.");
    };
  }, []);
  return (
    <>
      <div>타이머를 시작합니다. 콘솔창을 보세요.</div>
    </>
  );
};

export default Timer;

위와 같이 수정해주면 된다.

이렇게 수정한 뒤 Toggle Timer 버튼을 두 번 클릭하면

타이머 또한 정상적으로 멈추고 그 값이 초기화 되는 것을 볼 수 있다.

 

 

 

 

 

🔗 useRef

기본문법

const ref = useRef(value)
// useRef를 부르면 ref 객체를 반환하고. 
// 해당 객체 안에는 useRef안에 담아주었던 값은 current: vlaue로 저장된다.
// 반환값: {current: value}
// 또한 ref.current = "hello" 이런 식으로 값을 재 할당도 가능 하다.

 

 

 

useRef의 사용예제

  1. 저장공간으로 사용
    1. state 값을 변수로 사용할 땐 state값이 변화할 때마다 함수형 컴포넌트가 리렌더링(함수형 컴포넌트 재호출)이 일어나기 때문에 컴포넌트 내부 변수들이 초기화 된다.
    2. 하지만 ref를 사용하면 ref의 값을 아무리 변경해도 리렌더링 되지 않기 때문에, 따라서 컴포넌트 내부 변수들이 초기화 되지 않음.
    3. 그렇기 때문에 state가 변화되어 리렌더링이 일어나도, 값이 변화되면 안되는 변수들을 ref에 담아 보관한다.
  2. DOM 요소에 접근할 때 사용
    1. input focus 기능 등 DOM요소에 접근하고 제어할 때 사용

 

 

1. 저장공간으로 사용하는 Ref

⭐️ State와 Ref의 차이

import { useRef, useState } from "react";

function App() {
  const [count, setCount] = useState(0);
  //const countRef = useRef(0);

  console.log("렌더링!");

  const increaseCountState = () => {
    setCount(count + 1);
  };
  return (
    <div className="App">
      <p>State: {count}</p>
      <button onClick={increaseCountState}>State 올려</button>
    </div>
  );
}

export default App;

이와 같이 버튼을 클릭할 때마다, count state 값이 변하기 때문에 App component가 재렌더링이 일어나고 콘솔창에는 ‘렌더링!’ 메시지가 버튼 클릭 횟수 만큼 출력 되는 것을 확인할 수 있다.

 

 

import { useRef, useState } from "react";

function App() {
  const [count, setCount] = useState(0);
  const countRef = useRef(0);

  console.log("렌더링!");
  const increaseCountState = () => {
    setCount(count + 1);
  };
  const increaseCountRef = () => {
    countRef.current = countRef.current + 1;
    console.log(`Ref: ${countRef.current}`);
  };
  return (
    <div className="App">
      <p>State: {count}</p>
      <p>Ref: {countRef.current}</p>

      <button onClick={increaseCountState}>State 올려</button>
      <button onClick={increaseCountRef}>Ref 올려</button>
    </div>
  );
}

export default App;

코드를 위와 같이 추가해보자.

State 올려 버튼을 클릭하면 화면에 State의 값이 1씩 증가되는 것을 확인할 수 있으나,

Ref 올려 버튼을 아무리 클릭해도 화면에 Ref의 값은 0으로 변화되지 않는 것을 확인할 수 있다.

이에서 볼 수 있듯이 Ref의 값이 아무리 변화되더라도 리렌더링이 일어나지 않는다는 것을 알 수 있다.

State 올려 버튼과 Ref 올려 버튼을 각 순서대로 3번씩 클릭 후 다시 State 올려 버튼을 추가로 1회 클릭하면 그제서야 State: 4 / Ref: 3이 화면에 렌더링 되는 것을 볼 수 있을 것이다.

Ref의 값이 3으로 변경 된 뒤 State 올려 버튼을 클릭하면 화면이 재렌더링 되면서

컴포넌트 생에주기에 따라 그 값을 보존하는 성질의 Ref의 값은 그대로 유지 되기 때문에

count state의 값이 4로 재렌더링 되는 시점에 Ref: 3이 함께 렌더링 된 것이다.

여기서 볼 수 있는 Ref의 장점은
값이 자주 변화되는 변수를 State에 담는다면 리렌더링이 자주 일어나서 많은 자원을 낭비하게 될 것이나, Ref를 사용하여 변수에 담을 경우 값이 자주 바뀌더라도 리렌더링이 일어나지 않기 때문에 자원을 절약할 수 있다는 장점이 있다.

 

 

 

 

⭐️Var와 Ref의 차이

이제 기본 자료형 변수와 Ref의 차이에 대해서 알아보자.

 

 

import { useRef } from "react";

function App() {
  const countRef = useRef(0);
  let countVar = 0;

  const increaseCountRef = () => {
    countRef.current = countRef.current + 1;
    console.log(`Ref: ${countRef.current}`);
  };
  const increaseCountVar = () => {
    countVar += 1;
    console.log(`Var: ${countVar}`);
  };
  return (
    <div className="App">
      <p>State: {countRef.current}</p>
      <p>Var: {countVar}</p>

      <button onClick={increaseCountRef}>Ref 올려</button>
      <button onClick={increaseCountVar}>Var 올려</button>
    </div>
  );
}

export default App;

위와 같이 작성한 뒤 각 Ref 올려 / Var 올려 버튼을 클릭해보자.

두개의 버튼을 클릭하면 값이 변화하는 것을 콘솔창에서 확인할 수 있으나

변화된 값을 화면에 리렌더링 하지 않는 것을 확인할 수 있다.

이제 해당 값들이 변화될 때마다 리렌더링이 일어나도록 버튼을 하나 더 생성해보자.

 

 

import { useRef, useState } from "react";

function App() {
  const [renderer, setRenderer] = useState(0);
  const countRef = useRef(0);
  let countVar = 0;

  const dorendering = () => {
    setRenderer(renderer + 1);
    console.log("렌더 버튼을 클릭했어요.");
  };
  const increaseCountRef = () => {
    countRef.current = countRef.current + 1;
    console.log(`Ref: ${countRef.current}`);
  };
  const increaseCountVar = () => {
    countVar += 1;
    console.log(`Var: ${countVar}`);
  };
  return (
    <div className="App">
      <p>Ref: {countRef.current}</p>
      <p>Var: {countVar}</p>

      <button onClick={dorendering}>렌더!</button>
      <button onClick={increaseCountRef}>Ref 올려</button>
      <button onClick={increaseCountVar}>Var 올려</button>
    </div>
  );
}

export default App;

렌더라는 버튼을 생성해준 뒤

각 Ref 올려 / Var 올려 버튼을 세 번씩 클릭하고 나서 렌더 버튼을 클릭해보자.

 

Var의 값 3은 렌더링 되지 않고 0이 되었다.

Ref의 값 3은 렌더링이 되었으나 Var의 값 3은 렌더링 되지 않고 0이 되었다.

그 이유는 무엇일까?

 

렌더 버튼을 클릭하면서 함수형 컴포넌트가 재호출이 되었고, 재호출 되면서 일반 변수의 값은 0으로 할당 되어있기 때문에 0으로 초기화가 되면서 0이 렌더링 된 것이다.
반면에 Ref 언마운트시 까지 그 값을 보존하는 성질이 있기 때문에 리렌더링시 값 3이 정상적으로 출력된 것이다.
저 상태에서 Var 올려 버튼을 클릭하게 된다면 콘솔창에는 Var: 1이 출력 되는 것을 알 수 있을 것이다.

콘솔 출력화면

 

 

import { useRef, useState } from "react";

function App() {
  const [renderer, setRenderer] = useState(0);
  const countRef = useRef(0);
  let countVar = 0;

  const dorendering = () => {
    setRenderer(renderer + 1);
    console.log("렌더 버튼을 클릭했어요.");
  };
  const increaseCountRef = () => {
    countRef.current = countRef.current + 1;
    console.log(`Ref: ${countRef.current}`);
  };
  const increaseCountVar = () => {
    countVar += 1;
    console.log(`Var: ${countVar}`);
  };
  const prinResults = () => {
    console.log(`ref: ${countRef.current}, var: ${countVar}`);
  };
  return (
    <div className="App">
      <p>Ref: {countRef.current}</p>
      <p>Var: {countVar}</p>

      <button onClick={dorendering}>렌더!</button>
      <button onClick={increaseCountRef}>Ref 올려</button>
      <button onClick={increaseCountVar}>Var 올려</button>
      <button onClick={prinResults}>Ref Var 값 출력</button>
    </div>
  );
}

export default App;

위 코드의 버튼을 각각 눌러보며 렌더링 전 후로 Ref의 값과 Var의 값이 어떻게 유지되고 초기화되는지 살펴보도록 하자.

 

 

 

다른 예제

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

function App() {
  const [count, setCount] = useState(1);
  const [renderCount, setRenderCount] = useState(1);

  useEffect(() => {
    console.log("렌더링!");
    setRenderCount(renderCount + 1);
  });
  return (
    <div className="App">
      <p>Count: {count}</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        올려
      </button>
    </div>
  );
}

export default App;

위와 같이 작성할 경우

 

콘솔 에러 발생

useEffect 내부에서 컴포넌트가 마운트될 때 setRenderCount(renderCount + 1); 이 지속적으로 실행되게 되고 renderCount가 변화되므로써, 리렌더링이 일어나는 과정이 무한 반복 되므로 이와 같은 에러들이 발생할 것이다.

 

 

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

function App() {
  const [count, setCount] = useState(1);
  const refRenderCount = useRef(1);

  useEffect(() => {
    refRenderCount.current = refRenderCount.current + 1;
    console.log(`렌더링 수: ${refRenderCount.current}`);
  });
  return (
    <div className="App">
      <p>Count: {count}</p>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        올려
      </button>
    </div>
  );
}

export default App;

코드를 위와 같이 수정한 뒤 올려 버튼을 클릭해보자.

useEffect에서 무한 에러를 발생시키지 않고 렌더링 수가 정상적으로 카운트 되는 것을 확인할 수 있을 것이다.

즉, useRef는 변화된 값은 감지해야 하지만, 그 변화가 화면을 렌더링 시키면 안되는 값을 다룰 때 사용해야 한다는 것을 알 수 있다.

 

 

 

2. DOM 요소에 접근할 때 사용하는 Ref

ref에 DOM 요소를 담아보자.

 

 

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

function App() {
  const inputRef = useRef();

  useEffect(() => {
    console.log(inputRef);
  }, []);
  return (
    <div className="App">
      <div>
        <input ref={inputRef} type="text" placeholder="username" />
        <button>로그인</button>
      </div>
    </div>
  );
}

export default App;

정상적으로 인풋이 담긴 모습

inputRef에 input이 정상적으로 담긴 것을 확인할 수 있을 것이다.

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

function App() {
  const inputRef = useRef();

  useEffect(() => {
    console.log(inputRef);
    inputRef.current.focus();
  }, []);
  const login = () => {
    alert(`환영합니다. ${inputRef.current.value}님!`);
    inputRef.current.focus();
  };
  return (
    <div className="App">
      <div>
        <input ref={inputRef} type="text" placeholder="username" />
        <button onClick={login}>로그인</button>
      </div>
    </div>
  );
}

export default App;

위와 같이 작성하면

 

결과화면

화면에 접속하자마자 input 폼에 포커싱이 되어있고,

e.target.value를 사용하지 않더라도 useRef만을 이용하여 input 폼의 값을 가져와 값을 입력 후 로그인 버튼 클릭 시 input의 값이 alert 창에 반영 된 것을 확인할 수 있으며.

alert창을 닫은 뒤에도 여전히 input 폼에 포커싱이 되어 있는 것을 확인할 수 있다.

 

 

 

 

 

🔗 useContext + Context API

props를 일일히 하위컴포넌트로 전달해주지 않더라도 필요한 위치에 해당 값을 전달해줄 수 있는 전역적 데이터 전달 방법

 

 

//App.js

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

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

  return (
    <div className="App">
      <Page isDark={isDark} setIsDark={setIsDark} />
    </div>
  );
}

export default App;
//Page.js

import Content from "./Content";
import Footer from "./Footer";
import Header from "./Header";

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

export default Page;

isDark state의 값을 Header / Content / Footer 컴포넌트에 사용하기 위해서 Page.js로 props를 통해 전달하고 있으나, 정작 Page 컴포넌트에서는 사용되지 않고 전달을 해주는 브릿지 역할만 하고 있는 것을 확인할 수 있다.

//ThemeContext.js

import { createContext } from "react";

export const ThemeContext = createContext(null);

ThemeContext.js 를 생성하고

import { useState } from "react";
import "./App.css";
import Page from "./components/Page";
import { ThemeContext } from "./context/ThemeContext";

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

  return (
    <ThemeContext.Provider value={{ isDark, setIsDark }}>
      <div className="App">
        <Page />
      </div>
    </ThemeContext.Provider>
  );
}

export default App;

ThemeContext를 import한 뒤 Provider를 이용해 감싸준다.

이렇게 되면 모든 하위 컴포넌트들은 props를 사용하지 않아도

Provider value={{ isDark, setIsDark }} 로 넘겨준 값들에 접근할 수 있게 되는 것이다.

 

 

//Page.js

import { useContext } from "react";
import Content from "./Content";
import Footer from "./Footer";
import Header from "./Header";
import { ThemeContext } from "../context/ThemeContext";

const Page = () => {
  const data = useContext(ThemeContext);
  console.log(data);
  return (
    <div className="page">
      {/* <Header isDark={isDark} />
      <Content isDark={isDark} />
      <Footer isDark={isDark} setIsDark={setIsDark} /> */}
    </div>
  );
};

export default Page;

useContext()를 활용하여 ThemeContext를 data 변수에 담아준 뒤 콘솔창에 출력해보자.

 

 

isDark / setIsDark의 값이 전달 된 것을 확인할 수 있다.

위와 같이 App.js에서 전달하였던 isDark / setIsDark의 값이 전달 된 것을 확인할 수 있다.

이제 Page.js에 있던 useContext를 모두 삭제해주자.

 

 

//Page.js

import Content from "./Content";
import Footer from "./Footer";
import Header from "./Header";

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

export default Page;
//Header.js

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

const Header = () => {
  const { isDark } = useContext(ThemeContext);

  return (
    <header
      className="header"
      style={{
        backgroundColor: isDark ? "black" : "lightgrey",
        color: isDark ? "white" : "black",
      }}
    >
      <h1>Welcome 홍길동</h1>
    </header>
  );
};

export default Header;
//Content.js

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

const Content = () => {
  const { isDark } = useContext(ThemeContext);

  return (
    <div
      className="content"
      style={{
        backgroundColor: isDark ? "black" : "white",
        color: isDark ? "white" : "black",
      }}
    >
      <p>홍길동님 좋은 하루 되세요.</p>
    </div>
  );
};

export default Content;
//Footer.js

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

const Footer = () => {
  const { isDark, setIsDark } = useContext(ThemeContext);

  const toggleTheme = () => {
    setIsDark(!isDark);
  };
  return (
    <footer
      className="footer"
      style={{
        backgroundColor: isDark ? "black" : "lightgrey",
      }}
    >
      <button className="button" onClick={toggleTheme}>
        Dark Mode
      </button>
    </footer>
  );
};

export default Footer;

위와 같이 Page.js에 props로 isDark / setIsDark 를 중간에 전달하지 않아도 isDark / setIsDark를 사용해야하는 컴포넌트들만이 useContext를 이용해 전달받았다.

 

 

만약 유저 정보를 useContext를 이용해 필요한 컴포넌트에만 보내고싶다면,

//UserContext.js

import { createContext } from "react";

export const UserContext = createContext(null);
//App.js

import { useState } from "react";
import "./App.css";
import Page from "./components/Page";
import { ThemeContext } from "./context/ThemeContext";
import { UserContext } from "./context/UserContext";

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

  return (
    <UserContext.Provider value={"사용자"}>
      <ThemeContext.Provider value={{ isDark, setIsDark }}>
        <div className="App">
          <Page />
        </div>
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

export default App;
//Header.js

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

const Header = () => {
  const { isDark } = useContext(ThemeContext);
  const user = useContext(UserContext);

  return (
    <header
      className="header"
      style={{
        backgroundColor: isDark ? "black" : "lightgrey",
        color: isDark ? "white" : "black",
      }}
    >
      <h1>Welcome {user}!</h1>
    </header>
  );
};

export default Header;

위와 같이 UserContext 정보를 Header.js에 useContext를 이용해 user 변수에 담아 전달할 수 있다.

 

다크모드 구현

 

 

 

 

 

 

 

 

참고자료

유튜브 별코딩 리액트 훅 

https://youtu.be/G3qglTF-fFI

 

 

 

반응형