[모던 리액트] 1장 리액트 개발을 위해 꼭 알아야할 자바스크립트

Minhyuk Song·2024년 12월 10일
0

모던 리액트

목록 보기
1/3

서론

왜 React를 사용해야 할까?

  • 명시적인 상태 변경: 컴포넌트 상태 변경이 뷰를 변경하는 단방향 바인딩을 사용해 데이터 흐름을 파악하기 쉽다.
  • JSX: JavaScript 문법을 확장한 JSX를 사용해 자바스크립트 문법 친화적
  • 강력한 커뮤니티: 메타를 기반으로 한 꾸준한 성장과 자유도로 인한 커다란 커뮤니티

🙋 내가 기존에 생각하던 내용
1. 컴포넌트 기반 코드 >> 재사용성, 책임 분리
2. 화면 업데이트를 효율적으로 함 (Virtual DOM)
3. 선언적이므로 화면 업데이트를 쉽게 할 수 있다

React의 역사

  • 당시 주류였던 양방향 바인딩 구조코드 작성은 간단하지만 변경된 DOM과 변경 이유를 추적하는 것이 어려웠다.

  • 그래서 모델이 뷰를 변경하는 단방향 방식으로, 이전 DOM을 버리고 새로운 데이터에 따라 새로 렌더링하는 방식을 채택.

  • ⭐️ 선언적 인터페이스를 통해 간단하게 작성하고, 필요하지 않은 영역에 대한 DOM 변경을 하지 않아 효율적

  • ⭐️ 리액트는 시간의 흐름에 따라 변경되는 데이터를 효율적으로 나타내기 위한 재사용 가능한 컴포넌트를 만드는 데 중점을 둔다.

React를 곁들일 수 있는 라이브러리

  • 상태관리: Redux, Zustand, Recoil, Jotai
  • 서버 사이드 렌더링: Next.js, Remix, Hydrogen
  • 애니메이션: Framer Motion, react-spring, React Move
  • 차트: Recharts, visx, nivo
  • 폼: React Hook Form, Formik, React Final Form

본문

1.1 자바스크립트의 동등 비교

  • 리액트에서 의존성 배열 변경 판단, 렌더링 여부 판단, 메모이제이션 등은 모두 자바스크립트의 동등 비교 기반

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

  • 원시 타입: 객체가 아닌 다른 모든 타입
  • 객체 타입
    • object
      • 배열, 함수, 정규식, 클래스 등이 포함

Truthy와 Falsy

  • Falsy: 조건문에서 false로 취급
    • false, 0, -0, NaN, “”, null, undefined
  • Truthy: 조건문에서 true로 취급 - falsy 값 이외 모든 값
    • 객체와 배열은 항상 truthy

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

객체 간 비교 시 내부의 값이 같더라도 true가 아닐 수 있다는 점을 인지해야 한다.
이는 메모리에 있는 주소 값이 다르기 때문이다.

  • 원시 타입

    • 불변값으로, 변수 할당 시점에 메모리 영역에 저장
    • 값을 비교
  • 참조 타입

    • 가변값으로 참조를 저장
    • 참조를 비교

🙋 그래서 참조 타입을 제대로 비교할 방법이 필요하다.

1.1.3 Object.is

  • ES6에서 새롭게 도입된 비교 문법

  • ==와 동등 비교 ===가 만족하지 못하는 특이 케이스까지 고려하여 비교

    • Object.is와 ==

      5 == "5"; // true
      Object.is(5, "5"); // false
    • Object.is와 ===

      -0 === +0; // true
      Object.is(-0, +0); // false
      
      Number.NaN === NaN; // false
      Object.is(Number.NaN, NaN); // true
      
      NaN === 0 / 0; // false
      Object.is(NaN, 0 / 0); // true

1.1.4 리액트에서의 동등 비교

