카테고리 없음

React hooks - useContext,React.memo,useCallback,useMemo

이훈히 2024. 6. 10. 17:27

useContext

//useContext
//react context의 필요성
//일반적으로 부모컴포넌트 -> 자식컴포넌트로 데이터를 전달 해 줄 때
//props를 사용했음. 그러나 부모->자식->그자식 이렇게 너무 깊어지게 되면
// prop drilling 현상이 일어남
//prop drilling의 문제점
//1. 깊이가 너무 깊어지면 이 prop이 어떤 컴포넌트로부터 왔는지 파악 힘듬
//2. 어떤 컴포넌트에서 오류가 발생할 경우 추적이 힘들어지니 대처가 늦을 수 밖에 없음
//그래서 전역적으로 쓸 수 있는 context가 필요 함  
//context API 필수 개념
//createContext : context 생성
//Consumer : context 변화 감지
//Provider : context 전달 (to 하위 컴포넌트)
//주의사항 렌더링문제  useContext를 사용할때 Provider에서 제공한
//value가 달라지면 useContext를 사용하고 있는 모든 컴포넌트가 리렌더링 됩니다->너무 비효율적임
//따라서 value 부분을 항상 신경써 줘야하는데 이후에 배우게 될 메모이제이션이 그 해답
//ex) 코드로 구현해보기
//context/FamillyContext.js
import { createContext } from "react";
//FailyContext 생성
export const FamilyContext = createContext(null);


//components/Grandfather.jsx
import React from 'react'
import Father from "./Father"
import { FamilyContext } from '../context/FamilyContext';
//GF ->Child한테 어떤 정보를 알려줘서 Child가 그내용을 출력
function GrandFather() {
    const houseName = '스파르타';
    const pocketMoney = 10000;

  return (
    //하위 폴더에 valuer {오브젝트로 데이터를 내려준다 }
  <FamilyContext.Provider value={{
    houseName : houseName,
    pocketMoney: pocketMoney
  }}>
    <Father/>
  </FamilyContext.Provider>
)
}
export default GrandFather

//components/Father.jsx
import React from 'react'
import Child from "./Child"
function Father() {
  return <Child />;
 
}
export default Father;

//components/Child
import React, { useContext } from 'react'
import { FamilyContext } from '../context/FamilyContext'

function Child() {
    //useContext 사용법
    //기본구조 const 이름 = useContext(Context이름)
    const data =useContext(FamilyContext);
  return (
    //받은데이터 사용법 이름.키이름
    <div>
    나는 집안의 막내에요 ! <br/>
    우리 가문이름은 {data.houseName}이고 <br/>
    할아버지가 용돈은 {data.pocketMoney} 만큼 주셧어요
    </div>
  )
}
export default Child

 

 

React.memo

//React Hooks- 최적화 (React.memo, useCallback,useMemo)
//리-렌더링의 발생조건
//1.컴포넌트에서 state가 바뀌었을 때
//2.컴포넌트가 내려받은 props가 변경되었을 때
//3.부모 컴포넌트가 리-렌더링 된 경우 자식 컴포넌트 모두 리렌더링
//최적화
//리액트에서 리렌더링이 빈번하게,자주일어나는건 안좋은 일
//비용이 발생하는 것은 최대한 줄여야 하는데 이런 작업을 우리는 최적화라 부른다
//불필요한 렌더링을 발생하기 않도록 하는 대표적인 방법 3가지
//memo(React.memo):컴포넌트를 캐싱
//useCallback : 함수를 캐싱
//useMemo : 값을 캐싱
//memo(React.memo)란 ?
//기본구조  주로 React.memo(컴포넌트) 를 씀
//발생조건 3번에서 자식 컴포넌트는 바뀐게 없는데 왜 리렌더링 해야 하지 ? 라고 할때
// 이 부분을 돕는 도구가 바로 React.memo다
//React.memo를 이용해서 컴포넌트를 메모리에 저장해두고 필요 할 때 쓰는데
//이렇게 하면 부모 컴포넌트의 state의 변경으로 인해 props가 변경이 일어나지 않는 한
//컴포넌트는 리렌더링 되지 않는다 이것을 컴포넌트 memoization이라고함
//ex01 ) memo 사용하기
//App.js
import React, { useState } from 'react'
import Box1 from './components/Box1';
import Box2 from './components/Box2';
import Box3 from './components/Box3';
function App() {
  const [count,setCount] = useState(0);

  //1증가
  const onPlus= ()=>setCount(count+1);
  //1감소
  const onMius=()=>setCount(count-1);
  return (
    <>
    <h3>카운트 예제입니다</h3>
    <p>현재 카운트 : {count}</p>
    <button onClick={onPlus}>+</button>
    <button onClick={onMius}>-</button>
    <div style={{
      display : "flex",
      marginTop :"10px",
    }}>
    <Box1/>
    <Box2/>
    <Box3/>
    </div>  
    </>
  )
}
export default App
//components/Box1
import React from 'react'

