우아한 테크러닝 3기 3회차

설영환·2020년 9월 9일
0

우아한 테크러닝

목록 보기
3/3

1. Intro

많은 라이브러리나 프레임워크들이 나오고 있습니다. 이를 사용하면 쉽게 어떠한 것을 구현하기 쉬워집니다. 하지만 개발자들이 단순히 사용자가 되지 않길 바라는 마음에 이번 회차에 리액트를 구현해보고 라이브러리나 프레임워크들의 코드의 내부를 보았으면 좋겠다고 하셨습니다.

상황은 항상 변하고 코드는 지속적으로 유지보수 측면이더라도 변해야됩니다.
그래서 대응을 하는 코드도 꾸준히 작성되어야되는데 내가만든 코드라도 한달 뒤에 보았을 때,
한번에 알수있는 코드구조를 작성해야됩니다.
그래서 어떠한 architecture가 좋은 architecture인가 고민해보면 결국 '같은 것들끼리 묶고 다른것들끼리는 분리해야된다'라는 것이 대원칙입니다.
어떤것이 같은것인지는 사람마다 다르고 지식수준에 따라도 다릅니다.

한번에 알아볼수 있는 구조를 만드는 방법중 가장 쉬운 방법이자 첫번째 방법은
이름을 제대로 잘 짓는것입니다.
아키텍쳐 디자인 패턴은 20~30프로 인데 이를 잘하는 것도 중요하지만 기본을 지키는게 제일 중요합니다.

2. 리액트의 철학

다루기 쉬운 방법을 만들기 위해서 복잡도를 낮추는 것도 하나의 방법입니다.
하지만 리얼돔을 다루면 복잡도가 필연적으로 높아질수밖에 없습니다.
리얼돔을 가지고 MVC패턴도 구현하고 two-way binding 등의 방법을 해도 볼륨이 커지면 복잡도가 커지고 컨트롤도 어려워서 페이스북에서 리액트를 만들었습니다.

다루기 어려운 것이 있을때 레이어로 직접 다루면 레이어도 다루기 어려워집니다.
그래서 어렵지 않은걸로 감싸서 레이어에서 다루면 레이어는 복잡도가 좀 나아지게 됩니다.

그래서 애초에 다루기 쉬운 구조를 만들어서 중간에서 컨버팅 하면 좀 쉬워집니다.
브라우저를 예를 들자면 HTML코드는 기본적으로 문자열입니다.
문자열은 기본적으로 다루기 힘든 구조로 그래서 나온것이 DOM tree 입니다.
유사 객체로써 컨버팅되서 다루기 쉬어졌습니다.

리액트에서는 돔트리 자체도 다루기 힘드니 가상돔 (Virture DOM 이하 vdom)을 만들었습니다.
vdom은 돔에서도 알아보기 쉽고, js에서도 다루기 쉬운 구조로 되어있습니다.
react팀에서는 알아보기 쉽도록 JSX문법까지 만들어서 사용하기 쉽게까지 하였습니다.
(하지만 초창기에는 이상하다고 욕을...)

3. 리액트의 구현

일단 기본리액트 컴포넌트의 구조를 보면

import React from 'react';
import ReactDOM from 'react-dom';

function StudyList(){
    return (
        <ul>
            <li className = "item">React</li>
            <li className = "item">Redux</li>
            <li className = "item">Typescript</li>
            <li className = "item">mobX</li>
        </ul>
    );
}

라는 컴포넌트가 있다고 생각해봅시다.

React.createElement()는 vdom을 만드는 함수인데 인자가 tag의 타입, props(object),children(array) 순서로 들어갑니다.

그래서 vdom을 직접 만들어보면

const vdom = {
    type:"ul",
    props:{},
    children:[
        {type:"li, props:{className:"item}, children:"React"}
        {type:"li, props:{className:"item}, children:"Redux"}
        {type:"li, props:{className:"item}, children:"typescript"}
        {type:"li, props:{className:"item}, children:"mobX"}
     ]
 }

이러한 구조가 될것입니다. 그래서 vdom을 만드는 React.createElement를 간단히 구현해보면

function createElement(type,props={},...children){
    return {type,props,children}
};

간단한 함수 하나로 vdom 생성이 완료되었습니다.

바벨이 되어 있다면 /* @jsx H */를 js code 맨 상단에 올려놓으면 jsx문법을 만나면 H라는 함수로 바뀌게 됩니다.(H 대신 createElement쓰면 그걸로 바뀝니다.)

추가적으로 vdom과 realdom을 비교하여 다른것만 올리는 diff알고리즘도 있지만 여기서는 리액트의 기본만 구현하여 이해하기 위한 것이기때문에 diff 알고리즘은 없습니다.

이를 이용해서 하나씩 바꿔보면

/* @jsx createElement */

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

