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 -> 함수의 값을 저장해서 원할때 꺼내서 씀(캐싱)