아직도 헷갈리는 내용 ❓

  • shallowEqual 함수: Object.is로 먼저 비교하고, 객체 간 얕은 비교를 한 번 더 수행

    • 코드 보기

      // React의 shallowEqual.js
      function shallowEqual(objA: mixed, objB: mixed): boolean {
        // 객체 참조가 같은 경우 동일한 것으로 판단
        if (is(objA, objB)) {
          return true;
        }
      
        // 객체가 아니거나 null인 경우 비교하지 않도록
        if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
          return false;
        }
      
        // A의 키 값과 B의 키 값을 비교
        const keysA = Object.keys(objA);
        const keysB = Object.keys(objB);
      
        if (keysA.length !== keysB.length) {
          return false;
        }
      
        for (let i = 0; i < keysA.length; i++) {
          const currentKey = keysA[i];
          if (
            !hasOwnProperty.call(objB, currentKey) ||
            // $FlowFixMe[incompatible-use] lost refinement of `objB`
            !is(objA[currentKey], objB[currentKey])
          ) {
            return false;
          }
        }
      
        return true;
      }
  • 객체의 첫 번째 깊이까지만 비교하는 이유는 객체 안에 객체가 몇 개까지 있을지 알 수 없으므로 성능 상 문제를 고려해야하며, 일반적으로 객체인 JSX props를 비교하기에 충분하기 때문이다.

  • 따라서 props에 객체를 넘겨줄 경우, 리액트 렌더링이 예상치 못하게 동작할 수 있다. 예를 들어, 객체를 props로 넘겨받으면 React.memo는 항상 새로운 props를 받은 것으로 판단하여 메모이제이션된 컴포넌트를 반환하지 못하고, 따라서 리렌더링 된다.

  • 부모 컴포넌트의 상태를 변경하여 리렌더링했을 때 Deeper Component만 리렌더링되고, Component는 정확히 객체 간 비교를 수행해 렌더링을 방지해주었다.

    type Props = {
      counter: number;
    };
    
    // 메모이제이션 O
    const Component = memo((props: Props) => {
      useEffect(() => {
        console.log("Component has been rendered");
      });
    
      return <h1>{props.counter}</h1>;
    });
    
    type DeeperProps = {
      counter: {
        counter: number;
      };
    };
    
    // 메모이제이션 X
    const DeeperComponent = memo((props: DeeperProps) => {
      useEffect(() => {
        console.log("Deeper Component has been rendered");
      });
    
      return <h1>{props.counter.counter}</h1>;
    });
    const App = () => {
      const [, setCounter] = useState(0);
    
      function handleClick() {
        setCounter((prev) => prev + 1);
      }
    
      return (
        <div>
          <Component counter={100} />
          <DeeperComponent counter={{ counter: 100 }} />
          <button type="button" onClick={handleClick}>
            Add
          </button>
        </div>
      );
    };

자바스크립트에서 객체 비교는 불완전하다. 이러한 자바스크립트를 기반으로 리액트는 Object.is 비교 + 객체의 얕은 비교를 수행하여 상태의 변경을 감지한다.

함수형 컴포넌트에서 사용되는 훅의 의존성 배열 비교, 렌더링 방지, useMemo와 useCallback의 필요성, > 렌더링 최적화를 위해 필요한 React.memo를 올바르게 사용하기 위한 것들을 이해할 수 있다.


1.2 함수

1.2.1 함수란 무엇인가?

  • 함수란 작업을 수행하거나 값을 계산하는 등의 과정을 표현하고, 이를 하나의 블록으로 감싼 실행 단위
  • 리액트의 컴포넌트도 일반 함수처럼 props 객체를 매개변수로 받고, return 문으로 JSX를 반환한다. 함수형 컴포넌트는 JSX 형태로 호출한다.

🙋 내가 생각하는 좋은 함수란?
함수는 하나의 역할만을 해야 한다 (단일 책임 원칙)
그래서 인자와 반환값을 잘 고려해야 하고, 무슨 역할을 하는지에 따라 직관적인 네이밍을 하는 게 유지보수성을 높일 수 있다.

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

