본문 바로가기

프로젝트 캠프 : Next.js 2기

[유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 2기 - 사전직무교육 8~9일차 후기

 

1. 조건부 렌더링

import React from 'react'
// 조건부 렌더링 : 특정조건에 따라 다른 결과를 보여주는 
// if문 , 삼항연산자, 논리연산자(&&), 즉시실행함수

const App = () => {
  const isLoggedin = false;
    // 즉시실행함수
  return (
    <>
    {(()=> {
      if (isLoggedin) {
        return <h1>Login Successful</h1>
      }
    return <h1>Login Failed</h1>
    })()}
    </>
  )
}

export default App

 

 

2. React Hook

1) useState

import React, { useState } from 'react'
import Input from './components/html/Input';
import Button from './components/html/Button';
import Checkbox from './components/html/Checkbox';

// 리액트에서 useState() 함수는 리액트가 추적/관찰 가능한 데이터를 생성한다.
// useState() 함수는 배열을 반환한다.
// 첫번째 요소는 상태 값, 두번째 요소는 상태값을 변경하는 함수이다.
// 리액트는 배치업 데이터의 형태를 가지고 있어서 효율성을 위해 useState를 한번만 일으킴.
// 콜백함수로 > set((최신상태값을 보장) => 값) 
// value와 onChange를 함께 쓰면 안정적인 useState 처리를 할 수 있다.
const App = () => {
  const [text, setText] = useState("")
  const [agree, setAgree] = useState(false)

  return (
    <>
    <div className='item-middle p-10'>
      <div className='flex flex-cal'>
        <h1>{text}</h1>
        <select value={text} onChange={(e) => setText(e.target.value)}>
          <option value="">선택</option>
          <option value="banana">banana</option>
          <option value="apple">apple</option>
        </select>
        <Checkbox 
        checked={agree} 
        onToggle={()=> setAgree((prev) =>(!prev))}>
          I agree
        </Checkbox>
        <Input type='"text' value={text} onChange={(e) => setText(e.target.value)}/>
      </div>
      <Button className='bg-rose-500 mt-4' onClick={() => setAgree((prev) => !prev)}>상태변경</Button>
    </div>
    </>
  )
}

export default App

 

 

2) useRef

리액트에서 render 메서드에서 생성된 DOM 노드나  HTML 요소에 접근하거나 컴포넌트의 렌더링에 영향없이 값을 유지하고 싶을 때 사용한다. 바람직한 사룡 사례는 다음과 같다.

  • 포커스, 텍스트 선택영역, 혹은 미디어의 재생을 관리할 때
  • 애니메이션을 직접적으로 실행시킬 때
  • 서드 파티 DOM 라이브러리를 React와 같이 사용할 때

useRef는 순수 자바스크립트 객체를 생성하며 그 객체를 수정하는 것은 컴포넌트의 렌더링과 무관하니 재렌더링이 일어나지 않는 장점이 있다. 즉, current 프로퍼티를 렌더링에 신경쓰지 않고 변경할 수 있다. 

 

useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 상자와 같다.

 

import { useRef } from "react";

const App = () => {
  const inputEl = useRef<HTMLInputElement>(null); // input 엘리먼트에 대한 참조를 생성
  const onButtonClick = () => {
    inputEl.current?.focus();
  };
  return (
    <div>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </div>
  );
};

export default App;

 

 

3) useId

React 18에서 추가된 hooks로 컴포넌트의 최상위 수준에서 호출하여 고유한 id를 생성한다. 매개변수를 사용하지 않으며, 특정 컴포넌트 내 특정 userId와 관련된 고유 문자열을 반환한다. 반복문이나 조건문 내부에서 호출할 수 없고 key를 생성하기 위해서 사용하는 것은 지양해야한다.

 

4) useCallback / usememo / React.memo ( memoization)

import React, { useCallback, useState } from 'react'

const functionSet = new Set()

