[React] 리액트를 처음부터 배워보자. — 04. Functional Component와 useState

Stark Studio
Cross-Platform Korea
10 min readJun 18, 2020

--

이 글을 남기는 계기는 Dan Ambramov의 블로그 Overreacted를 보며 React.js에 대해 더 깊이 공부할 필요성을 느꼈기 때문이다.

이번 글은 지난 글을 공부하며 발견한 Functional Component의 코드를 분석하며 React v16.8부터 추가된 React Hooks에 대해 깊이 알아보기 위해 쓰는 글이다.

최근 1년 동안 React를 처음 접하게 되는 사람들은 자연스럽게 React Hooks와 함께 React를 시작하곤 한다. 처음 내가 제로초님의 NodeBird 강의를 들으며 개발을 시작했을 때도 Hooks를 이용해 대부분의 컴포넌트를 만들었다.

1년이 지난 지금useState, useCallback, useMemo , useImperativeHandle 등 다양한 Hooks를 사용할 수 있게 되었다. 하지만 최근 React를 깊게 공부하기 시작하며 Hooks가 어떻게 컴포넌트를 조작하는지에 대한 궁금증이 생기기 시작했다.

Class Component는 내부적으로 render라는 메소드를 포함하기에 state가 변경되는 로직에 해당 컴포넌트의 render 메소드를 호출하면 된다.
그렇다면, Functional Component는 어떻게 render 부분을 재 실행할 수 있을까?

이에 대한 고민을 하던 중 How does React Hooks re-renders a function Component?라는 글을 읽고 이 문제에 대한 React 내부 동작을 파악하기로 결정했다.

이 글은 “How does React Hooks re-renders a function Component?글에서 구현한 Overreact를 바탕으로 React 코드를 실제로 분석하는 방식으로 진행될 것이다.

01. Overreact를 통한 Hooks

Overreact란 이 글의 논의에 앞서 간략하게 Hooks의 원리를 파악하기 위해 “How does React Hooks re-renders a function Component?글에서 가상으로 구현한 가상 React이다.

React를 쓰기 위해서는 보통 다음과 같이 Class Component 혹은 Functional Component로 구현한다.

위에서 구현한 OverReact의 형태는 현재의 React와 큰 차이가 없다. 이제 각 부분의 구현체의 의사코드를 살펴보자.

먼저, Class Component의 내부 의사코드는 다음과 같다.

Class Component는 Component class안에 setState가 호출되면 this.render() 를 통해 컴포넌트를 다시 렌더링 시키는 것을 확인할 수 있다.

하지만, Functional Component에서 Hooks를 이용할 때는 state를 변경시 this를 사용할 수 없기에 위와 같은 동작을 구현하기 쉽지 않다. 이를 어떻게 구현했는지 OverReact를 통해 알아보자.

이 코드를 보기 전에, Functional Component는 render후 다시 useState 를 실행한다는 것을 염두해두는 것이 좋다.

코드를 분석해 본 결과 OverReact를 통한 useState의 원리는 Javascript의 Closure 개념을 사용하고 있었다. 이를 하나씩 확인해보자.

먼저, Component를 렌더링할 때 상위 스코프에 있는 context.Component 에 Functional Component를 보관하는 것을 확인할 수 있다.

또한, useState의 외부 영역에 contextcallId라는 변수를 둔다는 것을 확인할 수 있다. context 에는 context.hooks 라는 배열이 있는데, useState가 호출될 때마다 context.hooks 배열의 해당 인덱스에 state가 저장되는 원리이다.

setState 부분은 해당 hooks 인덱스에 새로운 state를 저장하고 context.Component 에 있는 Component를 다시 렌더링 하는 동작을 하며 이 과정에서 모든 useState는 새롭게 호출한다.

다만, useState 외부에 있는 context.hooks에는 접근할 수 있어 useState를 다시 호출해도 state는 유지된다.

02. React에서의 Hooks

이제 실제 React의 useState를 보자.

React에서 구현된 useState는 OverReact에서 보는 것 보다 좀 더 가독성이 높게 구현이 된 것을 확인할 수 있었다.

