ESLint 코드 분석 방법

1) JS 코드를 문자열로 인식 

2) 파서로 코드 구조화

3) 2에서 구조화한 AST(Abstract syntax tree)를 기준으로 규칙과 대조

4) 위반한 코드 report or fix

 

자바스크립트 & 타입스크립트 코드 분석

- expree 기반 파서

- AST explorer : https://astexplorer.net 


 ESLint 관련 패키지

- eslint-plugin ~ : 특정 프레임워크, 도메인 관련 규칙  ex ) eslint-plugin-import, eslint-plugin-react

- eslint-config ~ : eslint-plugin 을 묶어서 세트로 제공하는 패키지 

- eslint-config-airbnb : 가장 유명한, 수많은 개발자가 유지보수하고 있는 Airbnb사 패키지 ⭐️

- @titicaca/triple-config-kr : 한국 트리플사에서 개발

- eslint-config-next : 리액트 기반 Next.js 프레임워크 


ESLint 규칙 만들기

// .eslintrc.js 파일

module.exports = {
	rules: {
    	'no-restricted-imports': [
        	'error',
            {
            	path: [
                    name: 'react', // 모듈명
                    importNames: ['default'], // 모듈의 이름
                    message: "import React from 'react'는 react 17 버전부터 필요하지 않습니다. 필요한 기능만 react에서 import하여 사용하십시오.",
                ]
            }
        ]
    }
}


ESLint 주의사항

- Prettier 설정 충돌 -> eslint-plugin-prettier 사용

- 규칙 예외 처리 : eslint-disable- 주석 사용  ex ) eslint-disable-line, eslint-disable-deps

- ESLint 버전 추돌 : 최신 ESLint / eslint-config 패키지간 버전 충돌


Test

- 테스트 : 개발자가 만든 프로그램이 코딩을 한 의도대로 작동하는지 확인하는 일련의 작업

- 백엔드는 주로 화이트박스 테스트, 프론트엔드는 블랙박스 테스트

- 애플리케이션에서 가장 취약하거나 중요한 부분을 파악해 테스트

 

 

Test 코드 작성법

1) 테스트 함수, 모듈 선정

2) 반환하길 기대하는 값 작성

3) 실제 반환값 작성

4) 2-3 일치 확인

5) 기댓값 반환시 테스트 성공


 React Testing Library

- 리액트 기반 환경에서 리액트 컴포넌트를 테스팅하는 라이브러리

- Assertion Library : 테스트 결과 확인 라이브러리  ex ) Node.js assert, should.js, expect.js, chai

- cf )  DOM Testing Library : jsdom 기반

ㄴ jsdom : 순수 자바스크립트로 작성된 라이브러리, HTML이 없는 JS 환경(node.js) 에서 HTML +DOM 을 사용할 수 있게 도와줌

 

Test Framework

- JS : Jest, Mocha, Karma, Jasmine

- React : Jest (import 없이 global하게 사용 가능)


리액트 컴포넌트 테스트

1) 컴포넌트 렌더링

2) 특정 액션 수행

3) 1-2 비교


컴포넌트 타입

- 정적 컴포넌트 : 별토의 상태가 존재하지 않고 항상 같은 결과를 반환하는 컴포넌트

- 동적 컴포넌트 : 상태(state)가 있는 컴포넌트  ex ) useState 사용

 

테스트 라이브러리

- fetch 등 비동기 이벤트가 발생하는 컴포넌트 : MSW(Mock Service Worker) 사용

- 사용자 정의 훅 테스트 : react-hooks-testing-library

 

그 외 프론트엔드 테스트

- 단위 테스트(Unit Test) : 각각의 코드나 컴포넌트가 독립적으로 분리된 환경에서 의도된 대로 정확히 작동하는지 검증하는 테스트  ex ) React Testing Library

- 통합 테스트(Integration Test) : 유닛 테스트를 통과한 여러 컴포넌트가 묶어서 하나의 기능으로, 정상적으로 작동하는지 확인하는 테스트

- E2E Test(End to End Test) : 실제 사용자처럼 작동하는 로봇을 활용하여 애플리케이션 전체 기능을 확인하는 테스트

 

 

때는 2024년 여름 어느 날, ASP.NET 와 관련 UI 라이브러리를 써서 개발 중이던 개발자 1.

해당 UI 라이브러리를 사용해서 edit 가능한 테이블을 만들고 있었는데 문제 발생 🚨

안그래도 회사에서 산 버전은 오래되어서 레퍼런스가 잘 없는데, 고치자고 API 문서를 뒤져보자니 시간 낭비만 하는 것 같아서 그냥 HTML로 만들기로 했다. (ㅠㅠ)

 

* 구현한 기능 

1) table row 추가 및 시퀀스 자동 계산

2) table row 삭제

3) 합계 계산

4) number 컬럼 숫자 체크

5) table data 저장

6) confirm = "Y" 시 readonly

- 데이터 뿌리는 건 C# 으로 구현해서 따로 js 코드에 넣진 않음

 

<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <style>
      input[type="number"]::-webkit-outer-spin-button,
      input[type="number"]::-webkit-inner-spin-button {
        -webkit-appearance: none;
        margin: 0;
      }
      .non-clickable {
        pointer-events: none;
      }
    </style>

    <script defer>
      window.onload = () => {
        const arrInput = document.getElementsByTagName("input");
        const arrTextarea = document.getElementsByTagName("textarea");
        const confirm =
          document.getElementById("confirm").value === "Y" ? true : false;

        // readonly
        if (confirm) {
          [...arrInput].forEach((input, index) => {
            input.classList.add("non-clickable");
          });

          [...arrTextarea].forEach((textarea, index) => {
            textarea.classList.add("non-clickable");
          });
        }
      };

      function isValidNumber(args, event) {
        const value = event.target.value;
        let bInteger = /^-?\d+$/.test(value) && value <= 100 && value > 0;

        if (!bInteger) {
          event.target.value = "";
          event.target.focus();
        }
        return bInteger;
      }

      function sum(args, event) {
        const elements = document.getElementsByClassName("ratio");
        let sum = 0;

        if (!isValidNumber(args, event)) {
          return false;
        }

        for (let element of elements) {
          const val = parseInt(element.value);
          if (!isNaN(val)) {
            sum += val;
          }
        }
        document.getElementsByClassName("footerSum")[0].innerText = sum + "%";
      }

      function deleteRow(args, event) {
        const table = document
          .getElementById("table")
          .getElementsByTagName("tbody")[0];
        const cnt = table.rows.length;
        const row = args.closest("tr");

        event.preventDefault();

        if (cnt <= 1) {
          alert("삭제하실 수 없습니다.");
        } else {
          row.remove();
        }
      }

      function addRow(args, event) {
        const table = document
          .getElementById("table")
          .getElementsByTagName("tbody")[0];
        const newRow = table.insertRow();

        const confirm =
          document.getElementById("confirm").value === "Y" ? true : false;
        let cnt = !confirm ? 4 : 3;

        const arrSeq = [...document.getElementsByClassName("seq")].map(
          (row) => row.value
        );
        const rownum =
          Math.max.apply(Math, arrSeq.length === 0 ? ["0"] : arrSeq) + 1 || 1;
        let focusCell = null;

        for (let i = 0; i < cnt; i++) {
          const cell = newRow.insertCell(i);
          if (i === 0) {
            // UPJSEQ
            cell.innerHTML =
              '<input class="seq" type="number" value="' +
              rownum +
              '" onblur="isValidNumber(this, event);" />';
          } else if (i === 1) {
            // UAHEAD
            cell.innerHTML =
              '<textarea class="text" rows="3" maxlength="180"></textarea>';
            focusCell = cell;
          } else if (i === 2) {
            cell.innerHTML =
              '<input class="ratio" type="number" onblur="sum(this, event);" />';
          } else if (i === 3 && !confirm) {
            // delete button
            cell.innerHTML =
              '<button onclick="deleteRow(this, event);">삭제</button>';
          }
        }
        focusCell?.children[0]?.focus();

        return false;
      }

      async function saveTable(args, event) {
        const seq = document.getElementsByClassName("seq");
        const text = document.getElementsByClassName("text");
        const ratio = document.getElementsByClassName("ratio");
        let arrData = [];
        let bError = false;
        let msg = "";

        event.preventDefault();

        if (seq.length < 1) {
          bError = true;
          msg = "처리할 데이터가 없습니다.";
        }

        if (bError) {
          alert(msg);
          return false;
        }

        for (let i = 0; i < seq.length; i++) {
          arrData.push({
            seq: seq[i].value,
            text: text[i].value,
            ratio: ratio[i].value,
          });
        }

        let data = JSON.stringify(arrData);
        console.log(data);

        try {
          const response = await fetch("../etc/submit.js", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: data,
          });

          if (!response.ok) {
            throw new Error("Network error : "); // 500 error
          }

          const jsonResult = await response.json();
          const result = JSON.parse(jsonResult.d); // asp

          if (result.error) {
            alert(result.message);
          } else {
            alert("saved.");
          }
        } catch (error) {
          console.error("Fetch error : ", error);
        }
      }
    </script>

    <button id="save" onclick="saveTable(this, event);">SAVE</button>
    <button id="add" onclick="return addRow(this, event);">ADD</button>
    <input type="hidden" id="confirm" />

    <table border="1" id="table">
      <thead>
        <tr>
          <th style="width: 20%">SEQ</th>
          <th style="width: 40%">TEXT</th>
          <th style="width: 20%">RATIO</th>
          <th style="width: 20%">DEL</th>
        </tr>
      </thead>
      <tbody></tbody>
      <tfoot>
        <th colspan="2">SUM</th>
        <td class="footerSum"></td>
        <td></td>
      </tfoot>
    </table>
  </body>