const App = () => {
  const [count, setCount] = useState(0)
  const [count2, setCount2] = useState(0)

  //setCount(값)  -  setCount(()=> {})
  // useCallback(()=> {}, []); 함수를 메모이제이션 할 때 사용.
  // 은 그때 메모이제이션되었을 때의 상태를 기억하기 때문에 set내부의 함수는 콜백함수로 써야한다.
  const increment = useCallback(() => {
    setCount(count + 1)
  }, [count])
  // 의존성 배열에 count를 넘겨주게 되면, count가 변경될 때마다 재작동한다. 불필요한 메모이제이션을 경계해야.


  functionSet.add(increment)
  console.log(functionSet)
  return (
    <>
    <h1>Count: {count}</h1>
    <button onClick={increment}>클릭</button>
    <h1>Count2: {count2}</h1>
    <button onClick={() => setCount2((prev) => prev + 1)}>클릭</button>
    </>
  )
}

export default App

내가 메모이제이션 하고 싶은 컴포넌트를 React.memo로 감싼다. 그리고 props로 넘어오는 데이터가 변경되지 않게 신경쓰면 메모는 계속 유지된다. 반대로 React.memo를 설정했는데 컴포넌트가 memoization이 되지 않는다면, props의 값이 변하고 있다는 것이다.

 

성능적인 면에서 무조건적으로 memoization을 하는 것은 좋지 않다. 반복문으로 출력되는 것은 memoization 하는 게 좋지만, 갯수가 변하지 않는 컴포넌트는 성능을 오히려 떨어뜨릴 가능성이 있으니 성능비교가 필요하다.

 

props의 사용여부와는 상관없이 넘겨진 props의 값이 변경되면 컴포넌트 메모가 풀린다. 

 

 

5) useReducer : 여러 함수의 상태관리를 하나의 함수로 할 수 있게 하는 React 내장 훅.

import React, { useReducer, useState } from 'react'
// import Todo from './components/TodoCopy'

const reducer = (state: number, action: {type: string; payload?: number}) => {
  // payload를 옵셔널로 처리했기 떄문에 &&을 이용해 타입가드를 걸어줬다.
  if (action.type === "increment" && action.payload) {
    return state + action.payload
  } else if (action.type === "decrement" && action.payload) {
    return state - action.payload
  } else if (action.type === "reset") {
    return 0
  } else {
    return state
  }
}

const App = () => {
  // 첫번째 인자에 의존하고 그것을 호출하는 게 dispatch
  // 첫번째 매개변수(현재 state값이 고정)에 있는 함수가 두번째 매개변수로 들어가서 호출된다.
  // dispatch의 매개변수로 객체형태를 많이 사용(확장성이 좋음.)
  const [count, dispatch] = useReducer(reducer, 0)

  return (
    <>
    <h1>Count: {count}</h1>
    {/* 첫번째 객체는 type, 두번째는 payload를 관례상 많이 사용 */}
    <button onClick={() => dispatch({ type: "decrement", payload: 4})}>감소</button>
    <button onClick={() => dispatch({ type: "reset"})}>0</button>
    <button onClick={() => dispatch({ type: "increment", payload: 4})}>증가</button>
    </>
  )
}

export default App

 

여기서 쓰는 dispatch는 항상 reducer라는 함수를 가리키므로 참조값이 변하지 않고 렌더링마다 재생성되지 않기에 자동으로 memoizaion된다. 따라서 reducer를 사용하면 memoezation하는 메모리 비용을 낮출 수 있다.

 

3. Context API

여러 컴포넌트에 제공되는 컨텍스트 값의 장점은 state와 연결이 쉽다는 점이다. 이 연결을 통해 모든 컴포넌트에 접근할 수 있으며, 여러 컴포넌트를 거쳐서 전달되는 불필요한 props를 제거할 수 있다. 대신 state에 연결된 컨텍스트 값이 앱의 모든 컴포넌트에 제공할수 있고 필요한 컴포넌트만 골라서 전달할 수 있다.

실무에선 context 폴더로 따로 만들고, 거기서 아래의 방식으로 export해서 사용한다. 이게 컴포넌트에 직접 들어가게 되면 memoization을 직접 해줘야 하지만, 아래의 방식을 사용하면 고차 컴포넌트를 반환할 수 있는 형태가 되어 재렌더링되지 않는다. 그리고 이렇게 기본값을 잘 설정해주면 자동완성도 십분 활용할 수 있어 편리하다.

export const CounterContextProvider = ({
children,
} : {
  children: React.ReactNode;
})

 

타입 with, without

* 고차컴포넌트 : 컴포넌트를 반환하는 컴포넌트/함수. ex) React.memo

share - secret - copyURL