본인이나 프로젝트의 상황에 맞는 작성법을 일관되게 사용하자.

  1. 함수 선언문

    function add(a, b) {
      return a + b;
    }
    • 어떠한 값도 표현하지 않으므로 일반 문으로 분류

    • 함수 호이스팅 발생해 선언 이전에 호출 가능 → 관리해야하는 스코프가 길어질수록 나쁘게 작용

    • 이름이 있는 함수 리터럴의 경우, 코드 문맥에 따라 선언문 또는 표현식으로 해석될 수 있다.

      const sum = function (a, b) {
        return a + b;
      };
      
      sum(10, 24); // 34
  2. 함수 표현식

    const sum = function (a, b) {
      return a + b;
    };
    
    sum(10, 11); // 21
    • 자바스크립트에서 함수는 일급 객체로, 매개변수, 반환값, 값에 할당할 때 사용할 수 있다.
    • 일반적으로 혼란을 방지하기 위해 함수 이름을 생략
  3. Function 생성자

    const add = new Function("a", "b", "return a + b");
    
    add(10, 11); // 21
    • 권장 X
  4. 화살표 함수

    const add = (a, b) => {
      return a + b;
    };
    
    const add = (a, b) => a + b;
    • ES6에서 새롭게 추가된 함수 생성 방식

    • 다른 함수 생성 방식과의 차이점

      1. 생성자 함수로 사용할 수 없다. constructor를 사용할 수 없기 때문이다.

        const Car = (name) => {
          this.name = name;
        };
        
        const myCar = new Car("carr"); // TypeError: Car is no ta constructor
      2. arguments가 존재하지 않는다.

        function hello() {
          console.log(arguments);
        }
        
        hello(1, 2); // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
        
        const hi = () => {
          console.log(arguments);
        };
        
        hi(1, 2); // Uncaught ReferenceError: arguments is not defined
      3. ⭐️ this 바인딩이 존재하지 않아 상위 스코프의 this를 따른다.

      즉, 화살표 함수의 this는 상위 스코프의 this를 상속받아 혼란을 줄일 수 있다.

      • 클래스형 컴포넌트에서 이벤트에 바인딩할 메서드 선언 시 차이가 있다.

        • 일반 함수에서는 undefined를, 화살표 함수에서는 클래스의 인스턴스를 가리킨다.
        • 화살표 함수를 통해 클래스형 컴포넌트의 메서드에서 클래스 인스턴스인 this에 접근할 수 있다.
        import React from "react";
        
        class Component extends React.Component {
          constructor(props) {
            super(props);
            this.state = {
              counter: 1,
            };
          }
        
          ArrayFunctionCountUp = () => {
            console.log(this); // undefined
            this.setState((prev) => ({ counter: prev.counter + 1 }));
          };
        
          functionCountUp() {
            console.log(this); // Class Component
            this.setState((prev) => ({ counter: prev.counter + 1 }));
          }
        
          render() {
            return (
              <div>
                <p>{this.state.counter}</p>
                <button type="button" onClick={this.functionCountUp}>
                  일반 함수
                </button>
                <button type="button" onClick={this.ArrayFunctionCountUp}>
                  화살표 함수
                </button>
              </div>
            );
          }
        }
        
        export default Component;

🙋 함수 표현식과 함수 선언식의 차이

  • 호이스팅 여부
  • 함수 선언문: 함수 호이스팅 발생
    • 함수 실행 전에 메모리에 등록해두어 선언 이전에 실행 가능
  • 함수 표현식: 변수 호이스팅 발생
    • 변수의 종류에 따른 반환값이 다르다
      (var - undefined / let, const - Reference Error)

this

  • 자신이 속한 객체나 자신이 생성할 인스턴스를 가리키는 값
  • 함수가 어떻게 호출되느냐에 따라 동적으로 결정

1.2.3. 다양한 함수 살펴보기

  • 즉시 실행 함수(Immediately Invoked Function Expression, IIFE)

    (function (a, b) {
      return a + b;
    })(10, 14);
    
    ((a, b) => {
      return a + b;
    })(10, 14);
    • 함수를 정의하고 즉시 실행되는 함수로, 재사용할 수 없다.

    • 글로벌 스코프를 오염시키지 않는 독립적 함수 스코프 생성하는 장점이 있다.

    • 다시 호출되지 않는 것이 확실하기 때문에 리팩터링에도 도움이 된다.

  • 고차 함수(Higher Order Function)

    // 함수를 매개변수로 받는 고차 함수
    const doubledArray = [1, 2, 3].map((item) => item * 2);
    
    console.log(doubledArray); // [2, 4, 6]
    
    // 함수를 반환하는 고차 함수
    const add = function (a) {
      return function (b) {
        return a + b;
      };
    };
    
    add(1)(3); // 4
    • 인수로 함수를 받거나 반환값이 함수인 경우

고차 컴포넌트(Higher Order Component, HOC)

