본문 바로가기

FrontEnd

React에서 마진 대신 Spacer 컴포넌트 활용하기

반응형

원문 보기

 

Let's Bring Spacer GIFs Back!

The 90s web gave us many delightful things: web rings, guestbooks, “under construction” animations, and spacer GIFs. In this article, we'll see how I use a Spacer component to solve common layout problems, and why it's often a great tool for the job in

www.joshwcomeau.com

TL;DR

그리드, 플렉스를 사용하는 경우가 아니면서 마진이 필요한 경우, Spacer 컴포넌트를 고려해본다.


HomeButton 컴포넌트를 만들어 봅시다.

function HomeButton() {
  return (
    <LinkButton href="/">
      <BackIcon />
      Go back home
    </LinkButton>
  );
}
render(<HomeButton />);

 

화살표가 텍스트에 너무 붙어있는것 같네요. 약간 조절해봅시다.

 

1. 마진 활용

function HomeButton() {
  return (
    <LinkButton href="/">
      <BackIcon />
      <span
        style={{ marginLeft: 16}}
      >
        Go back home
      </span>
    </LinkButton>
  );
}

render(<HomeButton />);

2. Spacer 활용

마진을 사용하는 대신 아이콘과 텍스트 사이에 공간을 추가하기 위해 명시적으로 새 컴포넌트를 만듭니다.
이것은 새로운 아이디어가 아닙니다. 사실 이것은 아주 오래된 아이디어입니다. 그리고 컴백할 때가 된 것 같아요.

역사적 문맥

90년대 후반에 일반적인 웹 사이트의 소스에선 이런 라인들을 발견할 수 있습니다.
<img alt="" src="spacer.gif" width="1" height="1" />
CSS는 아직 존재하지 않았고 웹 레이아웃은 HTML 테이블을 사용하여 구축되었습니다.
테이블은 복잡했고 빈 셀은 축소되어 레이아웃이 깨지기에, 개발자는 이 이미지를 테이블 셀에 넣어두었습니다.
 
이 다재다능한 도구에는 또 다른 목적이 있었습니다.
즉, 어떤 크기나 모양으로든 늘어나거나 찌그러질 수 있어 요소 사이에 보이지 않는 버퍼를 생성할 수 있습니다.
예를 들어 두 테이블 사이에 약간의 간격을 줄 때도 사용하였습니다.
 
이후 CSS는 스타일에 대한 대안을 제공하기 위해 브라우저에 추가되었습니다.
이 언어는 원래 레이아웃을 염두에 두고 설계되지 않았지만,
영리한 개발자는 float를 사용하여 테이블 레이아웃을 완전히 대체할 수 있다는 것을 빠르게 깨달았습니다!
 
 
이는 구조적으로 HTML, 레이아웃 및 프리젠테이션은 CSS, 행동은 JS)으로 관심사를 분리함으로써
복잡성을 줄이고 궁극적으로 유지 관리를 더 쉽게 만드는 데 도움이 되는 규칙을 갖게 되었습니다.

<font>, <center>, <strike> 및 <marquee>와 같은 태그는 폐기되고 CSS 대안으로 대체되었습니다.
<table>은 레이아웃이 아닌 실제 테이블을 위해 예약되었습니다.

10년 동안 모든 사람들은 각 관심사에 대해 별개의 기둥(pillars)을 갖는 것이 좋은 생각이라는 데 동의했습니다.

 

그리고 Facebook은 React.js를 출시했습니다...

그리고 많은 것이 변했습니다.

React.js의 유명한 특징 중 하나는 HTML이 JS 내에서 생성된다는 것입니다.

styled-components와 같은 도구를 추가하면 세 개의 기둥이 하나로 병합됩니다.

React는 Facebook과 같이 거대하고 거대한 웹 애플리케이션의 복잡성을 관리하는 데 있어 부인할 수 없는 강력한 도구입니다.

이것은 수년 전에 기술로 관심사를 분리한게 잘못된 선택임을 의미하나요?

 

저는 그렇게 생각하지 않습니다. 저는 2000년대에도 웹 개발을 했었지만, 그 당시 웹 개발 씬은 황무지나 다름없었고

HTML을 동적으로 업데이트 하는 JS 생성을 위해 PHP를 사용하던 시대입니다.

 

그래서 그 구조는 훌륭한 아이디어였고 지금도 여전히 좋은 아이디어지만 유일한 좋은 아이디어는 아닙니다.
하나의 올바른 방법은 없습니다.
비결은 앱의 크기와 복잡성이 커짐에 따라 스파게티가 되지 않도록 일종의 규칙을 마련하는 것입니다.
지금은 유명한 이 이미지가 정말 마음에 듭니다.

From&nbsp;Cristiano Rastelli

중요한 것은 (컴포넌트 간) 경계선을 그릴 수 있다는 것입니다.