</html>

 

SEQ TEXT RATIO DEL
SUM

 

 

 

 

React 초기 상태 관리

- 2014년 당시 웹 개발 생태계에서는 MVC 패턴이 대세, Model(JS) - View(HTML) 간 서로 변경할 수 있는 양방향 데이터 바인딩 방식

-> 웹 애플리케이션이 비대해지고, 데이터의 양도 많아짐에 따라 상태 관리가 어려워짐

-> 문제를 인식한 페이스북 팀에서 단방향 데이터 바인딩 제안 : FLUX 패턴

 

FLUX 패턴

- 단방향 데이터 바인딩을 사용하는 아키텍처 디자인 패턴

- 데이터 흐름 Action -> Dispatcher -> Model -> View (View에서 다시 Action -> .. )

1) Action : { type, payload } - type: 어떠한 작업 / payload: 작업할 데이터

2) Dispatcher : Action -> Store 로 디스패치 수행

3) Store : 실제 상태 값, Action type 에 따라 상태를 변경할 수 있는 메서드 포함

4) View : Component, Store 에서 만들어진 데이터를 화면에서 렌더링, 사용자의 행위에 Action 호출

type StoreState = {
	count: number
}

type Action = {
	type: "add";
    payload: number;
}

function reducer(prevState: StoreState, action: Action) {
	const { type: ActionType } = action;
    
    if (ActionType === "add") {
    	return { coun: prevState.count + action.payload };
    }
    
    throw new Error(`Unhandled Action [${ActionType}]`);
}

export default function App() {
	const [state, dispatcher] = useReducer(reducer, { count: 0 }); 
    
    function handleClick() {
    	dispatcher({ type: "add", payload: 1 });
    }
    
    return (
    	<div>
        	<h1>{state.count}</h1>
            <button onclick={handleClick}> + </button>
        </div>
    );
}

 

단방향 데이터 바인딩

- 사용자의 입력에 따라 데이터를 갱신하고, 화면을 업데이트 해야하는지 코드로 작성 -> 코드 양 증가

- 데이터의 흐름이 단방향으로 제한되기 때문에 흐름 추적 용이, 마찬가지로 코드 가독성 향상

- 리액트 : 대표적인 단방향 데이터 바인딩 라이브러리

 

Redux

- https://mykuromi.tistory.com/13

- FLUX 구조를 구현하기 위해 만들어진 라이브러리

- Elm 아키텍처 도입 : 웹페이지를 선언적으로 작성하기 위한 언어, 데이터의 흐름을 Model(상태), View(HTML), Update(Type, Logic)로 분류

- 등장과 동시에 리액트 생테게에 큰 영향 -> Global state 를 하위 컴포넌트에 전파해 props drilling 문제 해결

- dispatcher, selector, action type 선언 등 복잡한 구조, 보일러 플레이트가 많아지는 문제 -> Redux toolkit 으로 간소화

 

Context API

- https://react.dev/reference/react/useContext

- React 버전 16.3 에서 출시 (이전에는 getChildContext 로 구현)

- props drilling 대신 Context provider 사용

 

Hook, React Query, SWR
- React 버전 16.8 에서 함수형 컴포넌트 Hook API 추가(Custom hooks ..) -> state 재사용 용이 ⭐️

- 이전에는 없던 상태관리 방식 등장 : React Query, SWR -> API 호출에 따른 상태 관리, HTTP 요청에 특화

- React Query : https://tanstack.com/query/latest/docs/framework/react/overview

- SWR : https://swr.vercel.app/ko

 

다양한 상태관리 라이브러리 등장

- Redux 라이브러리와는 다르게, 훅을 활용해 상태를 작고 효율적으로 관리 (React ver 16.8 이상 조건)

- 전역 상태 관리 패러다임에서 벗어나 개발자가 지역적으로 상태 관리

- Recoil : https://recoiljs.org

- Jotai : https://jotai.org

- Zustand : https://zustand-demo.pmnd.rs

- Valtio : https://valtio.pmnd.rs

 


 

🖤 상태 관리 범위 : useState / useReducer < Context < 상태 관리 라이브러리

 

컴포넌트 내부 상태 관리

- 가장 기본적인 상태 관리 방법

- useState : https://react.dev/reference/react/useState

- useReducer : https://react.dev/reference/react/useReducer

- useState 나 useReducer 를 기반으로 한 사용자 정의 훅을 통해 컴포넌트 상태 관리

- useState 가 선언될 때마다 state 초기화 되므로, 이는 곧 해당 컴포넌트 내에서만 유효한 지역 상태(local state)

 

useState 를 사용해서 컴포넌트 외부에서 상태 관리하기

1) Parent 컴포넌트로 state 를 끌어올리기 :  여러 컴포넌트가 동일한 상태를 사용할 수 있지만 코드가 복잡해짐  ex) parent 에서 handleClick 같은 함수 drilling

2) 컴포넌트 외부 별개의 파일에서 state 를 관리하는 경우, 리렌더링이 되지 않는 문제 발생 (컴포넌트의 state를 업데이트 치는게 아니므로)

-> useState 로 컴포넌트 외부 상태까지 관리하기에는 코드 가독성이 저해되거나, 리렌더링 문제가 발생

 

전역 상태 관리 조건

* 상태 관리 라이브러리에서 공통적으로 구현된 부분

- 컴포넌트 외부에 위치, 여러 컴포넌트가 함께 사용

- 이 상태를 참조하는 컴포넌트는 상태 변화를 감지하고 리렌더링 되어야 함 -> callback / subscribe 

- 상태가 원시값이 아니라 객체인 경우에는 객체에서 감지하는 값이 변화되어야 리렌더링

-> state, initialState, get, set, callback, subscribe, unsubscribe(cleanup) 필요

- cf) useSubscribtion : 리액트 외부에서 관리하는 값 추적, 리렌더링 훅 - https://resthooks.io/docs/api/useSubscription

 

Context 로 상태 관리

- useState 만 사용하는 것 보다 좀 더 넓게 상태 관리

- Context provider 를 지역적으로, 또는 여러 번 사용해서 컴포넌트 레이어별로 상태 관리 가능

 

상태 관리 라이브러리를 사용한 상태 관리

특징 Recoil Jotai Zustand MobX Redux
API https://recoiljs.org/ko/ https://jotai.org https://zustand-demo.pmnd.rs https://mobx.js.org/README.html https://redux-toolkit.js.org
https://redux.js.org
examples https://app.sideguide.dev/recoil/tutorial/ https://tutorial.jotai.org/examples/textLenght https://github.com/pmndrs/zustand https://mobx.js.org/README.html#a-quick-example https://redux-toolkit.js.org/tutorials/quick-start
설계 철학 간단한 상태 관리와 효율적인 상태 업데이트 간단하고 작으며 원자 단위 상태 관리 불변성을 유지하지 않는 단순 상태 관리 반응형 상태 관리와 옵저버 패턴 전역 상태 관리와 상태의예측 가능성
상태 관리방식 Atom Selector 사용 Atom 사용 Immutable 아닌 mutable 상태 관리 Observable 상태 단일 스토어와 Reducer통한 상태 관리
데이터 구조 트리 구조 트리 구조 플랫 구조 (얕은 복사) 트리 구조 트리 구조
성능 높은 성능과 효율적인 리렌더링 높은 성능과 적은 메모리 사용 매우 높은 성능과 빠른 상태 업데이트 높은 성능과 효율적인 상태 업데이트 중간 정도의 성능, 대규모애플리케이션에 적합
리렌더링제어 Atom 단위의 리렌더링제어 Atom 단위의 리렌더링 제어 선택적 리렌더링 제어 Observable 통한 효율적인 리렌더링 필요할 때만 리렌더링
미들웨어지원 제한적 제한적 지원 - immer, persist 없음 다양한 미들웨어 지원 - Thunk / Saga
커뮤니티와 생태계 작음 작음 작음 작음 매우 
개발자 도구 기본 제공 기본 제공 없음 MobX DevTools 제공 Redux DevTools 제공
동시성 관리 기본 제공 기본 제공 없음 기본 제공 기본 제공
설정 복잡도 간단 매우 간단 매우 간단 중간 정도 복잡할  있음
TypeScript 지원 기본 제공 기본 제공 기본 제공 기본 제공 기본 제공
학습 곡선 낮음 낮음 낮음 중간 정도 중간에서 높음
애플리케이션 규모 중소규모 중소규모 중소규모 중소규모에서 대규모까지 대규모

