본문 바로가기
프로그래밍/react

[220428] React.memo / useMemo / useCallback / Context

by 한코코 2022. 5. 5.

리액트의 개념&흐름 정리

 

JSX코드로 소통하는 리액트

리액트 앱에서는 컴포넌트를 통해 작업을 수행한다 

컴포넌트는 하나의 작업을 수행하며 JSX코드를 반환한다.

왜? 리액트가 JSX코드를 알아들으니까.

 

state의 변화로 바뀌는 props, 전역변수 context

컴포넌트에서 state, props, context로 작업하고, props는 사실 state의 변화로 생성되는 값이다.

context는 리액트에서 전역적으로 사용되는 변수라고 보면된다.

state의 변화가 일어날때마다,

컴포넌트의 변화나 컴포넌트에게 영향을 주는, 어플리케이션 일부에 영향을 미치는 데이터를 변경하게 된다.

컴포넌트에서 state를 변경할 때마다 변경된 state가 있는 컴포넌트는 재평가된다. = 컴포넌트 함수 재실행

 

재평가된 결과를 렌더링하는 reactDOM

리액트는 단순하게 최신평가의 결과를 가져와서 직전 평가의 결과와 비교한다

모든 변경사항이나 차이점을 reactDOM에 전달한다.

reactDOM을 통해 화면에 표시되는 index.js파일을 렌더링하기 때문.

reactDOM은 브라우저의 실제!DOM에 적용하고 변경되지 않은 것들은 손대지 않음.

 

리액트의 재평가

컴포넌트 재평가시 = 모든 함수를 재실행하고 코드를 다시 build = JSX코드가 최신 스냅샷의 출력결과를 다시 build

하지만 React.memo는 props 값을 먼저 보고, 실제로 변경되었을 경우에만 React.memo가 있는 컴포넌트 함수를 재실행한다.

props값이 변경되지 않았다면 컴포넌트를 실행하지 않음.

단, React.memo는 내부적으로 등호를 통해 비교를 하기때문에 원시값에는 적용되지 않는다. (객체는 참조값이니까)

그래서 나온게 useCallback.

 

 

 

 

 


React.memo(컴포넌트)

App에 변경이 발생할때마다 memo가 있는 컴포넌트로 돌아온다.

기존 props 값과 새로운 props값을 비교해 같다면 컴포넌트를 실행하지 않는다.

단, React.memo는 내부적으로 등호를 통해 비교를 하기때문에 원시값에는 적용되지 않는다. (객체는 참조값이니까)

 

그러면 왜 이걸 모든 컴포넌트에 적용하지 않을까?

최적화에는 비용이 필요하다.

아무런 대가없이 뾰롱 하고 되는건 없다.

리액트가 두가지 작업을 할 수 있어야한다.

기존 props값을 저장할 공간이 필요하고, 비교하는 작업도 필요하다.

컴포넌트를 재평가하는 비용과 props를 비교하는 성능비용을 맞바꾼 최적화.

props의 개수, 컴포넌트의 복잡도, 자식 컴포넌트의 숫자에 따라 달라지기 때문에 어느 쪽의 비용이 높다고 말하기는 없음.

자식 컴포넌트가 많아서 컴포넌트 트리가 매우 클 수록, React.memo()가 컴포넌트 트리의 상위에 위치해 있을수록 쓸데없는 재렌더링을 막을 수 있어서 유용하다.

컴포넌트가 작을수록, 부모 컴포넌트를 재평가할때마다 props 값이 자주 바뀌는 상황일수록 불필요.

 

 

 

 

 

useCallback(콜백,  배열)

재렌더링을 하면 코드를 다시 실행하고, 모든 데이터를 새롭게 생성한다는 뜻과 같다.

상태값은 바뀔지 몰라도 내용이 같은 함수를 매번 재렌더링하는 것은 비효울적이다.

선택한 함수를 리액트의 내부 저장 공간에 저장해서 함수 객체가 실행될때마다 재사용할 수 있게 해준다.