그 선들이 어떤 축을 가로질러 그려지는지는 덜 중요합니다.

 

코드 보기

저는 다음과 같이 스페이서 컴포넌트를 작성합니다.

(마진을 width로 대체)

// Spacer.js
import styled from 'styled-components';
function getHeight({ axis, size }) {
  return axis === 'horizontal' ? 1 : size;
}
function getWidth({ axis, size }) {
  return axis === 'vertical' ? 1 : size;
}
const Spacer = styled.span`
  display: block;
  width: ${getWidth}px;
  min-width: ${getWidth}px;
  height: ${getHeight}px;
  min-height: ${getHeight}px;
`;
export default Spacer;
styled-components/Emotion을 사용하지 않는 경우:
// Spacer.js
import React from 'react';
const Spacer = ({
  size,
  axis,
  style = {},
  ...delegated,
}) => {
  const width = axis === 'vertical' ? 1 : size;
  const height = axis === 'horizontal' ? 1 : size;
  return (
    <span
      style={{
        display: 'block',
        width,
        minWidth: width,
        height,
        minHeight: height,
        ...style,
      }}
      {...delegated}
    />
  );
};
export default Spacer;

필수 prop은 size 뿐입니다. 기본적으로 정사각형을 만듭니다.

// Produces a 16px × 16px gap:
<Spacer size={16} />
단일 축을 지정할 수도 있습니다.
// Produces a 32px × 1px gap:
<Spacer axis="horizontal" size={32} />

이 컴포넌트는 픽셀 값을 사용합니다.

optical alignment를 위해 스케일을 벗어난 값을 선택해야 하는 경우가 많기 때문입니다.

즉, 이 패턴은 대신 디자인 토큰을 사용하도록 쉽게 조정할 수 있습니다.

<Spacer space="sm" />
<Spacer space="md" />
<Spacer space="lg" />
<Spacer space="xl" />

코드를 상세히 설명하도록 하겠습니다.

블록 span

원래 내 <Spacer> 컴포넌트는 span 대신 div를 렌더링했지만,
HTML 사양에 따르면 div는 p 및 버튼과 같은 특정 요소 내에 포함되어서는 안 됩니다.
 
span은 훨씬 더 유연한 태그이지만 기본적으로 인라인 요소이며,
인라인 요소는 레이아웃 작업을 위해 설계되지 않았습니다.
즉, 너비나 높이를 명시적으로 지정할 수 없습니다. 하지만 이것이 바로 우리가 스페이서를 원하는 이유입니다!
 
 
일반적으로 내 <Spacer>가 블록 수준 컴포넌트 분리하기를 원하므로
display: inline-block 대신 display: block을 제공하는 것이 합리적입니다.
인라인 요소를 분리하려는 드문 경우 합성으로 수행할 수 있습니다.
const InlineSpacer = styled(Spacer)`
  display: inline-block;
`;

 

최소 / 최대 크기

width, height 외에 min-width와 min-height도 설정합니다.

너비가 실제로는 제약이라기 보다는 제안이기 때문입니다.

function HomeButton() {
  return (
    <LinkButton href="/" style={{ maxWidth: 200 }}>
      <BackIcon />
      {/* Quick Spacer implementation */}
      <span
        style={{
          display: 'inline-block',
          width: 16,
          height: 16,
          background: 'hotpink',
        }}
      />
      Go back home
    </LinkButton>
  );
}

render(<HomeButton />);

<Spacer />를 분홍색 상자로 대체하여 무슨 일이 일어나고 있는지 볼 수 있습니다.
이 예에서 컨테이너의 너비는 maxWidth로 제한되며,
공간이 충분하지 않습니다.
 
분홍색 상자는 16픽셀 x 16픽셀이 되기를 원하지만 렌더링된 출력에서 ​​압축되고 있음이 분명할 것입니다.
직사각형이 아닙니다. 브라우저는 난감한 상황에 놓였습니다. 모든 것을 렌더링할 공간이 충분하지 않습니다!
 
브라우저는 기본적으로 컨텐츠가 없는 children을 압축합니다.
이것은 합리적인 가정이지만 우리가 원하는 것은 아닙니다!
 
min-width는 보다 견고한 속성입니다. 밀리지 않습니다.
이를 통해 브라우저에 이 요소가 중요하고 압축되는 것을 원하지 않는다는 것을 알릴 수 있습니다.
 
 
분홍색 상자의 너비를 minWidth로 변경하여 이 동적 동작을 확인하십시오!
스페이서가 형제보다 더 중요한 이유는 무엇입니까?
전문적이고 세련된 UI를 유지하려면 일관된 간격이 절대적으로 중요하기 때문입니다.
 
텍스트가 잘리거나 여러 줄로 된 경우에도
내 페이지의 모든 버튼에 아이콘과 텍스트 사이에 일정한 간격이 있음을 신뢰할 수 있기를 원합니다.
 
 

