리액트 공식 문서 읽기 (v17.0.2)

Nine·2022년 6월 27일
0

React

목록 보기
17/22

로이드 리액트 특강

리액트 공식 문서 읽기 (v17.0.2)

왜 공식문서를 읽을까요?

공식문서가 친절하지 않아요.

어떤 공식문서든 전제가 깔려있어요 → 그 전제가 무엇일까?

난이도가 들쭉날쭉한 공식문서 → 쉽다고 훅 하고 넘어가도 될까?

느껴봅시다.

또한 공식문서는 읽고 적용하고 다시 읽어봐야해요. (한 번에 이해가 될리가 없잖아요!)


0. 시작하기

모든 건 도구 👉 페인모인츠를 해결하기 위한 것.

하지만 보통 how to에 집중을 하죠.

이 도구가 어떤 문제를 해결하기 위해서 (why) 등장했는지 알아야 해요.


1. Hello World

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);
  • 오잉? javascript가 아니네!
  • JSX가 등장했네요.
  • ReactDOM.render 는 virtual DOM을 만들어주는 친구입니다.

2. JSX

Javascript를 확장한 문법인 건 알겠는데…

공식문서가 무슨 말을 하는지 잘 모르겠어요.

딱 봐도 이해를 위한 공식문서라기 보다는 자랑하는 거 같아..

훅훅 읽어 갑시다.


2-1. JSX에 표현식 포함하기

중괄호 안에 javascript가 들어갈 수 있군요.

👉 여기서 왜?

값, 식만 넣을 수 있구나.

문은 안되는구나!

(공식문서를 읽으면서 값식문을 떠올릴 수 있다면 당신이 바로 고수)

JSX는 결국 JS의 React.createElement로 변환되기 때문에 객체의 value값으로 당연히 문을 쓸 수가 없죠! 값과 식만 가능합니다.


2-2. JSX 속성 정의

JSX는 HTML보다는 Javascript에 가깝기 때문에 camelCasae 프로퍼티 명명 규칙을 사용합니다.

👉 여기서 왜? 라는 의문이 들어야합니다.

  • BabelJS을 생각해볼까요?
    • javascript compiler, transfiler
    • 상위 버전의 javascript를 하위 버전의 javascript로 변환해줍니다.
    • JSX를 작성해보니 JS로 변환되는군요. React.createElement

바벨 입장에서 리액트도 사실 많은 플러그인들 중에서 하나일 뿐이라서 이렇게 바꿔줄 수도 있어요.


2-3. JSX는 객체를 표현합니다.

위의 이야기와 같죠. JSX -> JS의 React.createElement 객체로 변환됩니다.

특정 상황에서는 createElement를 직접 작성해야하는 경우도 생겨요.


3. 엘리먼트 렌더링

React 앱의 가장 작은 단위

React Element는 일반 객체입니다. (DOM element와는 다름)

⚠️ 컴포넌트와 엘리먼트는 달라요. 엘리먼트가 컴포넌트의 구성요소죠. 더 작아요.

❗ 처음에는 헷갈린다면 넘어가요. 너무 하나씩 이해를 하기 위해 시간을 소비한다면 좋지 않습니다.

일반적으로 하나의 루트 DOM 노드가 있어요.

Virtual DOM 는 여러 개가 있을 수 있죠. 하지만, 각 Virutal DOM은 데이터를 공유할 수 없어요.

어, Real DOM HTML 앱에서 로그인 부분은 React 하고 싶어! 👉 render로 삽입
어, 이 부분도 React로 하고 싶어!👉 render로 삽입

이렇게 점진적으로 Virtual DOM을 적용할 수는 있지만 여러 개의 Virtual DOM은 데이터를 공유할 수 없어요. (여러 개의 앱 인거죠.)

리액트 입장에서는 Virtual DOM 하나가 하나의 앱입니다.


3-1. 렌더링된 엘리먼트 업데이트하기

React 엘리먼트는 불변객체입니다. (런타임 상황에서!)


✋ 잠깐~
코드는 항상 두 가지 상태가 있어요.
1. 코드 에디터 상의 컴파일 상태

  • 서버에서 컴파일하면 SSR
  • 클라에서하면 CSR
  1. 실행되는 런타임 상태

불변이라니 결국 UI를 업데이트하는 유일한 방법은 새로운 엘리먼트를 생성하고 이를 ReactDOM.render 한테 전달하는 것 뿐이겠네요.

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root'));
}

setInterval(tick, 1000);

tick이 호출될때마다 늘 새로운 element를 전달하게 됩니다. (런타임에서 새롭게 생성)

🤔 흠... 그래도 바뀐 부분만 업데이트되면 좋지 않을까? 낭비같아요. 아래에서 이야기 해보죠.