이 기능을 함수를 캐싱 또는 메모이제이션한다고 한다.

 

두번째 인자에 들어가는 배열은 useEffect처럼 '변경되었을시에만 반응한다'조건을 가졌으며, 변경된 배열값으로 함수를 재생성해준다.

cosnt clickHandler = useCallback(()=>{원하는 함수}, [배열])

 

 

 

 

useMemo(()=>({}), 배열)

useCallback이 함수를 재사용하게 해준다면, useMemo는 값을 재사용 하게 해준다.

const a = {state,dispatch}
  // a는 새로운 객체로 생성됨
  // Comment가 실행될때마다 함수가 리렌더링됨 = a가 계속 생성되며 메모리에 적재됨
  // 그래서 상태가 달라지면 값이 달라지게끔 만들어야함 = useMemo
  // 값이기 때문에 useMemo, 함수는 useCallback
  
const defaultValue = useMemo(()=>({
    state,
    dispatch
}),[state])
// [state]안에 있는 state가 변할때마다 useMemo가 실행된다

 

 

 

 


Context

이렇게 파고들아가는 경우에는 값을 하나 받으려면 전달하고전달하고전달하고전달하고를 거쳐야한다.

그게 귀찮아서 나온게 Context. 전역변수처럼 사용할 수 있다.

 

 

 

 

context 생성하기

creatContext() 메서드를 사용하면 Global이 컴포넌트가 된다.

import {createContext} from 'react'
const Global = createContext()

 

 

 

context 사용하기

값을 꺼내서 사용할때는 useContext(context명)을 사용해서 내용을 꺼낸다.

전역변수같은 context를 사용할 컴포넌트를 만든 context명.Provider로 감싸주면 된다.

넘겨주고 싶은 값은 value로 넘겨주는데, 이건 고정값이라서 다른 값으로 바꾸지 못한다.

보통 많은 정보를 넘기기 위해서 객체형태를 이루고 있다.

// A.jsx
// context 내용 사용하기
const {userid} = useContext(Global)


// App.jsx
// 컴포넌트로 context 사용하기
<Global.Provider value = {name}>
	<A name={name}/> //사용할 컴포넌트
</Global.Provider>

 

에러가 난건 아닌데 가끔 재렌더링이 부분적으로 실패할때가 있어서 useMemo로 보수한 코드

name이 변경된게 아닌데 부분적 렌더링 실패로 Global 컨텍스트가 재샐행되지 않게

useMemo안에 name이 변화한건지 한번 더 검증해주고 있음.

const obj = useMemo(()=>{name,setName return{name}},[name])
const obj = useMemo(()=>({name,setName}),[name])

<Global.Provider value = {name}>
	<A name={name}/> //사용할 컴포넌트
</Global.Provider>

 

 

 

 

 

Context 실제로 사용하기

context 사용하기 전

import {useState} from 'react'

const C = ({name}) => {
    return(
        <>
            hello {name}
        </>
    )
}

const B = ({name}) => {
    return(
        <>
            <C name={name}/>
        </>
    )
}

const A = ({name}) => {
    return(
        <>
            <B name={name}/>
        </>
    )
}

const Context = () => {
	const [value,setValue] = useState('ingoo')
    return(
        <A name={value}/>
    )
}

 

 

context 사용한 후

import {useState, createContext,useContext} from 'react'
export const Global = createContext() // 컴포넌트 선언

const C = () => {
	const {name,value} = useContext(Global) // 값을 꺼내올때
    return(
        <>
            hello {name}
            <button onClick={ ()=>{setName('ingoooo')} }>네임</button>
        </>
    )
}

const B = () => {
    return(
        <>
            <C />
        </>
    )
}

const A = () => {
    return(
        <>
            <B />
        </>
    )
}

const Context = () => {
	const [name,setName] = useState('ingoo')
    const obj = {
    	name,
        setName
    }
    return(
        <Global.Provider value={obj}> //전역변수를 설정할때
	        <A name={name}/>
        </Global.Provider>
    )
}

 

댓글