[우아한 테크러닝] 3일차) React

2020. 9. 10. 01:12React/우아한 테크러닝

우아한 테크러닝 3일차


리액트를 직접 만들어보았다.

 

React


React Concept

const list = [
  { title: '안녕하세여' },
  { title: '안녕히가세여' },
  { title: '안녕히주무세요' },
];

const rootElement = document.getElementById('root');

function app(items) {
  rootElement.innerHTML = `
    <ul>
      ${items.map((item) => `<li>${item.title}</li>`).join('')}
    </ul>
  `;
}

app(list);

 

위의 코드와 같이 real dom으로 직접 조작하는건 안정성이 떨어짐 -> API가 low level -> 추상도가 높지 않음
그래서 작성하다보면 복잡도가 올라간다
two-way binding(Angular) 을 사용해도 복잡도가 올라갑니다
그래서 나온게 react

 

concept

다루기가 까다로운 Real DOM에는 직접 JS로 접근하기 어려우니까 접근 쉬운 VDOM을 만든다
VDOM 입장에서는 DOM / JS 모두를 알고 있고, DOM 쪽의 복잡한 상태와 JS 쪽의 쉬운 상태 사이의 일관성을 유지하게 해 줌
아예 다루기 쉬운 구조를 만들면 사용도 쉽고 / 복잡한 구조와 연결하는 쪽도 쉬운 상태가 된다
브라우저 -> 문자열은 다루기 까다롭기 때문에 다루기 쉬운 애 (DOM Tree)를 만들어서 다루게 됨
쉽다 -> 어떻게 쉽게 만들 것인가?
마크업하듯이 편하게 코딩할 수 있는 JSX를 만들었다

 

Virtual DOM

VDOM의 구조

const vdom = {
  type: 'ul',
  props: {
    item: 'abcd',
    id: 'hoho',
  },
  children: [
    { type: 'li', props: { className: 'item' }, children: 'React' },
    { type: 'li', props: { className: 'item' }, children: 'Redux' },
    { type: 'li', props: { className: 'item' }, children: 'TS' },
    { type: 'li', props: { className: 'item' }, children: 'mobx' },
  ],
};

VDOM은 위와 같이 객체 형태로 되어 있기 때문에, 최상위 부모 컴포넌트가 단 하나만 있을 수밖에 없는 구조이다.
최상위 요소가 배열로 감싸여 있다면 오류 없이 잘 돌아가는 것도 같은 원리이다.
리액트 코드를 Babel로 트랜스파일 했을 때 볼 수 있는 React.createElement가 이 버츄얼돔을 만들어 준다

 

 

VDOM 구현하기

/* @jsx createElement */
// 함수인 것들(사용자가 만든 컴포넌트)을 createElement로 호출하게 해줌
// babel 기능 -> 컴파일타임에 실행 (리액트 플러그인 문서 참고)

// VDOM 만들기
function createElement(type, props = {}, ...children) {
  if (typeof type === 'function') {
    // 만든 컴포일 경우는 string이 아니라 function으로 트랜스파일됨
    return type.apply(null, [props, ...children]);
  }
  return { type, props, children };
}

// 엘리먼트를 만든 후 재귀적으로 DOM tree를 구성한다.
function renderElement(node) {
  // 리턴은 real dom
  if (typeof node === 'string') {
    return document.createTextNode(node);
  }

  const el = document.createElement(node.type);

  // map의 첫번째 인자로 자기 자신을 호출하는 함수를 넣어 재귀를 돌며
  // 각각의 children 엘리먼트를 해당 엘리먼트에 append
  node.children.map(renderElement).forEach((element) => {
    el.appendChild(element);
  });

  return el;
}

// container: document.getElementById("root")
// root 노드에 만들어진 DOM을 append
function render(vdom, container) {
  container.appendChild(renderElement(vdom));
}

function StudyList(props) {
  return (
    <ul>
      <li className='item'>{props.item}</li>
      <li className='item'>Redux</li>
      <li className='item'>TS</li>
      <li className='item'>mobx</li>
    </ul>
  );
}

function App() {
  return (
    <div>
      <h1>Hello?</h1>
      <StudyList item='abcd' id='hoho' />
    </div>
  );
}

render(<App />, document.getElementById('root'));
console.log(<App />);

 

VDOM을 만들 때 App 등이 트랜스파일 되어 처리된 결과는 type이 function이 된다.
대문자로 시작된 애는 트랜스파일러가 함수 자체로 넣어주고, 소문자로 시작되는 애는 문자열로 넣어준다

리액트는 위와 같이 일반 마크업 문법같이 사용할 수 있도록 플러그인만 감싸준 것
컴포넌트 이름으로 더 부가효과 창출 가능 (직관적, 명시적)

 

class component

class Hello extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      count: 1,
    };
  }

  componentDidMount() {
    // setState가 호출되었을 때 리액트가 render를 호출할 시점을 결정
    // proxy를 이용하면 자동으로 변경이 감지
    this.setState({ count: this.state.count + 1 });
  }

  // 반드시 구현되어야 하는 함수
  render() {
    return <p>안녕하세요</p>;
  }
}

// 델리게이터 구조
// 1.
const hello = new Hello();

// 2.
vdom = hello.render();

// 3.
if (hello.hasOwnProperty('componentDidMount')) {
  // 적절한 타이밍에 호출해야 함 -> 그 타이밍이 라이프사이클
  hello.componentDidMount();
}

 

 

hook

// Hook => 함수형 컴포넌트에서 state를 가질 수 있는 spec

function App() {
  const [counter, setCounter] = useState(1);

  return (
    <div>
      <h1 onClick={() => setCounter(counter + 1)}>상태 {counter}</h1>
      <Hello />
    </div>
  );
}

 

위의 코드에서 setCounter가 호출되면 상태가 바뀐 것을 감지하는데, 다시 호출되었을 때 이전값을 어떻게 기억하는지? (상태로서 작용할 수 있는 상황)

 

리액트가 컴포넌트 만들때마다 createElement가 호출된다
node가 함수면 호출해서 나오는 애들을 기준으로 또 vdom 구성
App이 함수라는 것을 이미 알고 있고, hook이 호출되는 것도 알고 있음
그러면 App 컴포와 연결되는 hook임을 알게 되고,
초기값, 기존값 등을 컴포넌트를 키로 해서 컴포가 생성된 순서대로 전역 배열에 넣어 둔다
그래서 다음번 호출일 때 (배열에 들어있을 때) 초기값 호출하는 것을 무시하고 기존 값을 표시
그래서 어느 컴포에서 hook이 불렸는지 알 수 있음 -> useState, useEffect… 등 라이프사이클도 흉내낼 수 있다

 

hook을 사용할 때 메커니즘상 사용하면 안되는 방식들은 이런 인덱스 값이 어그러지기 때문