React 앱을 구조화하는 100% 올바른 방법 (또는 왜 그런 것이 없는지) 1편

CianJS
9 min readJul 7, 2018

--

이 글은 https://hackernoon.com/the-100-correct-way-to-structure-a-react-app-or-why-theres-no-such-thing-3ede534ef1ed을 작성하신분께 허락을 맡고 번역한 글입니다. 오역제보는 언제든지 환영합니다.

내용을 시작하기 전에 위 링크의 글이 생각보다 길기 때문에 조금 나눠서 작성되었음을 알려드립니다.
-1편의 내용: React앱 구조화 방법 2가지에 대한 소개.

React 앱을 구조화 할때, 이상적인 구조는 최소한의 노력(or 비용)으로 코딩 화면을 이동하는 것이다.

이 게시물에서는 내가 React 앱을 구조화하는 방법과 구조화에 관해 무슨 결정을 했는지에 대해서 이야기하려한다.

그리고 당신이 어떤 일을 하고 여기서 찾은 어떤 것이 좋은 도움이 됬는지 댓글로 들려주면 매우 기분이 좋을 것 같습니다.

당신에게 맞는 것은?

아마도 이건 당신에게 항상 명확했을테지만 그저 나를 위해 클릭했을뿐 일 것이다. 앱의 구조는 컴퓨터와는 상관이 없다.

당신의 컴포넌트, 리듀서, 스토어, 유틸리티, 그외의 모든 것이 오직 한 파일에만 있는 앱을 상상해보아라.

물론 끔찍할 것이지만, 지금당장 위의 것이 왜 나쁜것인지 생각해보아라.

우린 당신이 제대로 생각하지 않았다는 것을 알고 있다. 그래서, 너희들은 그저 내 생각을 들으면 될 것이다.
이 거대한 파일의 문제는 엄청 보기 어렵다는 것이다. 하지만 만약 코드의 각 영역에 북마크를 건다면 아마 함수당 북마크를 하나씩 걸 것이다. 이 작업은 중복된 북마크가 생길지도 모른다. 이 북마크에 대해선 어떤가?

이것이 쓸데없는 것처럼 보일 수도 있지만, 나는 파일 구조를 결정할때 할 수 있는 최선은 코드를 최대한 쉽게 찾을 수 있도록 하는것이라고 생각한다.
당신이 가진‘파일들’은 작업이 끝난 JS 코드의 일부분입니다.

그리고 이것이 당신이 “나의 앱을 구조화하는 최고의 방법은 무엇입니까”라는 질문에 대한 답을 가져올 수 없는 이유다.
그건 당신의 코딩 습관을 찾는것과 환경 설정에 많이 의존하기에 질문에 답하기 어려운 질문일 거다.

100% 옳은 애플리케이션 구조가 무엇인지 알아내기 위해 가장 흔한 코드들을 정량화했습니다.

새 컴포넌트 생성하기(Create a new component). 보통 이것은 이미 존재하는 컴포넌트의 복사/붙여넣기일겁니다.

다른 모듈 하나 가져오기(Import one module into another). 여기서는 실제로 타이핑 한 것을 의미합니다.

import { SomeComponent } from ‘../blah/de/blah.js’;

소스 이동(Jump to source). 이것은 어떤 파일을 보고 있을 때, 외부의 것을 참고하고 있는 <HeaderNav> 컴포넌트를 부르면 정의된 컴포넌트로 이동한다.

알려진 파일 열기(Open a known file). 아마 설명이 필요 없을테지만, 난 이 설명들이 좋게 보이길 원한다. 그래서 속으로 “난 header nav를 열고 싶어”라고 생각했고 키보드로 바로가기와 파일 이름을 입력해서 그 파일을 열고보고 싶다. 젠장, 지금 이 설명이 가장 길고 처음부터 할 필요가 없었던것 같아..

모르는 파일 찾기(Browse for a file I don’t know the name of). 아마 우리는 사용자 프로필 사진 아래에 드롭 다운해주는 기능을 표시하고 그 기능이 작동하지 않았을 수도 있다. 그래서 컴포넌트의 디렉토리 구조를 검색하려한다.

다른 열린 파일로 탭 변경(Change tab to another open file). 이건 매우 좋은 것이다. 나는 현재 7개의 파일이 열려 있고 탭 이름을 클릭해서 다른 탭으로 전환할 수 있기를 원한다. (아니면 키보드를 사용해서)

다음은 내가 얼마나 자주하는 행동 대해서 생각해보았다. 나는 다시 작년에 생성한 컴포넌트 수를 세고 파일당 import한 수의 평균과 그외의 생각나는 것들에 대해 정리해보았다.

Sorted by the order that I thought of the things (예상했던 순서대로 정렬되었다.)

이제 이 데이터들을 토대로 React App을 구성하는 모든 측면을 객관적으로 볼 준비가 되었습니다. 하나씩 하나씩 살펴보겠습니다.

디렉토리 구조

일반적으로 모듈이 다른 모듈내에서만 사용된다면, 아래처럼 디렉토리 구조를 중첩시켰으면 한다.

<HeaderNav>는 <Header> 컴포넌트 안에서만 참조되므로 자식 컴포넌트가 됩니다.
<Button>은 어디에서나 참조될 수 있으므로 최상위에 둡니다.

이건 좋은 규칙이지만 나는 이 성가신 규칙은 매우 엄격하다. 기술적으로 모든 것(컴포넌트)은 App 아래에 둔다. Page도 마찬가지. 하지만 나의 디렉토리 구조는 저렇게 표현하지 않을 것인데, 그 이유는 난 저게 싫다.