function StudyList(props) {
    return (
        <ul>
            <Row label="하하하음메" />
            <li className="item">React</li>
            <li className="item">Redux</li>
            <li className="item">Typescript</li>
            <li className="item">mobx</li>
        </ul>
    );
}

function Row(props){
    return <li>{props.label}<li/> ;
}

fuction createElement(type, props={}, ...children){
    if(typeof type === "function"){
        return type.apply(null,[props,children]);
    }  // 강제 함수실행
    return {type, props, children};
}

function renderElement(node){ // Dom에 그리는작업 - 재귀가 될수밖에 없습니다.
    if(typeof node === "string"){ //결국 최하위 children은 string일수밖에 없습니다.
        return document.createTextNode(node);
    }
    const $element = document.createElement(node.type);
    node.children.map(renderElement).forEach((el)=>{
        $element.appendChild(el);
    });
    return $element;
}

function render(vdom, $container){
    $container.appendChild(renderElement(vdom));
}

render(<App/>,document.getElementById("root"));

위의 함수처럼 만들었다면 기본적인 리액트는 완성이 되었습니다.

트랜스파일링 할때 태그 제일 앞의 문자가 대소문자인지에 따라 구분합니다. 대문자가 왔을경우 함수로 인식하고, 아닐경우 태그로 인식되어들어감을 볼수 있습니다.
구분하기 쉽게끔 해놓았습니다.

4. 리액트의 메서드 및 상태

리액트를 구현해 보았을때 변수의 스코프에 따라 지역변수들이 만들어집니다. 함수가 새로 생성되면 지역변수들은 다시생성되며 초기화가 됩니다.
함수형 컴포넌트에서는 호출밖에 받을수 없었습니다. 그래서 새로만드는것이나 업데이트나 다를바가 없었습니다.
그래서 함수형 컴포넌트는 상태를 가질수 없었습니다.

클래스라면 값을 바꾸어도 클래스 내부의 상태를 기반한 값을 가질수 있습니다. 리액트에서 필요할때만 클래스 내부에 저장되어있는 상태를 호출하기만하면 되고 원하는 타이밍에 렌더링도 가능하기 때문에 라이프 사이클 메서드도 존재합니다.

리액트 내부 코드를 간단히 보면

const hello = Hello();
vdom = hello.render();
if(hello.hasOwnProperty("componentDidMount")){
    hello.componentDidMount();
}

이런식으로 원하는 타이밍에 구현이 가능하고 이를 라이프사이클이라고 합니다.

현재는 프록시로 감지가 가능하지만 리액트를 처음만들어질 당시에는 없었을 거라고 합니다.

5. Hook의 기본개념

이번에는 시간이 부족하여 hook의 구현까지는 못했지만 기본개념설명까지는 했습니다.

Hook은 함수형 컴포넌트에서 스펙을 가질수있는 메서드입니다.

여기서 훅중에 가장쉬운 useState를 예시로 들었습니다.

useState는 기본적으로 배열을 리턴합니다

const result = useState(1);
console.log(result);

콘솔창을 열어본다면 배열이 나오고 index 0 의 값은 1이고, index 1은 디스패쳐 함수가 나옵니다.

그렇다면 어떻게 useState 가 이전값을 기억하는가??
기본적으로 심플한방식을 사용합니다.
(놀랍게도 기본개념은 클로저가 아닙니다.)
리액트가 컴포넌트들을 만들때 createElement가 생성됩니다. 그래서 컴포넌트 하나하나 만들때 맨 상단의 node가 함수면 호출되는데 createElement호출 후 use계열의 함수들이 호출된다면 어떠한 컴포넌트에서 훅을 호출했는지 알수있습니다.
그래서 컴포넌트의 생성순서를 배열에 넣어놓았다가 배열의 인덱스를 가지고 있는 디스패쳐 함수에서 그걸 받아서 사용을 합니다.

따라서 훅에서는 하면 안될것들이 몇가지 존재하는데
배열의 순서(index)가 어그러지게끔 하는것들이 리액트 훅에서 하면 안될 것으로 정해진것입니다.
마찬가지로 react컴포넌트 안이 아닌 바깥쪽에서 호출이 된다고 하더라도 index가 없기때문에 내부에서만 호출이 가능합니다.

만약 코드를 까보고 싶다면 초기 버전을 열어보면 컨셉이라던가 확인이 가능합니다.

또한 index를 가지고 있다면 어떤함수에서 어떤 훅이 호출되었는지 알수있기 때문에 라이프사이클도 가능해질수 있었습니다.

이 글을 읽고 이해하려면 vdom과 호출 render를 알고있어야 온전히 이해가 가능합니다. 위에 간단히 적어 놓았지만
더 필요하다면 초기 컨셉글을 읽어보시는걸 추천합니다.

profile
Frontend 를 목표로합니다.

0개의 댓글