- 사용자 추이 : https://json-5.com/awesome-react-state-management

 

Recoil

- 페이스북에서 만든 상태 관리 라이브러리

- 2020년에 출시했지만 아직 정식 출시 X - npm 0.7.7 (2024.08)

- RecoilRoot : 애플리케이션 최상단에 감싸서 스토어 생성 - createContext, 감싸지 않은 컴포넌트에서는 접근 불가 

- notifyComponents(store, storeState, treeState) : 값이 변경되면 콜백을 실행해 하위 컴포넌트에 상태 변화를 알림

- atom : Recoil의 최소 상태 단위, unique 해야함 

- selector : 하나 이상의 atom을 조합할 수 있는 API

- useRecoilValue(statementsAtom) : atom 의 값을 읽어오는 메서드

- useRecoilState(recoilState) : return [useRecoilValue(recoilState), useSetRecoilState(recoilState)]

 

Jotai

- Recoil 의 atom 에서 영감을 받은 라이브러리

- atom 으로 하나 이상의 상태 생성, selector 필요 X

- Recoil 에서는 atom 의 key 를 지정해줘야 했지만, Jotai 에서는 별도의 key 가 필요하지 않음 -> 객체의 참조를 WeakMap 에 보관해 관리

- useAtomValue

- Recoil 과는 다르게 컴포넌트 루트 레벨에 Context가 존재할 필요 X -> Context 가 없으면 Provider 가 없는 형태로, 루트 레벨에 기본 스토어 생성

- Provider 를 사용하면 Context 처럼 각 Provider 별로 atom 관리 가능

- renderedIfChanged 를 통해 최신 atom 렌더링

- Recoil 에 비해 간결한 API

 

Zustand

- 리덕스에 영감을 받아 만든 라이브러리

- 하나의 중앙 집중 스토어에서 상태 관리

- ./src/vanila.ts : 프레임워크 독립적, 순수 자바스크립트 환경에서 사용 가능, 리액트에서 사용시 별도 세팅(useStore)

- 컴포넌트 내/외부 store 생성 가능, 외부 생성시 createStore

- 리덕스에 비해 짧은 코드, 가볍고 편리

 

 

'React' 카테고리의 다른 글

React strict mode  (0) 2024.07.10
Deep Dive : useReducer, useImperativeHandle  (0) 2024.07.08
240706 리액트 스터디 : useRef  (0) 2024.07.07
240629 리액트 스터디 : useRef, useContext  (0) 2024.06.29
Deep Dive : useRef, useContext  (0) 2024.06.29


 
싱글 페이지 애플리케이션

- SPA(Single Page Application)

- 자바스크립트에 의존, 자바스크립트 번들이 렌더링

- 브라우저의 history.pushState & history.replaceState 를 이용해 페이지 전환

- 페이지를 불러온 이후에는 서버에서 HTML을 내려받지 않고, 하나의 페이지에서 모든 작업 처리

- 빈 <body /> 에 DOM을 추가하는 방식으로 페이지 전환 

- ex) Gmail


서버 사이드 렌더링

-전통적인 방식

- 페이지 전환시 서버에 새로운 페이지 요청 -> HTML 페이지 다운로드 및 파싱

- 서버에서 렌더링에 필요한 작업 수행


서버 사이드 렌더링 장점

- 최초 페이지 진입이 빠름 : 보통 서버가 클라이언트보다 성능이 더 좋으므로

- 검색 엔진 최적화 / 메타 데이터 제공 : 크롤링 로봇이 자바스크립트를 실행하지 않은, 페이지의 정적 정보를 수집

- 누적 레이아웃 이동이 적음

- 사용자 디바이스 성능에 비교적 자유로움

- 보안에 안전


서버 사이드 렌더링 단점

- 소스 코드 작성시 서버 고려 : window is not defined / 클라이언트에서 실행하는 코드 실행 불가

- 적절한 서버 구축 필요

- 서비스 지연에 따른 문제 : 막대한 데이터를 포함하는 경우 페이지 렌더링 지연


싱글 페이지 애플리케이션 vs 서버 사이드 렌더링 애플리케이션

- code splitting(사용자에게 필요한 코드만 나눠서 번들링, 최적화)을 잘 한 SPA는 멀티 페이지 애플리케이션 보다 나음

- 평균적으로 SPA는 멀티 페이지 애플리케이션 보다 느림 : 서버 문제

- Paint holding, bfcache(Back forward cache), Shared element transition 과 같은 방법을 사용해 멀티 페이지 애플리케이션에서 발생하는 라우팅 관련 문제 해결


서버사이드 렌더링 관련 리액트 API

- renderToString : 인수로 넘겨받은 리액트 컴포넌트를 렌더링해 HTML 문자(string)로 반환, 클라이언트에서 실행되는 자바스크립트 코드 미포함, 렌더링 X

- renderToStaticMarkup : renderToString과 유사하게 HTML 문자열을 반환하나, hydrate를 수행하지 않는다는 가정 하에 리액트에서 사용하는 추가적인 DOM 속성 생성 X

- renderToNodeStream : 브라우저에서 사용 불가, Node.js 환경에 의존, 서버 환경에서만 사용 가능한 ReadableNodeStream 반환, Chunk 형태로 분할해서 순차적으로 데이터를 가져오는 방식

- renderToStaticNodeStream : renderToNodeStream 과 반환값은 같으나, renderToStaticMarkup과 같이 리액트 속성 미제공, hydrate를 할 힐요가 없는 순수 HTML이 필요한 경우 사용

- hydrate : renderToString / renderToNodeStream 으로 생성된 HTML 컨텐츠에 자바스크립트 핸들러나 이벤트를 붙이는 역할, 인터랙티브한 웹페이지 결과물 생성  cf) hydrate에서 만든 리액트 컴포넌트 트리와 서버에서 만든 HTML이 다르면 불일치 에러(suppressHydrationWarning)

- cf) render : 컴포넌트와 HTML 요소를 인수로 받아 HTML 요소에 해당 컴포넌트 렌더링, 이벤트 핸들러까지 붙이는 작업 모두 수행

 

Next.js

- 미국 스타트업 Vercel 에서 만든 리액트 기반 프레임워크

- PHP에 영감

- react-page 와 유사

- npx create-next-app@latest


Next.js project

- package.json 

- next.config.js : Next.js 프로젝트 환경 설정, swcMinFy: true (번들링, 컴파일 빠르게 수행)

- pages/_app.tsx : 애플리케이션 전체 / Next.js 초기화, 에러 바운더리를 사용해서 애플리케이션 전역 에러 처리, 전역 css 선언, 모든 페이지에 공통 데이터 제공

- pages/_document.tsx : 애플리케이션 HTML 초기화, DOM 속성을 추가하고 싶을 때 사용, 무조건 서버에서 실행되므로 이벤트 핸들러 사용 불가능, next/document 에서 제공하는 head 사용 가능, CSS-in_JS 스타일을 서버에서 모아 HTML로 제공

- pages/_error.tsx : 클라이언트 에러 또는 서버 500 에러 처리 목적

- pages/404.tsx : 404 페이지 정의

- pages/500.tsx : 500 에러는 이 곳에서 먼저 처리

- pages/index.tsx : 웹 사이트 루트  ex) localhost:3000

- pages/.../...tsx : ex) localhost:3000/.../... 로 라우팅

- pages/.../[id].tsx : [ ] 사이에 어떠한 문자도 올 수 있음, 예를 들어 id라는 변수에 a, b

- pages/.../[...props].tsx : .../ 의 하위 모든 주소가 이 곳으로 라우팅, props라는 변수에 배열로 담겨짐


next/link

- Next.js에서 제공하는 라우팅 컴포넌트 <Link />

- 서버 사이드 렌더링이 아닌 클라이언트 라우팅/렌더링 (SPA 방식)

- <a /> 태그와 비슷한 동작을 하나, a 태그는 서버 렌더링 -> 클라이언트에서 hydrate 시 한 번 더 실행


getServerSideProps

- 페이지에 있으면 서버 사이드에서 렌더링되는 페이지로 분류

- 페이지에 없으면 서버 사이드 렌더링이 필요없는 정적인 페이지, 빌드 시점에 미리 만들어도 되는 페이지로 분류


Data fetching

- Next.js 에서 서버 사이드 렌더링을 위한 데이터 불러오기 전략

- pages/ 폴더에 라우팅 되어 있는 파일, export 를 사용한 함수만 사용 가능

- 서버에서 미리 피요한 페이지를 만들어서 제공하거나, 해당 페이지 요청이 있을 때 서버에서 데이터 조회 후 미리 페이지를 만들어 제공

- getStaticPaths : 페이지를 정적으로 보여줄 때 접근 가능한 주소 정의, 정의되지 않은 주소는 404

- getStaticPaths { fallback } : 미리 빌드해야 하는 페이지가 많은 경우 사용, 'true'면 빌드되기 전까지는 fallback 컴포넌트를 보여주고 빌드가 완료된 이후 해당 페이지를 보여줌 <-> 'blocking'시 빌드 완료까지 사용자가 기다려야 함

