# 2024. 09.

# 09. 01.

# React에서 key는 내부적으로 어떻게 처리될까?

React는 내부적으로 key를 통해 컴포넌트 인스턴스를 해시맵 형태로 저장한다. 이 후 리렌더링 과정에서 React는 이전 렌더링의 key-인스턴스 맵을 참조하고 동일한 key를 가진 새 엘리먼트가 발견되면, React는 해당 인스턴스를 재사용한다.

react/react-reconciler/ReactChildFiber.js - updateSlot (opens new window)

해당 코드에서 이전 key가 존재하면 (opens new window) updateElement를 호출하여 재사용해주고, 존재하지 않으면 (opens new window) null을 반환해서 Fiber를 새로 생성하는 과정을 거친다.

# 09. 02.

# 컴포넌트 다시 생각하기

# 09. 04.

# React Lane은 내부적으로 어떻게 동작할까?

Lane은 React에서 관리하는 비트 단위의 우선순위로 32개(0~31)로 이루어져있다.

// Lane values below should be kept in sync with getLabelForLane(), used by react-devtools-timeline.
// If those values are changed that package should be rebuilt and redeployed.
export const TotalLanes = 31;
export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;
export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lanes = /*            */ 0b0000000000000000000000000000100;
export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000001000;
export const DefaultLane: Lanes = /*                    */ 0b0000000000000000000000000010000;
const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /*                       */ 0b0000000001000000000000000000000;
const RetryLanes: Lanes = /*                            */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /*                             */ 0b0000100000000000000000000000000;
export const SomeRetryLane: Lane = RetryLane1;
export const SelectiveHydrationLane: Lane = /*          */ 0b0001000000000000000000000000000;
const NonIdleLanes = /*                                 */ 0b0001111111111111111111111111111;
export const IdleHydrationLane: Lane = /*               */ 0b0010000000000000000000000000000;
export const IdleLane: Lanes = /*                       */ 0b0100000000000000000000000000000;
export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;

Lane은 Fiber의 우선순위를 가르키고 있으며 Fiber에서 관리한다. Fiber는 본인의 Lane과 자식들의 Lane(ChildLane)정보를 가지고 있다. React는 렌더링 재조정 과정에서 우선순위가 높은 Fiber를 우선적으로 렌더링한다. 대부분 업데이트는 1번 우선순위인 Sync Lane이고 개발자가 useTransition이나 useDeferredValue 등을 이용해서 우선순위를 설정할 수 있다. Fiber가 상태 변화로 Lane이 설정될 때, 해당 Lane은 부모 Fiber의 ChildLane에 비트 | 연산으로 합쳐진다. 리렌더링 과정에서 React는 Root의 childLane을 확인하고 우선순위가 가장 높은 lane부터 처리한다. lane을 통해 어느 자식을 리렌더링 해야될지 알게되는데 Root부터 childLane을 검사해 자식 Fiber의 childLane을 각각 확인하여 렌더링해야되는 Fiber인지 알게된다. React는 한번의 렌더링 사이클 (render phase + commit phase)당 하나의 Lane을 처리할 수 있고 이런식으로 우선순위가 높은 Lane부터 하나씩 처리하게 된다.

# 09. 05.

# SSL 동작 과정

비대칭키는 두가지 역할을 할 수 있다. 암호화, 서명

  • 암호화
    • 암호화, 복호화로 구성
    • 원문값을 볼 수 없음
  • 서명
    • 서명, 검증으로 구성
    • 해당 원문에 서명을 검증하는 것을 목표
    • 원문값을 볼 수 있음
  1. CA에서 서버의 공개키를 CA의 비밀키로 서명 (인증서) → CA의 비밀키로만 검증 가능
  2. 브라우저가 서버에 접속하면 서버에서 인증서 제공.
  3. 브라우저는 전달받은 인증서를 CA의 공개키로 검증.
    • 이 때 대부분의 CA 공개키는 브라우저에 내장되어있음. 없는 경우 네트워크를 통해 공개키 요청
  4. 브라우저에서 서버와 통신하기 위한 대칭키 생성하여 인증서 내의 서버의 공개키로 암호화하여 서버에 전달 → 서버의 비밀키로만 복호화 가능
  5. 서로 공유된 대칭키로 암호화하여 통신

# 09. 06.

# React ErrorBoundary에서 fetch 에러를 catch 하지 못하는 이유

React에서는 class component의 getDerivedStateFromError 메소드로 컴포넌트에서 throw하는 에러를 catch할 수 있다.


class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 다음 렌더링에서 폴백 UI가 보이도록 상태를 업데이트 합니다.
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 에러 리포팅 서비스에 에러를 기록할 수도 있습니다.
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 폴백 UI를 커스텀하여 렌더링할 수 있습니다.
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