const style ={
  width : "100px",
  height :"100px",
  backgroundColor : "#01c49f",
  color : "white",
};
function Box1() {
  console.log("Box1 컴포넌트가 렌더링 되었습니다!")
  return (
    <div style={style}>Box1</div>
  )
}
// 이부분에서 React.memo ()
export default React.memo (Box1);

//이렇게 하면  App.js에서 버튼을 눌러 state의 변화를 줘도 리렌더링 안됌
//확인은 console이 안찍히는걸로 알 수 있음

 

 

useCallback

 

//useCallback은 인자로 들어오는 함수 자체를 기억(메모이제이션)한다.
//ex) Box1에 초기화 버튼 만들어서 count 초기화
//App.js
import React, { useCallback, useState } from 'react'
import Box1 from './components/Box1';
import Box2 from './components/Box2';
import Box3 from './components/Box3';
function App() {
  console.log("APP컴포넌트가 활성화 ")
  const [count,setCount] = useState(0);

  //1증가
  const onPlus= ()=>setCount(count+1);
  //1감소
  const onMius=()=>setCount(count-1);

  //초기화 해주는 함수
  //리렌더링 되면 onReset 함수는 새로운 주소값을 갖게 되는데
  //새로운 주소값을 갖게 되면 Box1은 새로운 props가 생겼구나 하면서 BOX1도  리렌더링 됨
  //그걸 방지하기 위한 useCallback  기본구조 useCallback(저장할 함수 ,[]) 의존성 객체 사용 []
  const onReset=useCallback(()=>{
    //의존성 배열에 []이들어가면.
    //증가나 감소 버튼을 눌러서 count가 변경되도 이 함수는 의존성 배열이 빈배열이라
    //처음 APP.js가 렌더링 될때의 count의 값이 초기값 (0)인 상태만 기억 함
    //그래서 count값이 변경되도 0에서 0으로 초기화 되었습니다. 라고 뜸
    //따라서 정상적으로 count가 출력 되려면 count의 상태변화에 따라 프롭스를 새로 내려줘야함
    //즉 의존성배열에 [count]를 입력하면 문제 해결
    console.log(`${count}에서 0으로 초기화 되었습니다`);
    setCount(0);
  },[count]);

  return (
    <>
    <h3>카운트 예제입니다</h3>
    <p>현재 카운트 : {count}</p>
    <button onClick={onPlus}>+</button>
    <button onClick={onMius}>-</button>
    <div style={{
      display : "flex",
      marginTop :"10px",
    }}>
    <Box1 onReset={onReset}/>
    <Box2/>
    <Box3/>

    </div>
   
    </>

  )
}
export default App
//
//
//components/Box1.jsx
import React from 'react'

const style ={
  width : "100px",
  height :"100px",
  backgroundColor : "#01c49f",
  color : "white",
};

function Box1({onReset}) {
  console.log("Box1 컴포넌트가 렌더링 되었습니다!")
  return (
    <div style={style}>
      <button onClick={()=>{
        onReset();
      }}>초기화</button>
    </div>
  )
}

export default React.memo(Box1);

 

 

useMemo