고차 컴포넌트는 고차 함수처럼 컴포넌트를 인수로 받아 다른 컴포넌트를 반환한다. 이를 통해 컴포넌트 내부에서 공통으로 관리되는 로직을 분리해 관리할 수 있어 효율적으로 리팩터링할 수 있다.

사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?

// 사용자 정의 훅
function HookComponent() {
  const { loggedIn } = useLogin();

  useEffect(() => {
    if (!loggedIn) {
      // do something...
    }
  }, [loggedIn]);
}

// 고차 컴포넌트
const HOCComponent = withLoginComponent(() => {
  // do something...
});
  • 두 가지 모두 중복된 로직을 분리해 별도로 관리할 수 있다. 이를 통해 컴포넌트의 크기를 줄이고 가독성을 향상시키는 데 도움을 줄 수 있다.
  • 사용자 훅이 필요한 경우
    • 컴포넌트 전반에 걸쳐 동일한 로직으로 값을 제공하거나 리액트 훅을 작동시키기 위해 사용
    • 컴포넌트 내부에 미치는 영향을 최소화해 개발자가 훅을 원하는 방향으로만 사용할 수 있다는 장점
    • ex) uselogin 커스텀 훅은 loggedIn에 대한 값만 제공하고, 이에 대한 처리는 컴포넌트를 사용하는 쪽에서 원하는 대로 사용. 부수 효과가 비교적 제한적이다.
  • 고차 컴포넌트를 사용해야 하는 경우
    • 함수형 컴포넌트의 반환값, 즉 렌더링에도 영향을 미치는 공통 로직인 경우
    • 고차 컴포넌트가 어떤 일을 하는지, 어떤 렌더링 결과물을 반환하는지는 직접 봐야 알 수 있기 때문에 예측하기 어렵다는 단점
    • ex) 로그인되지 않은 사용자가 컴포넌트에 접근하려 . 할때 로그인을 요구하는 공통 컴포넌트를 노출, 특정 에러가 발생했을 때 현재 컴포넌트 대신 에러 발생을 알리는 컴포넌트를 노출(ErrorBoundary)

함수를 만들 때 주의 사항 (꿀팁)

  • 함수의 부수 효과를 최대한 억제하라.

    • 순수 함수: 부수 효과가 없는 함수. 동일한 인수를 받으면 동일한 결과를 반환하여 예측 가능하며 안정적이다.

    • 웹 애플리케이션을 만드는 과정에서 외부에 영향을 미치는 부수 효과는 피할 수 없지만, 최소화하도록 함수를 작성해야 한다.

    • 리액트 관점에서는 useEffect의 작동을 최소화하여 컴포넌트의 안정성을 높이도록 해야 한다.

    • 결론적으로, 함수에서 가능한 부수 효과를 최소화하고, 함수의 실행과 결과를 최대한 예측 가능하도록 설계해야 한다. 이는 유지보수에 도움이 된다.

  • 가능한 한 함수를 작게 만들어라.

    • 하나의 함수에서 너무 많은 일을 하지 마라.

    • ESLint의 max-line-per-function 규칙은 50줄이 넘어가면 과도하게 큰 함수로 분류한다.

  • 누구나 이해할 수 있는 이름을 붙여라.

    • 가능한 한 함수 이름은 간결하고 이해하기 쉽게 붙이는 것이 좋다.

    • useEffect나 useCallback 등의 훅에 넘겨주는 콜백 함수에 이름을 붙여주면 가독성에 도움이 된다.

      useEffect(function apiRequest() {
        // do something..
      }, []);

1.4 클로저

함수형 컴포넌트에 대한 이해는 클로저에 달려있다.

  • 함수형 컴포넌트의 구조와 작동 방식, 훅의 원리, 의존성 배열 등이 모두 클로저에 의존

1.4.1 클로저의 정의

  • 클로저는 함수함수가 선언된 렉시컬 스코프의 조합

  • 함수형 프로그래밍의 중요한 개념인 ‘부수 효과가 없고 순수해야 한다’는 목적 달성을 위해 사용됨

  • 렉시컬 스코프: 코드가 작성된 순간에 정적으로 결정되는 환경. 함수가 정의된 위치에 따라 함수의 상위 스코프가 결정