이상하게 들릴지도 모르지만 이건 꽤나 기본적이다. 당신이 만든 규칙을 가진 구조라면 더 탐색하기 어렵고 눈이 이제 그 코드를 보기 싫다고 할거다.

컴포넌트의 밖에서는 나는 디렉토리 구조가 매우 중요하다고 생각하지 않는다.
당신은 리듀서와 action 생성자가 너의 서비스에서 같은 디렉토리안에 있어야하는 것인지 고민할 수 있다. 하지만 만약 내게 묻는다면, 기본적인 폴더 이름(actionCreators, reducers, data, etc.)을 가진 구조가 필요하다.

이것은 우리가 필요하고 원하는게 다른 것이 첫번째 포인트일 것이다. 나는 디렉토리 구조를 검색해서 파일을 열지않는다는 것을 알아냈고, 나는 디렉토리 구조에 더 낮은 중요성을 적용했다. 나는 몇 백개 이상의 컴포넌트를 가진 프로젝트를 한번도 해보지 않았습니다.

만약 당신이 디렉토리 구조 검색에 더 많이 의존하거나 30,000개의 컴포넌트를 가진 Facebook이라면 필요한게 매우 다를 수 있습니다.

또 다른 한가지: 컴포넌트의 이름을 ‘정규화된’(그리고 세계적으로 고유한)방식으로 하는 것이 좋다.
예를 들어, HeaderNav는 Header안에 있고 당신은 Nav라고 이름을 지을 수 있을 것이다. 그게 맘에 든다면 좋다. 하지만 파일들의 이름을 입력해서 열고 탭에 적힌 글자를 보고 파일을 전환한다. 이 두 경우 모두 정규화된 이름을 갖는 것이 유용하다.

그리고 파일이 컴포넌트 이름과 일치하는 BEM을 따른다면, 전역 고유 컴포넌트 이름이 필요하다.

Container components는 어때?

컨테이너 컴포넌트는 까다롭다. 왜냐하면 그들은 일종의 컴포넌트지만 그렇지않다.

구조를 컨테이너 컴포넌트에 알맞게 맞추는 두 접근 방법이 있습니다.

  1. Presentational Components랑 똑같이 하자.
  2. 디렉토리 구조의 밖에 두어 ‘background에서’ 돌아갈 것이다. 그저 컴포넌트에 데이터를 제공하는 것이다.

첫번째는 markup에서 실제 컨테이너 컴포넌트를 참조한다. 그래서, 컨테이너를 가지고 있는 header 컴포넌트의 예제는 다음처럼 컨테이너를 포함한다.

import React from ‘react’;
import HeaderContainer from ‘./HeaderContainer/HeaderContainer’;
import Page from ‘./Page/Page’;
import Footer from ‘./Footer/Footer’;

const App = (props) => (
<div>
<HeaderContainer />

<Page data={props.pageStuff} />

<Footer {…props.propsRelevantToFooter} />
</div>
);

export default App;

따라서 여기서는 <Page>와 <Footer> 컴포넌트로 특정 데이터를 전달해주고 있고 <HeaderContainer>에서는 알아서 필요한 데이터를 처리하고 있을것이다.

이 방법을 이용한다면, 논리적 구조는 다음 처럼 될 것입니다.

두 번째 옵션은 컨테이너 컴포넌트를 구조 밖에 두는 것이다. wrap하는 컴포넌트의 세부 사항은 이렇게 생각할 수도 있습니다.

즉, <Header>를 export할 때 컨테이너 컴포넌트에 <Header>를 보냅니다.

import React from ‘react’;
import headerContainer from ‘./headerContainer’;

export const Header = () => (
<header>
Just header stuff
</header>
);

export default headerContainer(Header);

그리고 참조(App.js)하고 있는 것은

import React from ‘react’;
import Header from ‘./Header/Header’;
import Page from ‘./Page/Page’;
import Footer from ‘./Footer/Footer’;

const App = (props) => (
<div>
<Header />

<Page data={props.pageStuff} />

<Footer {…props.propsRelevantToFooter} />
</div>
);

export default App;

단점은 <Header />가 다른 곳에 데이터를 제공할 때 그것이 뭔지 알 수 없다는 것입니다.
장점은 컴포넌트 계층이 한단계 더 적습니다.

이것이 당신이 원하는 것이라면, 너는 컨테이너를 같은 디렉토리에 있는 데이터를 제공하는 컴포넌트로 함수로 만들 수 있다.

(또한 원래 Header뿐 아니라 컨테이너로 래핑된(wrapped, 태그로 감싸여진) Header를 default export로 내보냈습니다. 전자는 유닛테스트를 위한 것이고 파일과 이름이 같은 상수(const)는 non-default로 내보내지면 혼란스러울 수 있다고 알려줍니다. 나는 린터(소스 코드를 분석해서 문법 체크를 해주는 기능)에 의존하는 경향이 있습니다.)

나는 중간크기의 프로젝트에 첫번째 접근법을 사용했고 그 방법은 아주 잘 작동했다. 나는 최근에 두번째 예제를 새 프로젝트에 시도했고(6개의 컨테이너 컴포넌트만 써서) 별로 맘에 들지 않아서 첫 번째 방법을 게속 쓰기로 했습니다.

나는 어느 방법을 시도해도 괜찮다고 생각합니다.

추가: 나는 “있잖아, 그건 중요하지 않아”라고 말하며 당신의 하루하루를 계속 나아가는 것이 너에게 좋은 예술을 깨닫는 시점이 있다고 생각합니다.
이 기사는 손톱관리하는 좋은 짧은 기사입니다. (경고: 기사의 제목에 욕이 포함됩니다. fuck같은 단어를 보기 싫다면 클릭하지마세요.)

--

--

CianJS

영어랑 영문서 자유롭게 듣고 읽고 싶다..