3-2. 변경된 부분만 업데이트하기

어? 위의 Tick을 해보니 inspector로 관찰하면 전부 새로 만드니 다 바뀌어야할 것 같은데 실제로는 시간부분만 번쩍번쩍 바뀌네요?

😀 Virtual DOM 등장
Javascript DOM selector와 Real DOM 사이에 Virtual DOM이 등장했을까!

👉 이 컨셉은 컴퓨터 세계에서 정말 자주 등장하는 컨셉이예요. 보통 속도 차이가 너무 크거나 다루는 cost가 차이가 너무 큰 경우죠. 예를 들면,

  • 사용자에게 살짝 기다리게 하고 유튜브 동영상과 플레이어 사이에 저장소를 하나 끼워두고 일정 수준 이상 받아지면 그 때 보여주게 하자! (버퍼링 개념)

  • HDD의 정보를 CPU까지 와야하는데 HDD 읽는 건 너무 느려요. 그래서 그 사이에 RAM을 만들었어요.

✋ Virtual DOM도 마찬가지죠.
Real DOM을 Javascript로 핸들링하기 안 좋아요.
Real DOM 입장에서는 이전 상황과 같은지 다른지를 판단할 수가 없어요.
그렇다면 다루기 쉬운 친구(virtual DOM)를 끼워넣고 이전 상황과 비교를 할 수 있다면 좋겠다!


4. Components and Props

개념적으로 컴포넌트는 JavaScript 함수와 유사합니다. “props”라고 하는 임의의 입력을 받은 후, 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환합니다.

4-1. 함수 컴포넌트와 클래스 컴포넌트

// 함수 컴포넌트
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// 클래스 컴포넌트
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

🤔 함수와 클래스의 가장 큰 차이점?

  • 함수는 인스턴스를 만들지 않아도 호출할 수 있지만,
  • 클래스는 new 연산자로 인스턴스를 생성해야만 호출할 수 있죠.

Virtual DOM의 제어권

모두 React에게 있습니다.

🤔 클래스 컴포넌트에서는 이렇게 하면 어떨까?

class Welcome extends React.Component {
  render(props) {
    return <h1>Hello, {props.name}</h1>;
  }
}
  • new로 인스턴스를 생성하지 않아도 this를 쓰지 않기 때문에 Welcome.render()를 사용할 수 있을 것 같은데 말이죠.

위처럼 작성하여 사용자가 임의로 render를 호출하여 DOM을 조작하면 문제가 생길 수 있을 거예요.

😋 안정적인 렌더링을 위해 VirtualDOM를 온전히 React가 담당해야겠죠.

마찬가지로 함수 컴포넌트도 코드 상으로 직접 호출하는 경우는 없죠. (호출해도 아무 일도 일어나지 않아요.)


4-2. 컴포넌트 렌더링

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>; // createElement 1번째 호출
}


const element = <Welcome name="Sara" />; // createElement 2번째 호출

ReactDOM.render(
  element,
  document.getElementById('root')
);

DOM tree처럼 createElement가 nesting 되어 호출되어요.

즉, 최상위 아래에 붙어있는 경우가 되겠네요.


❗ 잠깐~ 컴포넌트 이름은 항상 대문자로 시작합시다.

소문자로하면 바벨은 이게 사용자 정의 컴포넌트인지, html 컴포넌트인지 알 길이 없어요. 그래서 welcome(사용자 정의 컴포넌트)을 그냥 h1, div 태그처럼 일반 문자열로 인식을 해버려요.


createElement의 첫번째 인자가 Welcome 함수 입니다.

createElement는 우리가 호출했어요.

하지만, Welcome 함수는 createElement 안 쪽에서 리액트가 호출한 게 되겠죠.


5. State and Lifecycle

상태는 변화하는 값입니다. 프론트 앱은 결국 상태를 UI로 변경하는 함수라고 볼 수 있어요. 중요합니다.

  • 상태의 Lifecycle
  1. 생성 : 한 번 발생
  2. 변화 : 여러번 발생
  3. 죽음 : 한 번 발생
// Clock 컴포넌트는 날짜 생성 로직이 없어짐으로써 재사용성이 늘어났어요.
function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

👉 setInterval없이 안될까?

함수 -> 클래스로 변경해야합니다.

생성 -> 변화 -> 죽음

state를 메모리에 저장해야하는데 함수는 호출마다 생애주기가 새로 시작되죠.

생애주기를 가질 수 있는 class로 변환합시다. class는 instance를 만들고 제거하기 전까지 변화가 허용되죠.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

🤔 근데 왜 setState로 값을 변경하죠?