1.4.2 변수의 유효 범위, 스코프

  • 스코프: 변수의 유효 범위

    • 전역 스코프

      const global = "global scope";
      
      function hello() {
        console.log(global);
      }
      
      console.log(global); // global scope
      hello(); // global scope
      console.log(global === window.global); // true
      • 전역 객체(브라우저-window, Node.js-global)에 해당 스코프의 변수가 바인딩되어 어디서든 접근 가능
    • 함수 스코프(=지역 스코프)

      const x = 10;
      
      function foo() {
        const x = 100;
        console.log(x); // 100
      
        function bar() {
          const x = 1000;
          console.log(x); // 1000
        }
      
        bar();
      }
      
      console.log(x); // 10
      foo();
      • 자바스크립트는 기본적으로 함수 레벨 스코프를 따른다.
      • 함수에 의해 생성된 스코프

1.4.3 클로저의 활용

function outerFunction() {
  const x = "hello";
  function innerFunction() {
    console.log(x); // hello
  }

  return innerFunction;
}

const innerFunction = outerFunction();
innerFunction();
  • 반환한 함수 innerFunction에는 x가 존재하지 않지만, 해당 함수가 정의된 렉시컬 스코프에는 x가 존재하여 접근할 수 있기 때문에 정상적으로 ‘hello’를 출력할 수 있다.

  • 클로저의 활용

    • 전역 스코프의 사용을 막고, 개발자가 원하는 정보만 노출시킬 수 있다.

      // 클로저 미사용: 전역 스코프
      var counter = 0;
      
      function handleClick() {
        counter += 1;
        return counter;
      }
      
      // 클로저 사용:
      function Counter() {
        let counter = 0;
      
        return {
          increase() {
            counter += 1;
            return counter;
          },
          decrease() {
            counter -= 1;
            return counter;
          },
          counter() {
            console.log("counter에 접근");
            return counter;
          },
        };
      }
      
      const c = new Counter();
      console.log(c.increase()); // 1
      console.log(c.counter()); // counter에 접근 1
      • counter 변수를 직접 노출하지 않아 사용자가 직접 수정할 수 없다.
      • 접근하는 경우를 제한해 부차적 작업을 수행한다.
      • 변수의 업데이트를 increase, decrease를 통해서만 할 수 있다.
  • 리액트에서의 클로저

    • useState는 클로저를 활용해 상태의 최신 값을 기억하고 업데이트한다. 내부 함수 setState는 자신이 선언된 렉시컬 스코프를 기억해 계속해서 state 값에 접근할 수 있다.

1.4.4 주의할 점

클로저를 잘못 사용하면 예상치 못한 결과를 볼 수도 있다.

for (var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, i * 1000);
} // 5 5 5 5 5
  • 전역 변수 i는 for문이 완료된 이후 setTimeout을 실행할 때 이미 5로 업데이트되어 있다.

  • 이를 해결하기 위해서는 블록 단위로 스코프를 만들어줘야 한다.

    1. 블록 레벨 스코프를 갖는 let 사용하기

      for (let i = 0; i < 5; i++) {
        setTimeout(() => {
          console.log(i);
        }, i * 1000);
      } // 0 1 2 3 4
    2. 즉시 실행 함수로 반복문 블록마다 스코프 생성

      for (var i = 0; i < 5; i++) {
        setTimeout(
          (function (sec) {
            return function () {
              console.log(sec);
            };
          })(i),
          i * 1000
        );
      } // 0 1 2 3 4
  • 클로저 사용 시 주의 점은 선언적 환경을 기억하기 위한 비용이 발생한다는 것이다.

    • 불필요하게 메모리를 잡아먹는 결과를 야기할 수 있고, 성능에 악영향을 미칠 수 있다. 따라서 사용에 주의가 필요하다.

1.5 이벤트 루프와 비동기 통신의 이해

비동기 코드 작동 방식을 이해하면 자바스크립트가 작업을 동시에 처리하는 방법, 태스크에 대한 우선순위, 주의할 점을 파악해 더욱 매끄러운 웹 애플리케이션 서비스를 제공할 수 있다.

  • 자바스크립트는 싱글 스레드에서 작동한다. 즉, 한 번에 하나의 작업을 동기 방식으로만 처리할 수 있다.