- getStaticProps : 해당 페이지로 요청이 왔을 때 제공할 props를 반환하는 함수, props는 JSON으로 직렬화 할 수 있는 값만 가능, 무조건 서버에서 실행

- getInitialProps : getStaticPaths, getStaticProps 사용 권장, 페이지 데이터 불러오기

import { GetStaticPaths, GetStaticProps } from 'next';

export const getStaticPaths: GetStaticPaths = async () => { 
	return {
    	// /path/post/[id] : /post/1, /post/2 만 가능 /post/3 -> return 404
    	paths: [{ params: { id:  '1' } }, { params: { id: '2' } }],
        fallback: false,
    };
};

export const GetStaticProps: GetStaticProps = async ({ params }) => {
	const { id } = params;
    
    const post = await fetchPost(id);
    
    return {
    	post: { post },
    };
}

export default function Post({ post }: { post: Post }) {
	// page rendering with post ..
}

 

Style

- 전역 스타일 : css reset 등 글로벌 스타일, 다른 페이지 컴포넌트와 충돌할 수 있으므로 _app.tsx 에 제한적으로 작성

- 컴포넌트 레벨 css : [name].module.scss / 다른 컴포넌트와 클래스명이 충돌되지 않도록 컴파일시 고유한 클래스명으로 변환 ex) .Button_alert_62TGU 

- SASS : npm install --save-dev sass

- SCSS : export 문법 등 사용 가능

- CSS-in_JS : 자바스크립트 내부에 스타일 시트 삽입  ex) styled-component : 스타일을 모두 모아 각각의 스타일에 유니크한 클래스명 부여,서버에서  _document.tsx 를 렌더링할 때 React.Context 형태로 제공됨 

 

next.config.js

- basePath : 호스트 아래 매핑될 url 이름 ex) bathPath: docs -> localhost:3000/docs 에서 서비스 시작

- swcMinfy : swc를 사용해 코드 압축, 기본값 true

- powerByHeader : Next.js는 응답 헤더에 X-Power-by: Next.js 정보 제공, 보얀 취악점으로 분류되므로 false 설정

- redirects: 특정 주소를 다른 주소로 내보내고 싶을 때, 정규식 사용 가능

- reactStrictMode : 리액트 strict mode 사용 여부, true 면 리액트 업데이트 대비 가능

- assetPrefix : next 에서 빌드된 결과물을 동일한 호스트가 아니라, 다른 CDN에 업로드하고 싶을 때 해당 주소 명시

 

 

 

  // 사용자 고차함수
  function add(a) {
    return function (b) {
      return a + b;
    };
  }
  const result = add(1);
  const result2 = result(2);

 

HOC / HOF / Custom hook

- 고차 컴포넌트 (HOC): 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 함수. 컴포넌트의 재사용성과 로직 공유에 유용. 접두사 with 로 시작 권장, 부수 효과 최소화

- 고차 함수 (HOF): 함수를 인자로 받거나 함수를 반환하는 함수. 함수형 프로그래밍 패턴에서 사용됨

- 사용자 정의 (Custom Hook): React 훅을 사용하여 컴포넌트 로직을 캡슐화하고 재사용할 있게 해주는 함수

 

일급 객체

- 무명의 리터럴로 생성

- 변수나 자료구조(객체, 배열)에 저장

- 함수의 매개변수로 저장

- 함수의 반환값으로 사용


 
Outer/Inner Environment

- Outer Environment: 자바스크립트의 실행 컨텍스트에서 함수 외부에 정의된 변수와 함수들을 포함합니다. 함수가 호출될 때, 이 외부 환경은 함수의 상위 스코프를 제공합니다.

- Inner Environment: 함수 내부에서 정의된 변수와 함수들로, 함수 실행 중에만 유효합니다. 이 환경은 외부 환경의 변수에 접근할 수 있으며, 클로저를 통해 지속적으로 접근할 수 있습니다.

 

 클로저 (Closure)

- 클로저: 내부 함수가 외부 함수의 변수를 참조할 수 있게 해주는 기능입니다. 이는 내부 함수가 외부 함수의 실행이 끝난 후에도 변수에 접근할 수 있게 합니다.

- 클로저는 데이터 은닉과 상태 유지를 가능하게 하며, 함수형 프로그래밍과 비동기 작업에서 유용하게 사용됩니다. 클로저를 사용하면 비동기 코드에서도 상태를 유지할 수 있습니다.

 

 var

- var: 자바스크립트에서 변수 선언 시 사용되며, 함수 스코프를 가집니다. 함수 내부에서 선언된 var 변수는 함수 전체에서 유효하고, 함수 외부에서는 접근할 수 없습니다.

- 호이스팅: var 변수는 선언이 코드의 최상단으로 이동하지만 초기화는 원래 위치에서 이루어집니다. 따라서 변수는 선언 전에도 undefined로 접근 가능하며, 초기화된 이후에는 해당 값을 가집니다.

- 외부 참조 시 ReferenceError: 함수 외부에서 var로 선언된 변수에 접근하려고 하면 ReferenceError가 발생합니다. 이는 var 변수는 함수 스코프 내에서만 유효하기 때문입니다.

 

let

- let: 자바스크립트에서 블록 스코프를 가지는 변수 선언 방법입니다. 변수는 선언된 블록 내에서만 유효하며, 블록 외부에서는 접근할 수 없습니다. 이는 코드의 안정성과 가독성을 높이는 데 도움이 됩니다.

- TDZ (Temporal Dead Zone): let으로 선언된 변수는 초기화 전에는 접근할 수 없으며, 이 구역에서 변수에 접근하면 ReferenceError가 발생합니다. TDZ는 변수의 선언과 초기화 순서를 보장합니다.

- 블록 스코프: let은 블록 스코프를 지원하여, if 문, for 문 등 블록 내에서 선언된 변수는 그 블록 내에서만 유효합니다. 이로 인해 변수의 범위를 명확히 할 수 있습니다.

 

 const

- const: 자바스크립트에서 블록 스코프를 가지며, 상수 값을 선언할 때 사용됩니다. 선언과 동시에 값을 할당해야 하며, 이후 값의 재할당이 불가능합니다.

- 불변성: const는 변수의 재할당을 방지하지만, 객체나 배열의 내용은 변경할 수 있습니다. 이는 상수의 참조를 유지하며, 객체의 속성이나 배열의 요소를 변경할 수 있습니다.

- 블록 스코프: const는 블록 스코프를 가지므로, 선언된 블록 {} 내에서만 유효합니다. 이는 변수의 범위를 제한하여 코드의 안정성을 높이고, 블록 내에서의 의도치 않은 변경을 방지합니다.

 

실행 컨텍스트 (Execution Context)

- 실행 컨텍스트: 자바스크립트 코드가 실행되는 환경을 의미합니다. 실행 컨텍스트는 변수와 함수 선언, 실행 중의 스코프 등을 관리하며, 함수 호출 시 새로운 실행 컨텍스트가 생성됩니다.

- 컴포넌트: 실행 컨텍스트는 변수 객체(변수와 함수 선언), 스코프 체인(외부 환경에 대한 참조), this 값 등으로 구성됩니다. 실행 컨텍스트는 코드의 실행 시점에 생성되며, 함수 호출이 끝나면 제거됩니다.

- 콜 스택: 실행 컨텍스트는 콜 스택에 쌓여서 관리됩니다. 함수 호출 시 새로운 컨텍스트가 스택에 추가되고, 함수 실행이 완료되면 스택에서 제거됩니다. 이 스택 구조는 함수 호출과 실행 흐름을 관리합니다.

 

 힙 (Heap)

- 힙: 자바스크립트에서 메모리 관리의 영역으로, 동적으로 할당된 메모리를 저장하는 곳입니다. 객체, 배열, 함수 등과 같은 동적 데이터가 힙에 저장됩니다.

- 메모리 할당: 힙은 메모리 블록을 동적으로 할당하고 관리합니다. 이 영역은 크기가 고정되지 않으며, 필요한 만큼의 메모리를 자유롭게 할당받을 수 있습니다.

- 가비지 컬렉션: 힙 메모리는 가비지 컬렉터에 의해 관리됩니다. 사용되지 않는 메모리는 자동으로 해제되며, 메모리 누수를 방지합니다.

 

 화살표 함수 vs 일반 함수

- 화살표 함수: 간결한 문법을 제공하며, const add = (a, b) => a + b;와 같이 사용합니다. 화살표 함수는 상위 스코프의 this 값을 그대로 사용하며, arguments 객체를 지원하지 않습니다.

- 일반 함수: function 키워드를 사용하여 선언하며, function add(a, b) { return a + b; }와 같이 작성합니다. 일반 함수는 호출 시점에 this 값을 동적으로 결정하며, arguments 객체를 지원합니다.

- 호이스팅과 생성자: 일반 함수는 호이스팅되어 함수 정의 이전에도 호출할 있지만, 화살표 함수는 변수처럼 처리되어 정의된 후에만 호출 가능합니다. 일반 함수는 생성자로 사용될 있지만, 화살표 함수는 생성자로 사용할 없습니다.

 

 