반응형 버전

<Spacer /> 컴포넌트는 반응형이 아닙니다.
즉, 모든 뷰포트에서 동일한 양의 공간을 차지합니다.
주로 동적 간격이 필요하지 않은 상황에서 스페이서 컴포넌트를 사용하는 경향이 있습니다.
그러나 반응형 스페이서의 이점을 얻을 수 있는 상황이 발생하면 다음과 같은 API를 사용하도록 업데이트하겠습니다.

<Spacer
  size={32}
  when={{
    lgAndUp: 64,
    xlAndUp: 96,
  }}
/>​

나는 상황에 맞는 것들을 위해 when이라는 prop 이름을 사용하는 것을 좋아합니다.

이 prop의 구현은 특정 스타일링 솔루션과 테마에 따라 달라집니다.

Pros and cons

다른 사람들처럼 마진을 사용하지 않는 이유는 무엇입니까?
 
몇 가지 이유가 있습니다. 의미상으로, 그것은 때때로 나에게 이상하게 느껴집니다.
 

 

1.홈 버튼 예제에서 마진은 뒤로 화살표로 가야 할까요 아니면 텍스트로 가야 할까요?

어느 한 요소가 공간을 "소유"해야 하는 것처럼 느껴지지 않습니다.

이것은 별개의 레이아웃 관심사입니다.

 

2. 마진은 펑키합니다. 그들은 이상하고 놀라운 방식으로 겹칩니다.

위의 예에서 마진은 겹치지 않습니다만, 마진 겹침을 염두에 두어야 하는 정신적 오버헤드가 있습니다.
 

3. 구조적 의미가 있습니다.

위의 예에서 텍스트를 <span>으로 래핑했는데,
이는 특정 상황(예: 그리드의 자식)에서 문제를 일으킬 수 있습니다. 부모와 자식 사이에 추가 레이어를 두는 것은 문제가 될 수 있습니다.
 

4. 마진은 근본적으로 현대적인 컴포넌트 아키텍처와 상충됩니다.

 
그들은 컴포넌트 경계를 벗어나 인접 컴포넌트로 자신의 정보를 누출합니다.
 
점점 더 많은 개발자가 마진 대신 레이아웃 컴포넌트에 의존하고 있습니다.
<Spacer/>는 그 툴킷의 훌륭한 도구입니다.
 
'갭' 애트리뷰트는요?

CSS 그리드로 작업 시 grid-gap을 사용할 수 있습니다.
grid-gap을 사용하면 직관적이고 의미론적인 방식으로 컨테이너의 모든 자식 사이에 간격을 설정할 수 있습니다.
저는 그리드 갭을 좋아합니다.

CSS 사양은 최근에 정확히 같은 방식으로 작동하지만 flexbox와 함께 사용할 수 있는 gap 속성을 추가했습니다.
이것은 훌륭합니다.💯

Browser support는 나날이 개선되고 있지만 사용하기에는 아직 시기 상조인것 같습니다.

완전히 지원되면  내 스페이서 컴포넌트가 불필요할까요?
저는 그렇게 생각하지 않습니다.

저는 여전히 그리드/플렉스 부모 외부의 "flow layout"에서 작업하는 데 많은 시간을 보냅니다.
gap은 우리의 많은 문제를 해결할 수 있지만 모든 문제를 해결하지는 못합니다.

 

사용 시 문제점?

개발자가 사용하기엔 좋은게 확실합니다만.

가장 큰 걱정거리는 DOM 크기에 관한 것이었습니다.
Google은 페이지를 1500 DOM 노드 미만으로 유지할 것을 권장합니다. 이는 일부 복잡한 페이지가 초과하는 임계값입니다.(under 1500 DOM nodes)
하지만 스페이서를 한 페이지에 그렇게 많이 추가할 일은 별로 없습니다.

접근성 및 SEO와 같은 DOM 오염을 피해야 하는 다른 이유가 있습니다.

그러나 내가 아는 한, 몇 가지 여분의 빈 스팬은 이러한 측면에서 해롭지 않습니다.

 

마치며

이 블로그 게시물을 위해 약간의 조사를 하던 중에 스페이서 gif를 제공하는 API인
spacerGIF.org를 우연히 발견했습니다! 10년 전의 유물도 아닙니다. 지난 1년 동안 30배(grown 30x) 성장했습니다!
 
나는 많은 독자들,
특히 오랫동안 개발해온 독자들이 이 아이디어에 대해 본능적으로 부정적인 반응을 가질 것이라고 생각합니다.
 
웹의 초기 지저분한 시절의 산물이라 여길 수도 있습니다만,
오늘날의 생태계는 다르며 이 오래된 방식은 놀랍게도 컴포넌트 시스템에 잘 맞습니다. 
 
 

 

반응형