초기에는 변수가 값이 할당될 때를 런타임에서 감지할 수가 없었어요. 메서드 호출을 통해서 감지를 한거죠.

지금은 js의 프록시 라는 기능이 있긴한데 여전히 지금 그대로 쓰고 있어요.


5-1. 함수에서 클래스로 변경하기

class 컴포넌트가 초기에 자리 잡은 이유

함수 호출은 React가 합니다. (어떤 트리거인지는 다음에 배울게요.)

class는 무조건 render 메서드가 존재하고 render를 실행하면 함수 컴포넌트처럼 createElement로 객체가 만들어지게 됩니다.

✨ 하지만 다른 점이 있죠. class는 인스턴스가 존재한다면 이전의 인스턴스를 사용하여 render를 호출하겠죠. 그렇다면 기존의 상태를 이용할 수 있어요.

👉 지금은 함수 컴포넌트에서 상태를 가지고 있지만 이 당시에는 함수에 상태를 줄 수 있는 방법을 고안하지 못했어요. 그래서 class 컴포넌트가 주를 이루었던 거죠!

class로 바꾸는 방법

React.Component를 확장하는 동일한 이름의 ES6 class를 생성합니다.
render()라고 불리는 빈 메서드를 추가합니다.
함수의 내용을 render() 메서드 안으로 옮깁니다.
render() 내용 안에 있는 props를 this.props로 변경합니다.
남아있는 빈 함수 선언을 삭제합니다.

함수는 당연히 인자로 props를 받겠죠.
클래스는 당연히 생성자가 받아요.


5-2. class에 로컬 state 추가하기

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  1. 생성은 한 번만 됩니다. 👉 당연히 constructor
  2. 변화 👉 this.state에 저장하게 됩니다.

5-3. 생명주기 메서드를 class에 추가하기

키워드는 리액트와 컴포넌트입니다. 서로의 상황을 서로 알고 싶다!

react <-> component
이 사이의 관계를 봐야해요. 아래 이야기는 이벤트 핸들러와 굉장히 유사해요.

React 관점

😢 class 컴포넌트는 자기 자신의 render 메서드를 호출할 수 없죠. 그러니 virtual DOM이 갱신되지 않고 UI도 갱신되지 않겠죠.

결국 호출은 당연히 react가 해줘야합니다.

🤔 그런데 react는 그 업데이트 시점을 어떻게 알 수 있을까요?

  • class 컴포넌트에게 setState 메서드를 주게 되었습니다.
  • setState 메서드가 호출되면 바뀌었다고 React가 알 수 있게 되었어요.

컴포넌트 관점

마찬가지로 컴포넌트 입장에서도 Virtual DOM에 언제 붙는지 알아야겠죠?

  • Lifecycle 메서드입니다. 아래를 보실까요?
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  // ✨ 이 부분이죠
  componentDidMount() {
    // DOM에 rendering된 후에 실행됩니다.
     this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }
  // ✨ 이 부분이죠
  componentWillUnmount() {
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

🤔 어 근데 Timer는 왜 state로 관리하지 않죠?

 this.timerID = setInterval(
      () => this.tick(),
      1000
    );

애초에 상태가 아니죠? 변하는 값이 아닙니다. 유지해야하는 값이 아닙니다.


5-4. state를 올바르게 사용하기

1. 직접 수정 금지

  • 당연하죠? Virtual DOM을 업데이트하지 못하니까요.

2. 업데이트는 비동기적일 수도 있어요.

  • 리액트 tree 구조에서는 자식이 변경되면 부모도 변경되어야 합니다 👉 이게 너무 많으면.. 힘들어지겠죠?
  • ✨ 1초에 Virtual DOM을 몇 번만 만들자! 👉 버퍼처럼 쌓아놨다가 한 번에 넘겨주는 아이디어가 나왔어요. 👉 이게 비동기가 된 것이죠~

😀 동기적으로 하려면 setState 인자에 함수를 넘겨줍시다.

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

🤔 근데 왜 함수는 동기적으로 동작할까요?

3. State 업데이트는 병합됩니다.


5-5. 데이터는 아래로 흐릅니다.

부모 컴포넌트(A)는 state를 props로 자식 컴포넌트(B)에게 보낼 수 있어요.

⚠️ 하지만 자식(B)은 부모(A)가 누군지, 어떻게 동작하는지 몰라야 재사용이 가능해요.

👉 이런 부분에서 캡슐화 라고 불러요. 실제 js로 컴포넌트가 캡슐화되어 있지는 않지만 구조적으로 캡슐화가 되어 있어요.


6. 이벤트 처리하기

😀 skip~ 까다롭지 않네요.


7. 조건부 렌더링

profile
함께 웃어야 행복한 개발자 장호영입니다😃

0개의 댓글