useLayoutEffect

⭐️ useLayoutEffect can hurt performance. Prefer useEffect when possible.

성능 저하 문제로 useEffect 사용 권장

 

- a version of useEffect that fires before the browser repaints the screen.

브라우저가 화면을 그리기 전에 호출되는 useEffect의 새로운 버전

import { useLayoutEffect } from 'react';

useLayoutEffect(setup, dependencies?);

 

- Parameters

1) setup: The function with your Effect’s logic. Your setup function may also optionally return a cleanup function. Before your component is added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. Before your component is removed from the DOM, React will run your cleanup function.

Effect 로직을 담은 함수. 옵셔널하게 클린업 함수를 반환 가능

컴포넌트가 돔에 추가되기 전에, 리액트는 setup 함수 실행.

-> dependencies가 바뀌고 리렌더링될 때 마다, 리액트는 먼저 제공된 이전 value로 클린업 하는 함수부터 실행

-> 이후 새로운 value로 setup 함수 실행

2) optional dependencies: The list of all reactive values referenced inside of the setup code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison. If you omit this argument, your Effect will re-run after every re-render of the component.

옵셔널 dependencies.

setup 코드 내 reactive values 리스트. props, state, 컴포넌트 body 내부에 직접 선언된 함수나 변수

depedencies 리스트는 배열과 같은 형태로 작성

리액트는 Object.is 비교 방법을 사용해서 각각의 dependency를 이전 값들과 비교

dependencies를 작성하지 않으면, 매 컴포넌트 리렌더링 후에 실행됨

 

- Returns

useLayoutEffect returns undefined.

 

- Caveats

1) useLayoutEffect is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a component and move the Effect there.

loop나 조건문 내부에서 사용 불가, 컴포넌트 top level에 작성

2) When Strict Mode is on, React will run one extra development-only setup+cleanup cycle before the first real setup. This is a stress-test that ensures that your cleanup logic “mirrors” your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, implement the cleanup function.

Strict mode에서는 첫번째 setup 전, 개발용으로 setup + cleanup 사이클이 한번 더 실행됨

이는 cleanup 로직이 setup 로직을 반영하는지, setup이 하는 게 무엇이든지 멈추고 되돌릴 수 있는지 확인하는 스트레스 테스트

만약에 문제가 있으면, cleanup 구현

3)If some of your dependencies are objects or functions defined inside the component, there is a risk that they will cause the Effect to re-run more often than needed. To fix this, remove unnecessary object and function dependencies. You can also extract state updates and non-reactive logic outside of your Effect.dependencies가 컴포넌트 내부에 선언된 함수나 객체라면, 필요 이상으로 effect 될 위험이 존재

 

필요하지 않은 객체나 함수 depedencies는 제거해야 함

4) Effects only run on the client. They don’t run during server rendering.

Effect는 서버 렌더링이 아닌, only 클라이언트 단에서만 동작

5) The code inside useLayoutEffect and all state updates scheduled from it block the browser from repainting the screen. When used excessively, this makes your app slow. When possible, prefer useEffect.

useLayout 내부의 코드는 브라우저가 리페인팅 하는 것을 막으며 과도하게 사용하면 느려짐, useEffect 권장

 

- Usage

Measuring layout before the browser repaints the screen

브라우저가 화면을 다시 그리기 전에 레이아웃 측정

function Tooltip() {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0); // You don't know real height yet

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height); // Re-render now that you know the real height
  }, []);

  // ...use tooltipHeight in the rendering logic below...
}

 

- TroubleShooting

I’m getting an error: “useLayoutEffect does nothing on the server”

useLayoutEffect가 서버에서 작동하지 않을 때 

The purpose of useLayoutEffect is to let your component use layout information for rendering:

1) Render the initial content.

2) Measure the layout before the browser repaints the screen.

3) Render the final content using the layout information you’ve read.

useLayoutEffect는 컴포넌트가 렌더링을 위해 레이아웃 정보를 알아내도록 도와줌

1 초기 컨텐츠를 렌더링 -> 2 브라우저가 화면을 그리기 전에 레이아웃 측정 -> 3 레이아웃 정보를 이용한 final 컨텐츠 렌더링

When you or your framework uses server rendering, your React app renders to HTML on the server for the initial render. This lets you show the initial HTML before the JavaScript code loads.

The problem is that on the server, there is no layout information.

서버사이드 렌더링 프레임워크를 사용하면, 리액트는 서버 HTML을 먼저 렌더링

이 때문에, 자바스크립트 코드를 로딩하기 전에 초기 HTML을 먼저 볼 수 있는 것

 

 

useDebugValue

- https://react.dev/reference/react/useDebugValue

- https://ko.react.dev/reference/react/useDebugValue

 

- React Hook that lets you add a label to a custom Hook in React DevTools.

리액트 데브툴에 라벨을 추가하도록 해주는 커스텀 hook

import { useDebugValue } from 'react';

useDebugValue(value, format?);

 

- Parameters

1) value: The value you want to display in React DevTools. It can have any type.

리액트 데브 툴에 표시하고 싶은 값, 어떤 타입이든지 가능

2) optional format: A formatting function. When the component is inspected, React DevTools will call the formatting function with the value as the argument, and then display the returned formatted value (which may have any type). If you don’t specify the formatting function, the original value itself will be displayed.

포맷팅 함수, 컴포넌트가 감지되면 리액트 데브툴은 포맷팅 함수를 호출해서 포맷된 값을 표기

정의하지 않으면 original 값이 표기

 

- Returns

useDebugValue does not return anything.

 

- Usage

1) Adding a label to a custom Hook

커스텀 hook에 라벨 추가

2) Deferring formatting of a debug value

디버그 값 포맷팅 지연

Your formatting function will receive the debug value as a parameter and should return a formatted display value. This lets you avoid running potentially expensive formatting logic unless the component is actually inspected. For example, if date is a Date value, this avoids calling toDateString() on it for every render.

포맷팅 함수는 디버그 value를 파라미터로 받고 포맷된 value를 반드시 반환

비용이 많이 드는 포맷팅 로직이 매 렌더링마다 호출되는 것을 방지

useDebugValue(date, date => date.toDateString());

 

 

Bonus) React Hook flow

1. Mount) Run lazy initializers

2. Update) Render

3. React updates DOM

4. Cleanup LayoutEffects

5. Run LayoutEffects

6. Browser paints screen

7. Cleanup Effects

8. Run Effects

⭐️useEffect return 문에 있는 cleanup 함수부터 실행 !

 

- useLayoutEffect는 클라이언트 단에서 동작하기 때문에, Next등 서버사이드 렌더링의 경우 

1) useEffect 사용 권장

2) 클라이언트 렌더링이 필요한 컴포넌트를 SetVisible과 같은 custom hook을 사용해서 lazy하게 로드

 

 

 React Strict Mode

- 애플리케이션 내에서 잠재적인 문제를 알아내기 위해 도입된 도구

- 개발 모드에서만 활성화

- 특정 레거시 기능을 비활성화하거나 애플리케이션의 잠재적인 문제를 감지하는 역할

- Strict Mode는 실제로 UI에 영향을 미치지 않고, 개발 중에만 유용한 경고 메시지를 출력

 

 기능

- 안전한 라이프사이클 메서드 검출: componentWillMount, componentWillReceiveProps, componentWillUpdate 같은 메서드는 앞으로 React에서 사용되지 않기 때문에 경고 표시

- 부작용 감지: 의도하지 않은 부작용을 감지하기 위해 두 번 렌더링, useEffect 같은 훅이 예상대로 동작하는지 확인 가능 

- 레거시 문자열 ref 사용 경고

- 함수형 컴포넌트의 Deprecated 메서드 경고

- 추가적인 경고 및 검사

 

 주요 특징

- 개발 모드 전용: Strict Mode는 개발 중에만 활성화되며, 프로덕션 빌드에는 영향을 미치지 않음

- 경고 표시: 애플리케이션 내에서 잠재적인 문제나 추천되지 않는 패턴 감지, 경고 표시

- 디버깅 도구: Strict Mode는 애플리케이션 안정성 향상, 향후 React 업데이트에 대비할 수 있도록 도와줌


Strict mode(API doc)

- https://react.dev/reference/react/StrictMode

- https://ko.react.dev/reference/react/StrictMode

 

- lets you find common bugs in your components early during development.

개발 환경에서 컴포넌트에서 공통된 버그를 찾기 위한 모드

 

- Use StrictMode to enable additional development behaviors and warnings for the component tree inside:

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

 

- StrictMode accepts no props.

 

- Caveats 

There is no way to opt out of Strict Mode inside a tree wrapped in <StrictMode>. This gives you confidence that all components inside <StrictMode> are checked. If two teams working on a product disagree whether they find the checks valuable, they need to either reach consensus or move <StrictMode> down in the tree.

StrictMode 태그 내부의 요소들은 모두 strict하게 검사되어짐

 