동기 방식과 비동기 방식

  • 동기 방식은 직렬 방식으로 요청이 시작된 이후 응답을 받아야 다른 작업을 처리한다.
    • 직관적이지만 한 번에 많은 작업을 처리할 수 없다.
  • 비동기 방식은 병렬 방식으로 응답을 받지 않아도 다음 작업을 처리한다.
  • 한 번에 여러 작업이 실행될 수 있다.

1.5.1 싱글 스레드 자바스크립트

  • 프로세스와 스레드

    • 프로세스: 프로그램을 구동해 프로그램의 상태가 메모리상에서 실행되는 작업단위
      • 하나의 프로그램을 실행하면 하나의 프로세스가 할당되어 거기에서 모든 작업이 처리
    • 스레드: 프로세스보다 작은 실행 단위로, 하나의 프로세스에서 여러 개의 스레드를 활용해 동시 다발적 작업 가능
      • 스레드끼리는 메모리를 공유할 수 있어 여러 작업을 동시에 수행 가능

자바스크립트는 왜 싱글 스레드로 설계되었을까?

최초의 자바스크립트는 브라우저에서 HTML을 그리는 제한적 용도였고, 동시에 여러 스레드에서 DOM을 조작할 경우 DOM 표시에 문제가 생길 수 있기 때문.

자바스크립트는 싱글 스레드로 코드의 실행이 하나의 스레드에서 순차적으로 이루어지지만, 자바스크립트 런타임 외부에서 이벤트 루프를 사용해 비동기 코드를 처리할 수 있다.


1.5.2 이벤트 루프란?

  • 호출 스택과 이벤트 루프

    • 호출 스택(=콜스택): 자바스크립트에서 수행해야 할 코드나 함수를 순차적으로 담아두는 스택

    • ⭐️ 이벤트 루프: 이벤트 루프의 단일 스레드에서 콜 스택과 태스크 큐 내부에 대기 중인 함수가 있는지 반복적으로 확인하고, 실행 가능한 오래된 것부터 자바스크립트 엔진을 이용해 실행

      • 동기 함수는 자바스크립트 코드를 동기식으로 실행하는 메인 스레드에서 실행
      • 비동기 함수는 브라우저나 Node.js가 별도의 스레드에서 태스크 큐에 작업을 할당해 실행
    • 태스크 큐: 비동기 함수의 콜백 함수나 이벤트 핸들러 같은 실행해야 할 태스크의 집합(Set)

1.5.3 태스크 큐와 마이크로 태스크 큐

이벤트 루프는 하나의 마이크로 태스트 큐를 가진다.

  • 각 태스크에 들어가는 대표적인 작업은 다음과 같고, 마이크로 태스크 큐는 기존 태스크 큐보다 우선권을 갖는다.

    function foo() {
      console.log("foo");
    }
    
    function bar() {
      console.log("bar");
    }
    
    function baz() {
      console.log("baz");
    }
    
    setTimeout(foo, 0); // 2
    
    Promise.resolve().then(bar).then(baz); // 1
    
    // bar
    // baz
    // foo
    • 태스크 큐: setTimeout, setInterval, setImmediate

    • 마이크로 태스크 큐: process.nextTick, Promises, queueMicroTask, MutationObserver

  • 브라우저 렌더링은 언제 실행될까? 마이크로 태스크 큐와 태스크 큐 사이에서 발생

    • 발생 순서: 동기 → 마이크로 태스크 큐 → 렌더링 → 태스크 큐

    • 동기 코드와 마이크로 태스크가 렌더링에 영향을 미칠 수 있음을 고려해 특정 렌더링이 자바스크립트 내무거운 작업과 연관이 있다면 어떤 식으로 분리해 사용자에게 좋은 경험을 제공해 줄지 고민해 보아야 한다.


1.6 리액트에서 자주 사용하는 자바스크립트 문법

  • 일반적 자바스크립트와 비교해 리액트 코드는 JSX 구문 내부에서 객체를 조작하거나 객체의 얕은 동등 비교 문제를 피하기 위해 객체 분해 할당을 하는 등 독특하다.

  • 사용자의 다양한 브라우저 환경과 최신 문법을 작성하고 싶은 개발자의 요구를 해결하기 위해 바벨을 사용해 자바스크립트의 최신 문법을 다양한 브라우저에서 일관적으로 지원할 수 있도록 트랜스파일할 수 있다.

  • 바벨이 트랜스파일하는 방법과 코드 결과물을 이해하면 애플리케이션을 디버깅하는 데 도움이 된다.

