본문 바로가기
Front-End Study/모던 리액트 딥다이브 스터디

모던 리액트 딥다이브 - 2회차 [1-1, 1-2]

by 코딩기 2024. 6. 1.
728x90

1.1 자바스크립트 동등 비교


🔖 리액트 가상DOM과 실제DOM의 비교, 컴포넌트 렌더링 여부, 변수나 함수의 메모이제이션은 모두 동등 비교를 기반으로 한다.

1.1.1 자바스크립트의 데이터 타입


🔖 원시타입 : 간단하게 객체가 아닌 모든 타입을 지칭한다고 해도 무방

  • undefined : 선언 후 값을 할당하지 않을 시 자동으로 할당되는 값, undefined 하나만 할당 가능
  • null : 값이 없거나 빈 값을 표현할 때 사용, null 하나만 할당 가능
  • Boolean : true, false만 가질 수 있는 값, 주로 조건문에서 많이 사용(true나 false는 여러가지 값들로 암시적으로 표현 가능)
  • Number : 숫자를 할당하는 값, 무조건 10진수로 해석되어 표현
  • BigInt : ES2020에서 추가된 값, Number가 표현하지 못하는 큰 값들을 표현
  • String : 텍스트 타입 데이터를 저장하기 위한 값, 작은(''), 큰 따옴표("") 혹은 백틱(``)으로 표현된 모든 값(문자열 각각의 값은 변경 불가능)
  • Symbol : ES6 이후로 추가된 값, 중복되지 않는 고유한 값을 직접 작성하고 싶을 때 사용

🔖 객체타입 : 원시 타입을 제외한 모든 것, 배열, 함수, 정규식, 클래스 등(참조 타입이라고도 지칭)

1.1.2 값을 저장하는 방식의 차이


🔖 원시타입

  • 불변 형태의 값으로 저장되며 메모리 영역 차지
  • 값 자체를 복사하기 때문에, 비교하면 true 출력
const hello = "hello";
const hi = hello;

console.log(hello === hi); // true

🔖 객체타입

  • 변경 가능한 상태의 값으로 저장
  • 얕은 복사를 이용한 참조 전달이므로, 비교하면 false 출력
const hello = {
  a: "hello",
};
const hi = {
  a: "hello",
};

console.log(hello === hi); // false

1.1.3자바스크립트의 또 다른 비교 공식, Object.is


🔖 두개의 인수를 제공받아 동일한지 체크(타입까지 체크)


사용 이유 : ==, ===가 체크하지 못하는 경우도 전부 체크하기 위함


동등 비교가 가지는 한계를 극복하기 위해 제작, 하지만 객체 간 비교에선 ===와 차이X

1.1.4리액트에서의 동등 비교


  • 리액트에서 사용하는 비교가 바로 Object.is
  • 이를 기반으로 하는 shallowEquall이라는 함수를 만들어 사용
  • 기존 JS와 다르게 객체와의 얕은 비교를 한번더 수행
// 기존 Object.is
Object.is({ hello: "world" }, { hello: "world" }); // false

// shallowEquall : 객체의 비교를 한번 더 수행
shallowEqual({ hello: "world" }, { hello: "world" }); // true

// 객체의 깊이가 1 초과인 경우: 비교 불가
shallowEqual({ hello: { hi: "world" } }, { hello: { hi: "world" } }); // false

🔖 왜 얕은 비교를 한번만 수행하는가?

  • 리액트가 사용하는 JSX Props는 객체고, 이 Prop은 한번만 수행되기 때문에 상관X

🔖 props의 비교 예시 : React.memo (어렵다...)

  • 리액트의 memo는 props의 변경 여부만 확인하여 동일할 시 재사용하는 로직
  • 아래 로직에서 count나 text 둘 중 하나만 변경될 시, 그에 맞는 컴포넌트만 리렌더링
import { useEffect, useState } from "react";

const CounterB = React.memo(({ object }) => {
  useEffect(() => {
    console.log(`CounterB Update = count: ${object.count}`);
  });

  return <div>{object.count}</div>;
});

const OptimizeTest = () => {
  const [object, setObject] = useState({ count: 1 });

  return (
    <div>
      <h2>Counter B</h2>
      <CounterB object={object} />
      <button onClick={() => setObject({ count: object.count })}>B Button</button>
    </div>
  );
};

❗여기서 문제! memo는 props를 얕은 비교를 이용하여 비교하므로, props로 object가 들어오는 경우 리렌더링 시 새로운 주소를 가지게 된다.


😢 따라서 memo가 의미가 없어져 props 값이 바뀌지 않아도 리렌더링이 발생한다.


✅ 이를 위해 깊은 비교를 해주는 함수를 지정하여 따로 두번째 인수로 넣어주면 해결~

1.1.5 정리 + 주관적인 정리


🔖 책 정리

  • 객체 비교의 불완전성은 JS의 고유 개념이므로, JS 개발자는 이를 충분히 인지하여야 한다. 또 이 원리를 이용한 의존성 배열 비교useMemo, Callback의 필요성, 렌더링 최적화를 위한 React.memo 등을 위해 이 개념을 잘 숙지해야한다.