- Usage

1) Enabling Strict Mode for entire app

2) Enabling Strict Mode for a part of the app

 

- Strict Mode enables the following development-only behaviors:

All of these checks are development-only and do not impact the production build.

개발 환경에서만 작동되며 production 빌드 환경에는 영향을 끼치지 않음

 

1) Your components will re-render an extra time to find bugs caused by impure rendering.

순수하지 않은 렌더링으로 발생되는 버그를 찾기 위해 추가적으로 리렌더링

React assumes that every component you write is a pure function. This means that React components you write must always return the same JSX given the same inputs (props, state, and context).

리액트는 모든 컴포넌트가 순수 함수라고 가정.

즉, 리액트 컴포넌트는 항상 같은 input에 대해 같은 output(JSX)를 반환

Components breaking this rule behave unpredictably and cause bugs. To help you find accidentally impure code, Strict Mode calls some of your functions (only the ones that should be pure) twice in development.

impure한 코드를 찾기 위해 Strict mode 개발 환경에서 아래와 같은 함수는 두 번 실행됨

This includes:

a. Your component function body (only top-level logic, so this doesn’t include code inside event handlers)

컴포넌트 top level logic 함수(이벤트 핸들러 내부의 코드는 불포함)

b. Functions that you pass to useState, set functions, useMemo, or useReducer

useState, useMemo, useReducer, set functions에 전달되는 함수

c. Some class component methods like constructor, render, shouldComponentUpdate (see the whole list)

생성자, 렌더, 컴포넌트 업데이트와 같은 클래스 컴포넌트 메서드

If a function is pure, running it twice does not change its behavior because a pure function produces the same result every time. However, if a function is impure (for example, it mutates the data it receives), running it twice tends to be noticeable (that’s what makes it impure!) This helps you spot and fix the bug early.

순수 함수라면 항상 같은 결과를 반환하기 때문에, 두 번 실행되더라도 그 behavior가 변경되는 것은 아님

받은 데이터를 변경하는 것과 같이 순수한 함수가 아니라면 , 버그를 더 빨리 찾을 수 있도록 두 번 실행됨.

let stories = [
  {id: 0, label: "Ankit's Story" },
  {id: 1, label: "Taylor's Story" },
];

export default function StoryTray({ stories }) {
  // const items = stories; // impure because modify array directly
  const items = stories.slice(); // Clone the array
  items.push({ id: 'create', label: 'Create Story' });
  return (
    <ul>
      {items.map(story => (
        <li key={story.id}>
          {story.label}
        </li>
      ))}
    </ul>
  );
}

 

2) Your components will re-run Effects an extra time to find bugs caused by missing Effect cleanup.

effect 클린업이 빠져서 발생되는 버그를 찾기위해 추가적으로 effect

 

Strict Mode can also help find bugs in Effects.

Effects let you specify side effects that are caused by rendering itself, rather than by a particular event.

effect는 특정한 이벤트에 의해 발생되기 보다, 렌더링 하면서 발생되는 부작용 기술 

Sending a message in the chat is an event because it is directly caused by the user clicking a specific button. However, setting up a server connection is an Effect because it should happen no matter which interaction caused the component to appear. 

ex) 채팅에서 메시지 보내는 것은 이벤트, 그에 따른 서버 설정은 effect

Every Effect has some setup code and may have some cleanup code. Normally, React calls setup when the component mounts (is added to the screen) and calls cleanup when the component unmounts (is removed from the screen). React then calls cleanup and setup again if its dependencies changed since the last render.

리액트는 컴포넌트가 마운트될 때 setup을 호출하고, 언마운트될 때 cleanup을 호출

리액트가 dependencies에 변화가 있을 때 setup과 cleanup을 반복

When Strict Mode is on, React will also run one extra setup+cleanup cycle in development for every Effect. This may feel surprising, but it helps reveal subtle bugs that are hard to catch manually.

strict mode 개발 환경에서 리액트는 매 effect마다 추가적으로 setup + cleanup 사이클을 한번 더 돌게 됨

 

3) Your components will be checked for usage of deprecated APIs.

deprecated API를 사용하지 않았는지 검사

React warns if some component anywhere inside a <StrictMode> tree uses one of these deprecated APIs:

1) findDOMNode. See alternatives.

2) UNSAFE_ class lifecycle methods like UNSAFE_componentWillMount. See alternatives.

3) Legacy context (childContextTypes, contextTypes, and getChildContext). See alternatives.

4) Legacy string refs (this.refs). See alternatives.

These APIs are primarily used in older class components so they rarely appear in modern apps.

 

 

Prototype

- https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes

- 자바스크립트는 객체 지향적 언어이자 프로토타입 언어

- 객체 간 관계 정의

- 모든 객체는 다른 객체를 기반으로 생성, 이때 기반으로 생성된 객체 -> 프로토타입

- 프로토타입은 객체의 기본적 구조와 메서드를 정의하는데 사용

- 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 프로토타입 객체(prototype object) 가짐

- 프로토타입 체이닝 : 해당 객체에 직접 정의된 메서드나 속성을 찾고, 상위 프로토타입 체인을 따라 올라가면서 찾음

- 프로토타입 체이닝을 통해 상위 객체의 메서드를 사용할 수 있음

- prototype method :  arrayInstance.filter() 처럼, 내가 직접 선언한 array 인스턴스에 체이닝해서 사용, 해당 객체의 인스턴스에 대해 호출되서 사용 -> Array.prototype 에 정의

- static method : Array.isArray() 처럼, Array 객체에 직접 체이닝. Array 객체의 생성자에 정의된 메서드, 객체의 인스턴스와 독립적으로 사용 가능


Array(배열)

- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array

- 원시값이 아니라 객체

- 자바스크립트 배열은 사이즈 조정 가능, 다양한 데이터 형식을 혼합해 저장

- 숫자(정수) 인덱스를 사용해서 접근

- length 속성으로 배열의 사이즈를 늘리고 줄일 수 있음

- Sparse arrays : 빈 슬롯(empty slots)를 포함한 배열

- 배열을 중첩하여 다차원 배열로 생성 가능

- 프로퍼티 추가 가능  ex) array.property = "hi";

- 정적 메서드 : Array.from(), Array.fromAsync(), Array.isArray(), Array.of()

- 인스턴스 메서드 : Array.prototype.map(), Array.prototype.reduce() 등 공식 문서 참고

// 다차원 배열
const a = new Array(4);
for (let i = 0; i < 4; i++) {
  a[i] = new Array(4);
  for (let j = 0; j < 4; j++) {
    a[i][j] = `[${i}, ${j}]`;
  }
}

 

배열 복사 연산

- 얕은 복사 : spread 연산자, Array.from(), Array.prototype.slice(), Array.prototype.concat()

-> 값을 복사 하는게 아니라, 원본 객체와 같은 참조(메모리 내 같은 값을 가리킴)를 공유하는 복사본

- 깊은 복사 : JSON.stringify() -> Json.parse(), structuredClone()

const fruits = ["Strawberry", "Mango"];
const fruitsAlias = fruits;
// 'fruits' and 'fruitsAlias' are the same object, strictly equivalent.
fruits === fruitsAlias; // true
// Any changes to the 'fruits' array change 'fruitsAlias' too.
fruits.unshift("Apple", "Banana");
console.log(fruits);
// ['Apple', 'Banana', 'Strawberry', 'Mango']
console.log(fruitsAlias);
// ['Apple', 'Banana', 'Strawberry', 'Mango']

 

Array-like Objects(유사 배열)

- 배열처럼 숫자 인덱스 사용

- length 속성

- 배열의 메서드(push, pop, forEach, ..)를 직접 사용할 수 없음

- Array 의 인스턴스가 아님 

- ex) NodeList

const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
console.log(arrayLike.length); // 3
// arrayLike.push('d'); // TypeError: arrayLike.push is not a function

 

Working with array-like objects

- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#working_with_array-like_objects

- Some JavaScript objects, such as the NodeList returned by document.getElementsByTagName() or the arguments object made available within the body of a function, look and behave like arrays on the surface but do not share all of their methods. The arguments object provides a length attribute but does not implement array methods like forEach().

Array methods cannot be called directly on array-like objects.

Array 처럼 행동하지만, Array의 메서드를 공유하지 않는 자바스크립트 Objects

예시) NodeList, 함수의 arguments

length 속성 제공 O, forEach 메서드 제공 X

 

NodeList

- https://developer.mozilla.org/en-US/docs/Web/API/NodeList

- 노드 컬렉션 objects 

- Node.childNodes 속성이나 document.querySelectrotAll() 메서드를 호출하면 NodeList 반환

- Node.childeNodes : NodeList(Live collection), DOM의 변경사항을 실시간으로 컬렉션에 반영 

- document.querySelectorAll() : NodeList(Static collection), DOM을 변경해도 컬렉션에 영향 X

- 메서드

1) item() : 인덱스 반환

2) forEach() : NodeList의 element를 함수에 전달

3) entries(), keys(), values() : iterator 반환, 컬렉션에 포함된 모든 키/값 순회