//useMemo
//memo는 memoization을 뜻함 기억한다는 말
//동일한 값을 반환하는 함수를 계속 호출해야 하면 필요없는 렌더링을 한다고 볼 수 있음
//맨 처음 해당 값을 반환할 때 그 값을 특별한 곳(메모리)에 저장함
//이렇게 하면 필요할 때 마다 다시 함수를 호출해서 계산하는게 아니라 이미 저장한 값을 단순히
//꺼내와서 쓸 수 있음 보통 이러한 기법을 캐싱을 한다. 라고 함
// 함수의 Value를 저장함
//ex) App.js
import React from 'react'
import HeavyComponent from './components/HeavyComponent'

//heavy work -> 엄청 무거운 작업을 한다는 예시
function App() {
  return <>
   <nav style={{
    backgroundColor:"yellow",
    marginBottom:"30px",
   }}>네이게이션 바 </nav>
   <HeavyComponent/>
   <footer style={{
    backgroundColor:"green",
    marginBottom:"30px",
   }}>
    푸터 영역이에요.
   </footer>
  </>
}

export default App

//ex) components/HeavyComponent.jsx
import React, { useState,useMemo } from 'react'

function HeavyComponent() {
    const [count,setCount] = useState(0);

    const heavyWork=()=>{
        for(let i=0 ; i<20000000000000;i++){
        return 100;
        };
    }
    //useMemo로 리턴값을 저장한 후 리턴값이 변경되지 않는 이상
    //리턴값을 불러와서 씀 -> 리렌더링 되도 일일이 heavyWork를 실행하지 않고
    //저장되 있는 값만 꺼내서 씀
    const value = useMemo(()=>heavyWork(),[])
  return <>
   <p>나는 엄청 무거운 컴포넌트야</p>
   <button
   onClick={()=>
   setCount(count+1)}
   >
    누르면 아래 count가 올라가요 </button>
    <br/>
   {count}
  </>
}
export default HeavyComponent

//ex2)
import React, { useEffect, useMemo, useState } from "react";

function App() {
  const [isAlive, setIsAlive] = useState(true);
  const [uselessCount, setUselessCount] = useState(0);


  //우리가 원하는 코드는 useEffect로 me 가 변경될 때만 useEffect가 호출되야 하지만
  //아래의 uselessCount 가 변경되면 화면이 리렌더링 되면서 me 라는 객체가 새롭게 생성 됨
  //그래서 uselessCount가 변경되서 리렌더링 될때도 useEffect가 호출 됨
  //이걸 방지하기 위해 useMemo를 씀
  //저장해 놧다가 호출해야 하기에 useMemo로 함수로만든 후 return 을 씀
  //useMemo를 쓰면 별도의 메모리에 저장되는데 이걸 남발하면 오히려 성능 저하의 문제점이 됨
  //필요할때만 쓰는게 좋음
  const me = useMemo(()=>{
    return{
    name: "Ted Chang",
    age: 21,
    isAlive: isAlive ? "생존" : "사망",
  }
  },[isAlive]);

  useEffect(() => {
    console.log("생존여부가 바뀔 때만 호출해주세요!");
  }, [me]);

  return (
    <>
      <div>
        내 이름은 {me.name}이구, 나이는 {me.age}야!
      </div>
      <br />
      <div>
        <button
          onClick={() => {
            setIsAlive(!isAlive);
          }}
        >
          누르면 살았다가 죽었다가 해요
        </button>
        <br />
        생존여부 : {me.isAlive}
      </div>
      <hr />
      필요없는 숫자 영역이에요!
      <br />
      {uselessCount}
      <br />
      <button
        onClick={() => {
          setUselessCount(uselessCount + 1);
        }}
      >
        누르면 숫자가 올라가요
      </button>
    </>
  );
}

export default App;

 

요약

useContext -> 전역으로 사용할 수 있는 props

React.memo -> 컴포넌트를 캐싱한다 

useCallback -> 인자로 들어오는 함수 자체를 저장 

useMemo -> 함수의 값을 저장해서 원할때 꺼내서 씀(캐싱)