🏷️ 주관적 정리

  • 자바스크립트에 존재하는 데이터타입과 그 비교의 원리를 되짚어보고, 리액트에서 이를 어떻게 활용하는지 알 수 있었다.
  • 기존에 관심을 가지고있던 최적화를 위해 꼭 필요한 중요한 개념임을 인지할 수 있었다.

1.2 함수


🔖 JS와 리액트의 중요한 개념 중 하나. 함수형 컴포넌트를 작성 시 화살표 함수일반 함수의 차이도 알아보자

1.2.1 함수란 무엇인가?


🔖 작업을 수행하거나 값을 계산하는 과정을 하나의 블록으로 감싸 표현

function sum(a, b) {
  // 함수의 시작
  return a + b; // a와 b는 매개변수, return으로 결과 반환
} // 함수의 끝

sum(10, 24); // 34

🔖 리액트의 컴포넌트도 이와 같은 개념

function Compo(props) {
  // 함수의 시작
  return <div>{props.hello}</div>; // props는 매개변수, return으로 결과 반환
} // 함수의 끝

1.2.2 함수를 정의하는 4가지 방법


🔖 함수 선언문

  • 가장 일반적인 방식(표현식이 아닌 '문')
function add(a, b) {
  return a + b;
}

sum(10, 24);
  • ❗그러나 함수 선언문임에도 불구하고 변수에 할당 가능하며, 이는 JS엔진이 함수 선언문을 유도리있게 해석하기 때문에 가능한 일


🔖 함수 표현식

  • 자바스크립트에서 함수는 일급 객체(일반적으로 적용 가능한 연산을 모두 지원)이므로, 변수에 할당 하는 등의 표현식으로 작성하는 것이 가능
const sum = function (a, b) {
  return a + b;
};

sum(10, 24);
  • ❗일반적으로 익명 함수로 작성(혼란 방지)

🏷️ 표현식과 선언식의 차이

  • 가장 큰 차이는 호이스팅 (함수를 끌어올려 실행하는 것처럼 작동)
    • 선언식은 함수 그 자체이기 때문에, 호이스팅에 의해 가장 먼저 실행되는 것과 동일한 효과(선언 위치의 자유로움)
    • 표현식은 변수에 할당하기 때문에, var에 경우 런타임 이전에 초기화(명확한 선언)

🔖 Function 생성자

  • 별로 사용하지도 않고 이쁘지도 않음
const add = new Function("a", "b", "return a+b");

add(10, 24);

🔖 화살표 함수
- ES6에서 새롭게 추가된 방식(간편)

❗기존 함수 생성 방식과의 차이

  1. constructor(생성자)를 사용불가
  2. argument 존재X
  3. this 바인딩 객체가 상위 스코프의 this 객체를 가리킴
// 전역 스코프
const arrowFunc = () => {
  console.log(this); // window
};

1.2.3 다양한 함수 살펴보기


🔖 즉시 실행 함수

  • 함수의 정의와 실행이 동시에 이뤄지는 함수(일회용)
(function (a, b) {
  return a + b;
});

🔖 고차 함수

  • 일급 객체라는 특성을 활용해 함수를 인수로 받거나 이를 이용해 새 결과를 도출하는 함수
// 대표적 고차 함수 map
const doubleArr = [1, 2, 3].map((item) => item * 2);

console.log(doubleArr); // [2, 4, 6]
  • ❗이를 이용해 다른 컴포넌트를 인수로 받는 고차 컴포넌트 제작 가능

1.2.4 함수를 만들 때 주의사항


🔖 함수의 부수효과를 억제하라~

  • 함수 내 작동으로 인해 외부에 영향을 끼치는 효과를 지칭
  • 항상 기본에 충실한 함수 만들기

🔖 가능한 함수를 작게 만들어라~

  • 함수는 길어질 수록 에러 발생 확률이 증가하고 가독성이 감소
  • ESLint는 함수가 50줄이 넘어가면 과도한 함수로 판단

🔖 누구나 이해할 수 있는 이름을 붙여라~

  • 코드가 길어지고 많아질 수록 함수 이름이 명확하지 않으면 한눈에 파악하기 힘듦
  • 항상 간결하고 명확한 함수 네이밍을 할 수 있도록 주의

1.2.5 정리 + 주관적인 정리


🔖 책 정리

  • 함수를 위해 알아두어야 할 것과 예외적인 것들에 대한 것들을 항상 숙지해야 하며, 자신의 함수 설계를 되돌아보는 습관을 가지는 것이 좋다.

🏷️ 주관적 정리

  • 기존에 모르던 함수 생성자에 대해 알 수 있었고, 표현식과 선언식의 명확한 차이를 확실히 알 수 있었다.
  • 내가 지금까지 작성했던 함수 로직에 대해 조금 생각해보는 시간이 되었다.