- cf. 순서를 보장하지 않는 for .. in 문은 사용 금지, 대신 순서를 보장하는 for .. or 문 사용

var list = document.querySelectorAll("input[type=checkbox]");
Array.prototype.forEach.call(list, function (item) {
  item.checked = true;
});

 

유사 배열 객체 -> 배열 변환

- Array.from(arrayLike, mapFn?, thisArg?) : 순회 가능 객체나 유사 배열 객체를 배열로 변환 # Array.from 

- spread 연산자 사용

const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
const arr = Array.from(arrayLike);
console.log(arr); // ['a', 'b', 'c']

const nodeList = document.querySelectorAll('div');
const arr = [...nodeList];
console.log(arr); // 배열로 변환된 NodeList

 

 

useReducer

- https://react.dev/reference/react/useReducer

- https://ko.react.dev/reference/react/useReducer

 

- React Hook that lets you add a reducer to your component.

컴포넌트에 리듀서를 추가하기 위한 React Hook

 

import { useReducer } from 'react';

const [state, dispatch] = useReducer(reducer, initialArg, init?);

 

- Call useReducer at the top level of your component to manage its state with a reducer.

리듀서로 state를 관리하기 위해 컴포넌트 top level에서 호출할 것

 

- Parameters

1) reducer: The reducer function that specifies how the state gets updated. It must be pure, should take the state and action as arguments, and should return the next state. State and action can be of any types.

state가 어떻게 업데이트될지 기술하는 리듀서 함수

반드시 state와 action을 파라미터로 가지면서, 다음 state를 반환하는 순수 함수여야 함

state와 action은 어떤 타입이던지 가능

2) initialArg: The value from which the initial state is calculated. It can be a value of any type. How the initial state is calculated from it depends on the next init argument.

초기 state를 계산할 때 사용하는 값, 어떤 타입이던지 가능

3) optional init: The initializer function that should return the initial state. If it’s not specified, the initial state is set to initialArg. Otherwise, the initial state is set to the result of calling init(initialArg).

초기 state를 반환하는 초기화 함수(옵션)

init이 없을 경우 두 번째 인자 initialArg가 초기 state가 되고, 있는 경우 init 함수를 호출한 결과가 초기 state로 세팅

 

- Return

1) The current state. During the first render, it’s set to init(initialArg) or initialArg (if there’s no init).

현재 state를 리턴. 첫 번째 렌더링인 경우 initialArg나 init 값

2) The dispatch function that lets you update the state to a different value and trigger a re-render.

state를 업데이트 하고 리렌더링을 트리거하는 디스패치 함수

 

- Caveats

1) useReducer is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it.

2) In Strict Mode, React will call your reducer and initializer twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your reducer and initializer are pure (as they should be), this should not affect your logic. The result from one of the calls is ignored.

 

- dispatch function

The dispatch function returned by useReducer lets you update the state to a different value and trigger a re-render. You need to pass the action as the only argument to the dispatch function:

useReducer가 반환하는 디스패치 함수는 state를 다른 값으로 업데이트하고, 리렌더링을 트리거 함

반드시 action을 함수의 파라미터로 전달해야 함

 

const [state, dispatch] = useReducer(reducer, { age: 42 });

function handleClick() {
  dispatch({ type: 'incremented_age' });
  // ...

 

React will set the next state to the result of calling the reducer function you’ve provided with the current state and the action you’ve passed to dispatch.

리액트는 현재 state와 디스패치된 action을 인자로 하는 리듀서 함수를 실행한 결과로 다음 state를 세팅

 

- Parameters

action: The action performed by the user. It can be a value of any type. By convention, an action is usually an object with a type property identifying it and, optionally, other properties with additional information.

사용자가 행하는 action. 어떤 타입의 값이라도 가능.

action은 주로 어떤것을 서술하는 타입 속성을 가진 객체(컨벤션), 옵셔널하게 부가적인 정보를 나타내는 속성을 담기도 함.

 

- Returns

dispatch functions do not have a return value.

 

- Caveats

1) The dispatch function only updates the state variable for the next render. If you read the state variable after calling the dispatch function, you will still get the old value that was on the screen before your call.

디스패치 함는 다음 렌더링을 위한 state variable만 업데이트 함

디스패치 함수를 호출하고 state를 바로 읽으면, 디스패치 함수를 호출하기 전 old state를 가져옴

2) If the new value you provide is identical to the current state, as determined by an Object.is comparison, React will skip re-rendering the component and its children. This is an optimization. React may still need to call your component before ignoring the result, but it shouldn’t affect your code.

Object.is로 얕은 비교, 객체의 2 depth까지 비교해서 state를 업데이트 하는게 아님(렌더링 스킵)

3) React batches state updates. It updates the screen after all the event handlers have run and have called their set functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use flushSync.

리액트는 state를 업데이트 하는 것을 배치 형태로 실행. # State as Snapshot

하나의 이벤트 동안 여러번 리렌더링되는 것을 막기 위해, 모든 이벤트 핸들러가 실행되고 set 함수까지 완료한 후에 화면을 업데이트함

반면, DOM에 접근하기 위해 fulshSync(즉시 DOM 업데이트)를 사용하는 경우 React는 화면을 그전에 업데이트하게 됨

 

- Usage

import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });

  return (
    <>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        Increment age
      </button>
      <p>Hello! You are {state.age}.</p>
    </>
  );
}

 

React will pass the current state and the action to your reducer function. Your reducer will calculate and return the next state. React will store that next state, render your component with it, and update the UI.

리액트는 리듀서 함수에 현재 state와 action을 전달

-> 리듀서는 함수 실행 후 다음 state 반환

-> 리액트는 다음 state를 저장하고

-> 다음 state로 컴포넌트를 렌더링

-> 그리고 UI 업데이트

 

useReducer is very similar to useState, but it lets you move the state update logic from event handlers into a single function outside of your component. Read more about choosing between useState and useReducer.

useState랑 매우 유사하지만, state 업데이트 로직을 이벤트 핸들러에서 꺼내 컴포넌트 밖의 하나의 리듀서 함수 안에 기술.

 

The action type names are local to your component. Each action describes a single interaction, even if that leads to multiple changes in data. The shape of the state is arbitrary, but usually it’ll be an object or an array.

각 action은 그게 아무리 데이터의 여러 변화를 이끌어도, 하나의 행위만을 서술해야 함

 

- Pitfall

// State is read-only. Don’t modify any objects or arrays in state:
function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // 🚩 Don't mutate an object in state like this:
      state.age = state.age + 1;
      return state;
    }

// Instead, always return new objects from your reducer:
function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // ✅ Instead, return a new object
      return {
        ...state,
        age: state.age + 1
      };
    }

 

- Writing concise update logic with Immer

If updating arrays and objects without mutation feels tedious, you can use a library like Immer to reduce repetitive code. Immer lets you write concise code as if you were mutating objects, but under the hood it performs immutable updates:

mutation 없이 객체나 배열을 수정하는게 걸리고, 반복되는 코드를 줄이고 싶다면 Immer 라이브러리 사용

Immer 라이브러리를 사용하면 객체를 변경(mutation)하는 것 처럼 느낄지라도, 변경하지 않는(immutable) 방향으로 업데이트 됨 

 

` Immer를 사용하지 않은 코드

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Visit Kafka Museum', done: true },
  { id: 1, text: 'Watch a puppet show', done: false },
  { id: 2, text: 'Lennon Wall pic', done: false }
];

 

` Immer를 사용한 코드 

import { useImmerReducer } from 'use-immer';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