하지만 fetch에서 throw 하는 에러는 catch하지 못하는데 그 이유는 바로 실행 컨텍스트가 다르기 때문이다. React는 동일한 실행컨텍스트 내에서 발생하는 에러만 핸들링 할 수 있다. fetch가 실행되는 시점은 V8 엔진의 콜스택이지만, 비동기적으로 Web API에서 별도의 실행 컨텍스트에서 실행되고, 이 Web API는 C++ 등의 네이티브 언어로 개발 되었기에 여기서 말하는 실행 컨텍스트는 C++의 실행컨텍스트이다. 즉, 브라우저에 console에 출력되는 fetch 에러 메세지도 C++에서 출력된 것 이므로 V8 엔진 실행컨텍스트에서 C++ 실행컨텍스트에서 발생한 에러를 catch 할 방법은 없기에 ErrorBoundary가 catch 할 수 없다.

이를 해결하기 위해선 비동기 작업이 끝난 이후 응답에 따라 Javascript 실행 컨텍스트 내에서 throw 해주면 ErrorBoundary가 catch 할 수 있다.

# 09. 09.

# React Dependency Array에 일반 값이 들어오면?

아래의 컴포넌트의 부모 요소가 리렌더링 될 때 TestComponent는 재 호출 된다. 이 때 a라는 개체는 새로 생성되고 새로운 참조값을 갖게 되면서 useEffect가 실행된다.


import { useEffect } from "react"

const TestComponent = () => {

    const a = []

    useEffect(()=>{
        console.log("리렌더링")
    }, [a])

    return <h1>HI</h1>
}

export default TestComponent

반대로 a 값이 원시값인 경우 React의 부모가 리렌더링 되더라도 Dependency Array에 있는 a 값은 값으로 비교하기 때문에 부모 컴포넌트가 리렌더링 되더라도 useEffect에 callBack 함수가 실행되지 않는다.


import { useEffect } from "react"

const TestComponent = () => {

    const a = 1

    useEffect(()=>{
        console.log("리렌더링")
    }, [a])

    return <h1>HI</h1>
}

export default TestComponent

즉, useEffect에 Dependency Array는 얕은 비교를 통해 동작하며, 이는 꼭 React에서 관리하는 상태값이 아니어도 된다.

# 09. 10.

# React Lane vs React Flag

React에서 변화를 감지할 때 RootFiber의 ChildLane를 기준으로 판단한다. 그리고 해당 Fiber에서 어떤 변화가 있었는지를 Flag를 기준으로 판단한다.

Flag의 종류는 다음과 같다.

  • Placement: 요소가 DOM에 추가되거나 이동해야 함
  • Update: 요소의 속성이나 스타일이 변경되었음
  • Deletion: 요소가 DOM에서 제거되어야 함
  • EffectMask: 생명주기 메서드나 훅이 호출되어야 함

따라서, lanes는 '어떤' 컴포넌트가 변경되었는지를 감지하는 데 사용되고, flags는 '어떻게' 그 컴포넌트를 업데이트해야 하는지를 결정하는 데 사용된다.

# 09. 12.

# Let's Encrypt는 뭘까?

SSL을 발급받기 위해선 CA 서버가 필요하다.

Let's Encrypt는 무료 개방형 인증 기관 (CA)이다. 즉 TLS/SSL 인증서를 무료로 제공한다.

기본 만료 기간은 90일이며 계속 갱신할 수 있다.

# 09. 13.

# Nginx에서 SSL 적용하기 Feat. Let's Encrypt

Nginx의 다양한 기능 중에 SSL 보안 기능이 있는데 발급 받은 인증서 경로만 설정해주면 알아서 적용시켜준다.

python3-certbot-nginx 플러그인을 사용하면 자동으로 적용해준다.

# API 서버 SSL로 연결하기 Feat. Nginx

Nginx에는 Proxy 기능이 있다.

브라우저와 서버 (Nginx) 간의 연결을 SSL로 통신하고 Nginx에서 내부 서버(localhost:3000)로 Proxy 연결을 해주면 된다.

그림 1. 연결 과정 다이어그램.

# 09. 19.

# JS 실행 컨텍스트 구조

JS에는 함수를 호출하면 실행컨텍스트가 생성되어 콜스택에 추가된다. 실행컨텍스트는 두 가지 단계로 생성된다.

# 생성 단계

  • 렉시컬 환경 생성
    • 환경 레코드 생성: 변수와 함수 선언을 위한 메모리 공간 할당
    • 외부 렉시컬 환경에 대한 참조 설정

# 실행 단계

  • 코드 실행 (변수 할당, 함수 호출 등)
  • 필요에 따라 값 갱신

이 후 콜스택에 추가된다.