dispatcher 혹은 dispatch는 프로그래밍에서 특정 큐에서 이벤트나, 메세지를 처리 및 실행하는 부분을 말한다. 즉, 특정 상태를 준비 상태(Ready)에서 실행상태(Running)로 전환하는 것을 의미한다.

React의 코드는 의사 코드보다 레이어를 구분하고, 파일을 분리하였기에 dispatcher 내부에 비슷한 기능이 있을 것이라고 판단했다. 그래서 useStatemountState, updateState,dispatchAction 의 경로를 찾아 기능을 추적해보았다.

mountState 코드를 분석해 본 결과 지금의 useState는 OverReact에서 구현한 Context방식보다, 이전 글에서 본 scheduleUpdateOnFiber 에 Fiber를 등록해 스케줄러로 처리하는 방식에 더 가깝다는 것을 알 수 있었다.

즉, Functional Component도 Fiber라는 단위로 구분해서 updateQueue에 등록하고 이를 스케줄러가 처리하는 방식으로 동작한다는 것을 확인할 수 있었다.

03. React Hooks와 Closure(클로저)

JavaScript 언어에서 내부함수가 외부 함수의 맥락(Context)에 접근할 수 있는 것을 Closure(클로저)라 하며 MDN의 정의에 따르면 다음과 같이 정의되어 있다.

“A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.

실제로 React 코드를 파악해 본 결과 useStateClosure(클로저)를 이용하는 부분이 없었지만, OverReact 코드에서는 다음 부분에서 이를 확인할 수 있다.

클로저는 변수가 접근이 되어 언제든 변경될 수 있어 발생하는 부수 효과(Side Effect)를 억제할 수 있으며, 함수가 호출할 때마다 상태를 기억하지 못한다는 한계를 해결하기 위해 많이 사용된다.

Closure에 대해 더 깊게 이해하고 싶으면 아래 참고자료에 잘 설명된 블로그 글을 기재해뒀다. 다양한 글을 읽으며 해당 블로그가 JavaScript의 개념을 가장 쉽고 명쾌하게 설명한다고 생각되어 반드시 읽어 보는 것을 바란다.

결론

이 글을 통해 React Hooks가 Functional Component를 어떻게 재 렌더링(Re-rendering) 할 수 있는지에 대해 조사해보며 다음 내용을 진행했다.

첫번째로, 글 “How does React Hooks re-renders a function Component?를의OverReact 의사코드를 통해 state가 변할 때 Functional Component를 어떻게 재 렌더링 하는지에 대해 배워볼 수 있었다.

두번째로, 실제 React 코드를 분석하며 React Hooks가 의사코드와는 조금 달리 React Fiber 알고리즘을 통해 Fiber를 통해 scheduleUpdateOnFiber에 update를 등록하는 것을 알게 되었다.

마지막으로, 해당 부분을 배우기 위해 필요한 JavaScript의 Closure(클로저) 라는 개념을 조사하고 다시 한번 배워볼 수 있었다.

결과적으로 이 글을 통해useState가 어떤 방식으로 동작 하는지를 이해하게 되며 컴포넌트 디버깅 및 최적화 시 state 변화를 조금 더 단계적으로 파악할 수 있게 되었다.

맺음말

이 글의 시리즈를 쓰며 React 프레임워크의 동작 원리를 배우는 것이 컴퓨터 시스템에 대한 이해도를 높이는데 도움이 된다는 생각이 들었다.

또한, React 코드를 분석하며 JavaScript에 있는 다양한 API를 새롭게 학습하고 JavaScript에 대해 더 깊게 학습할 수 있는 계기가 되었다.

프로그래밍을 공부하는 다양한 방법 중 하나가 다른 사람의 소프트웨어를 읽고 학습하는 것이라고 한다. 다른 사람이 만든 코드를 보며 그 사람의 생각을 배울 수 있고, 다양한 문법적 기법 등을 배울 수 있는 것 같다.

다음 글에서는 최근에 종종 사용하며 관심을 갖게 되는 Context APIuseContext에 대한 글을 써보려고 한다.

Context API는 리액트의 props를 통한 단방향 데이터 전달의 번거로움을 해소하기 위해 여러 컴포넌트가 데이터를 공유하기 위해 제공하는 기능이다.

--

--