function tasksReducer(draft, action) {
  switch (action.type) {
    case 'added': {
      draft.push({
        id: action.id,
        text: action.text,
        done: false
      });
      break;
    }
    case 'changed': {
      const index = draft.findIndex(t =>
        t.id === action.task.id
      );
      draft[index] = action.task;
      break;
    }
    case 'deleted': {
      return draft.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

export default function TaskApp() {
  const [tasks, dispatch] = useImmerReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <h1>Prague itinerary</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Visit Kafka Museum', done: true },
  { id: 1, text: 'Watch a puppet show', done: false },
  { id: 2, text: 'Lennon Wall pic', done: false },
];

 

- Avoiding recreating the initial state

초기 state를 다시 생성하지 않는 법

React saves the initial state once and ignores it on the next renders.

리액트는 initial state를 한번 저장하고, 그 이후 렌더링부터는 무시함 

 

` 초기화 함수를 전달해서 재생성을 막는 법

function createInitialState(username) {
  // ...
}

function TodoList({ username }) {
  // Although the result of createInitialState(username) is only used for the initial render, 
  // you’re still calling this function on every render.  
  // const [state, dispatch] = useReducer(reducer, createInitialState(username));
  
  // To solve this, you may pass it as an initializer function to useReducer as the third argument instead:
  // If your initializer doesn’t need any information to compute the initial state, 
  // you may pass null as the second argument to useReducer.
  const [state, dispatch] = useReducer(reducer, username, createInitialState);
  // ...
}

 

- Trouble shooting

1) I’ve dispatched an action, but logging gives me the old state value.

디스패치 함수 뒤에 호출한 state가 old한 값인 경우

function handleClick() {
  console.log(state.age);  // 42

  dispatch({ type: 'incremented_age' }); // Request a re-render with 43
  console.log(state.age);  // Still 42!

  setTimeout(() => {
    console.log(state.age); // Also 42!
  }, 5000);
}

 

This is because states behaves like a snapshot. Updating state requests another render with the new state value, but does not affect the state JavaScript variable in your already-running event handler.

If you need to guess the next state value, you can calculate it manually by calling the reducer yourself:

State는 스냅샷같이 행동하기 때문에 발생

State 업데이트는 새로운 state 값으로 또 다른 렌더링을 요청함

현재 자바스크립트 변수로서의 state는 이에 영향을 받지 않음, 왜냐하면 이벤트 핸들러가 이미 진행중이기 때문.

 

2) I’ve dispatched an action, but the screen doesn’t update

디스패치 함수를 호출했는데 화면이 업데이트 되지 않는 경우

React will ignore your update if the next state is equal to the previous state, as determined by an Object.is comparison. This usually happens when you change an object or an array in state directly:

리액트는 Object.is 비교 방법으로 이전 state와 다음 state를 비교해 state 간 같은 경우 렌더링을 스킵

이 경우는 주로 state 내부의 객체나 배열을 직접적으로 수정했기 때문에 발생

 

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // 🚩 Wrong: mutating existing object
      state.age++;
      return state;
    }
    case 'changed_name': {
      // 🚩 Wrong: mutating existing object
      state.name = action.nextName;
      return state;
    }
    // ...
  }
}

 

3) I’m getting an error: “Too many re-renders”

무한 루프 렌더링 에러

You might get an error that says: Too many re-renders. React limits the number of renders to prevent an infinite loop. Typically, this means that you’re unconditionally dispatching an action during render, so your component enters a loop: render, dispatch (which causes a render), render, dispatch (which causes a render), and so on. Very often, this is caused by a mistake in specifying an event handler:

렌더링 중에 무조건적으로 action을 디스패치 하는 경우 무한 렌더링

 

// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>

// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>

// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>

 

 

 

useImperativeHandle

- https://react.dev/reference/react/useImperativeHandle

- https://ko.react.dev/reference/react/useImperativeHandle

 

- React Hook that lets you customize the handle exposed as a ref.

ref를 사용하는 핸들러를 커스터마이징하기 위한 React Hook

 

- Call useImperativeHandle at the top level of your component to customize the ref handle it exposes:

 

import { useImperativeHandle } from 'react';

useImperativeHandle(ref, createHandle, dependencies?);

 

- Parameters

1) ref: The ref you received as the second argument from the forwardRef render function.

forwardRef 렌더링 함수로 부터 전달받은 두 번째 인자 ref

2) createHandle: A function that takes no arguments and returns the ref handle you want to expose. That ref handle can have any type. Usually, you will return an object with the methods you want to expose.

인자가 없고, ref 핸들러를  반환. 어떤 타입이든지 가능한데 주로 메서드를 포함한 객체를 반환

3) optional dependencies: The list of all reactive values referenced inside of the createHandle code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison. If a re-render resulted in a change to some dependency, or if you omitted this argument, your createHandle function will re-execute, and the newly created handle will be assigned to the ref.

옵셔널한 의존성 : createHandle에서 참조된 값 리스트.

Reactive한 값으로 컴포넌트 body에 선언된 props, state, 모든 변수나 함수를 포함

의존성은 반드시 상수와 배열 형태로 구성, 리액트는 각 의존성의 이전 값과 현재 값을 Object.is 방식으로 비교

의존성에 변화가 생기거나, 이 의존성을 작성하지 않으면 createHandle 함수는 매번 실행되고 ref에 새로운 createHandle 할당

 

- Returns

useImperativeHandle returns undefined.

undefined 리턴

 

- Usage

1) Exposing a custom ref handle to the parent component.

부모 컴포넌트에서 ref 핸들러 사용

 

` case 1

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  return <input {...props} ref={ref} />; // ref : <input> DOM node
});

 

` case 2

import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input {...props} ref={inputRef} />;
});

 

Note that in the code above, the ref is no longer forwarded to the <input>.

For example, suppose you don’t want to expose the entire <input> DOM node, but you want to expose two of its methods: focus and scrollIntoView. To do this, keep the real browser DOM in a separate ref. Then use useImperativeHandle to expose a handle with only the methods that you want the parent component to call:

Now, if the parent component gets a ref to MyInput, it will be able to call the focus and scrollIntoView methods on it. However, it will not have full access to the underlying <input> DOM node.

위 코드에서 ref는 더이상 input을 가리키지 않음

예를 들자면, 전체 input DOM 노드를 가리키는 것 대신 focus와 scrollIntoView 메서드만 보여주고 싶을 때 useImeperativeHandle을 사용

그러면 부모 컴포넌트에서 자식 컴포넌트의 ref를 받을 때, 전체의 DOM 노드에 대해 접근하는 것이 아니라 메서드만 접근

대신, useImperatieHandle 내부 별도의 ref에 진짜 브라우저 DOM을 저장

 

2) Exposing your own imperative methods

커스텀 imperative 메서드

The methods you expose via an imperative handle don’t have to match the DOM methods exactly. For example, this Post component exposes a scrollAndFocusAddComment method via an imperative handle. This lets the parent Page scroll the list of comments and focus the input field when you click the button:

DOM 메서드가 아닌 메서드를 사용하고 싶을 때. 예를 들면, 스크롤과 포커스를 동시에 하는 메서드

 

'React' 카테고리의 다른 글

Deep Dive : React 상태 관리 라이브러리  (0) 2024.08.03
React strict mode  (0) 2024.07.10
240706 리액트 스터디 : useRef  (0) 2024.07.07
240629 리액트 스터디 : useRef, useContext  (0) 2024.06.29
Deep Dive : useRef, useContext  (0) 2024.06.29

 

useReducer 

- 기존 Redux 존재, 이후 React 팀에서 useReducer Hook 개발

 

선언형 프로그래밍

- Declarative programming

- "WHAT"

- 어떻게 해야할 지 내부 동작을 몰라도 되는 경우, 무엇을 해야할 지에 대해 집중

- 작업의 절차가 아닌 목표나 결과 중심으로 기술

- 세부적인 실행 과정을 추상화

- 간결한 코드, 유지보수 용이

- ex) html, css, react, jsx, js filter함수

SELECT name FROM users WHERE age > 30;


명령형 프로그래밍

- Imperative programming
- "HOW"

- 어떻게 해야할 지 서술이나 명령

- 작업을 수행하는 구체적인 절차나 순서를 기술

- 상태를 기술하는 연속적인 명령어 사용

- 상태 변화를 통해 문제를 해결 -> 상태 변화나 명령어의 순서가 중요

- 직관적이고 이해하기 쉬운 논리 흐름, 세부적 제어 가능

- ex) for문, useImperativeHandle

let total = 0;
for (let i = 0; i < 10; i++) {
  total += i;
}


함수형 프로그래밍

- Functional programming

- 순수 함수 : 같은 입력에 대해 항상 같은 출력 반환, 부작용이 없는 함수

- 불변성 : 상태나 데이터가 변경되지 않으며, 새로운 데이터를 생성하여 처리

- 고차 함수 : 함수를 인자로 받거나 함수를 반환하는 함수

- 예측 가능한 코드, 테스트 및 병렬 처리에 유리

const add = (a, b) => a + b;
const square = x => x * x;
const sumOfSquares = (a, b) => square(add(a, b));

// 함수형 업데이트 패턴 - 불변성
const state = { todos: ['Learn JavaScript', 'Learn React'] };
const newState = {
  ...state,
  todos: [...state.todos, 'Learn Redux']
};

 

 useImperativeHandle 이 구현된 이유

- 선언형 프로그래밍 방식인 React에서 명령형 프로그래밍 방식으로 구현된 부분 -> useRef, useImperativeHandle

- 사용자가 무수히 많고 복잡한 API를 가진 DOM을 직접 조작하지 않는 편의를 제공해주는 React에서 이러한 부분이 왜 생겼을까?

-> 사용자가 DOM을 조작할 필요가 없다는 React의 원칙에 어긋난 부분, useRef / useImperativeHandle은 DOM 조작 필요

 

useImpearativeHandle 을 사용하는 이유

- 사실 TypeScript에서 ref 타입을 다 잡아주는 이유로 굳이 useImperativeHandle을 사용할 필요는 없음

- DOM api가 아니라 커스텀 훅을 만들어 사용하고 싶을 때 useImperativeHandle 사용

 

'React' 카테고리의 다른 글

React strict mode  (0) 2024.07.10
Deep Dive : useReducer, useImperativeHandle  (0) 2024.07.08
240629 리액트 스터디 : useRef, useContext  (0) 2024.06.29
Deep Dive : useRef, useContext  (0) 2024.06.29
240622 리액트 스터디 : useMemo  (0) 2024.06.22

+ Recent posts