1.6.1 구조 분해 할당

  • 배열 또는 객체의 값을 분해해 개별 변수에 할당하는 것. 배열은 순서대로, 객체는 이름으로 꺼내온다.

  • 배열 구조 분해 할당 (ES2015-ES6)

    // useState
    const [state, setState] = useState();
    • 기본값 할당하면 undefined인 경우에 기본값 사용
    • ,를 통해 인덱스에 대한 할당 생략 가능
    • useState가 객체가 아닌 배열을 반환하는 이유?
      • 사용하는 쪽에서 자유롭게 이름을 선언할 수 있기 때문
  • 객체 구조 분해 할당 (ECMA 2018 - ES9)

    function Componenet({ a = 10, b = 20 }) {
      return a + b;
    }
    • 기본값 설정 및 새로운 이름 할당 가능
    • 리액트 컴포넌트 props에서 값을 바로 꺼내올 때 자주 쓰이는 방식
    • 계산된 속성 이름 방식으로 변수에 있는 값으로도 꺼내올 수 있다.(변수 네이밍 필수)

1.6.2 전개 구문

  • 이터러블(배열, 객체, 문자열)을 전개해 간결하게 사용할 수 있는 구문

  • 배열 전개 구문(ES6): 간편하게 배열 합성 가능

    const arr1 = [1, 2];
    const arr2 = arr1;
    
    console.log(arr1 === arr2); // true
    
    const arr3 = [1, 2];
    const arr4 = [...arr3];
    
    console.log(arr3 === arr4); // false
  • 객체 전개 구문(ECMA 2018): 간편하게 객체 합성 가능

    const obj1 = {
      a: 1,
      b: 2,
    };
    
    const obj2 = {
      c: 3,
      d: 4,
    };
    
    const newObj = { ...obj1, ...obj2 };
    console.log(newObj); // { a: 1, b: 2, c: 3, d: 4 }
    • 전개 구문과 값 할당 순서에 유의해서 사용
    • 객체의 속성값 및 설명자, 심벌 확인 때문에 트랜스파일 된 코드가 커지므로 사용 시 주의가 필요

1.6.3 객체 초기자(ES2015)

  • 객체 선언 시 객체에 넣고자 하는 키와 값을 가지고 있는 변수가 이미 존재한다면, 해당 값을 간결하게 넣어줄 수 있는 방식

    ```jsx
    const a = 1
    const b = 2
    
    const obj = {
    	a,
    	b
    }
    
    // {a: 1, b: 2}
    ```

1.6.4 Array 프로토타입의 메서드: map, filter, reduce, forEach

  • map, filter, reduce는 새로운 배열을 만들어 반환하기 때문에 안전
  • Array.prototype.map
    • 특정 배열을 기반으로 리액트 요소를 반환할 때 많이 사용
  • Array.prototype.filter
    • 기존 배열에 대한 조건을 만족하는 새로운 배열 반환
  • Array.prototype.reduce
    • map과 filter를 각각 활용해 구현하면 가독성이 좋지만 두 번 순환하는 문제가 있으므로 상황에 맞게 선택
  • Array.prototype.forEach
    • 반환값이 undefined로 의미 없음
    • 에러를 던지거나 프로세스를 종료하지 않는 이상 멈출 수 없으므로 최적화할 가능성 검토 필요

🙋 map()과 forEach()의 차이점
둘 다 배열의 요소마다 인자로 받은 콜백함수를 실행하여 얻은 반환값으로 치환한다는 공통점이 있는 반면에 새로운 배열을 반환하는지에 대한 여부가 다르다. map()은 새로운 배열을 반환하고, forEach()는 기존 배열을 변환한다.

1.6.5 삼항 조건 연산자

  • JSX에서 조건부 렌더링을 위해 가장 널리 쓰이는 방법
  • 가독성을 고려해 가급적 중첩해서 쓰지 않는 편을 권장
profile
어제보다 더 나은 오늘을 만들 수 있게

0개의 댓글

관련 채용 정보