컴포넌트 설계와 디자인패턴

·2023년 11월 13일
post-thumbnail

📯 컴포넌트 설계

🕐 컴포넌트란?

재사용이 가능한 독립적인 기능을 수행하는 '모듈'로서 웹의 구성 요소이다.

🕑 컴포넌트와 객체 지향

객체지향 언어에서는, 계속해서 반복되는 장치를 인터페이스로 제공한다.

이때 인터페이스는, 사용자게에 해당 소프트웨어를 쓰기 위한 메소드를 공개하고, 규격화된 메소드 환경에서 소프트웨어를 개발할 수 있는 환경을 제공해준다.
=> 컴포넌트는 각각 독립된 모듈이다.

🕒 컴포넌트의 역할

1) 데이터 관리

컴포넌트 안의 상태와 더불어 외부로부터 주입된 데이터를 관리한다.

2) 데이터를 UI로 표현

컴포넌트에서 관리하는 데이터를 어떻게 화면에 렌더링 할지에 대한 선인이다.

3) 사용자와의 상호작용

사용자로부터 어떤 인터랙션을 받을지 이벤트 핸들러를 정의해주고, 이벤트 핸들러에서 일어나는 일을 작성한다. 이때, 데이터를 관리하는 조작을 하기도 한다.

🕓 컴포넌트 설계

1) SOLID 원칙

하나의 컴포넌트가 하나의 책임만을 담당한다.
재사용성, 확장성, 관리 용이성이라는 목적을 가지고 있다.

*책임이란?
컴포넌트가 수행하는 행동!

2) 데이터 기반의 설계

외부로부터 주소 목록을 가져와 렌더링하는 페이지를 나누어본다.

function AddressPage() {
  const [addresses, setAddresses] = useState<주소[] | null>(null)

  const handleSaveClick = (id: string) => {
    // save
  }
  const handleUnsaveClick = (id: string) => {
    // unsave  
}

  useEffect(() => {
    ;(async () => {
      const addresses = await fetchAddressList()

      setAddresses(addresses)
    })()
  }, [])

  return (
    <section>
      <h1>주소목록</h1>
      <ul>
        {addresses != null
          ? addresses.map(({ id, address, saved }) => {
              return (
                <li key={id}>
                  {address}
                  <button onClick={saved ? handleUnsaveClick : handleSaveClick}>
                    {saved ? '저장' : '삭제'}
                  </button>
                </li>
              )
            })
          : null}
      </ul>
    </section>
  )
}

주소 목록을 렌더링하는 AddressPage 컴포넌트는 데이터를 외부에서, 데이터를 UI로 표현하며 인터렉션을 받는다.

데이터를 중심으로 컴포넌트를 분리하면,
'주소목록', '주소'를 기준으로 분리할 수 있다.

function AddressPage() {
  return (
    <section>
      <h1>주소목록</h1>
      <AddressList />
    </section>
  )
}

주소목록 데이터를 props로 내려받지 않고, 스스로 데이터를 가져올 수 있다.
또한, 각 데이터를 하나하나 관리하는 AdressItem으로 분리할 수 있다.

function AddressList() {
  const [addresses, setAddresses] = useState<주소[] | null>(null);
  // call fetchAddressList

  return (
    <ul>
      {addresses.map(address => {
        return (
          <AddressItem
            key={address.id}
      address={address.address}
            saved={address.address}
            onSaveClick={handleSaveClick}
            onUnsaveClick={handleUnsaveClick}
            {...address}
          />
        );
      })}
    </ul>
  );
}

이때, 사용자와의 인터렉션은 여전히 컴포넌트에 정의되어 있다.
또한, AddressItem 컴포넌트는 자신이 보여주는 데이터를 컴포넌트 자체에서 관리하지 못한다.

2-1) 역할 중심으로 분리하기

function AddressItem({ id, address, saved }: 주소) {
  const handleSaveClick = () => {
    // save
  }
  const handleUnsaveClick = () => {
    // unsave
  }
  return (
    <li key={id}>
      {address}
      <button onClick={saved ? handleSaveClick : handleUnsaveClick}>
        {saved ? '저장' : '삭제'}
      </button>
    </li>
  )
}

주소 데이터를 기준으로 사용자 인터렉션이 발생하고 있고, 역할에 따라 핸들러가 정의되었고 불필요한 인자가 사라지게 되었다.
주소 아이템 컴포넌트는 하지만, 데이터를 보여주고 저장하고 삭제하는 역할도 수행하고 있다. 따라서 저장과 삭제 역할을 버튼에게 위임할 수 있다.

참고로 이때, 위 컴포넌트를 이용하는 AddressList 컴포넌트는 다시 작성된다.

function AddressList() {
  const [addresses, setAddresses] = useState<주소[] | null>(null);
  // call fetchAddressList

  return (
    <ul>
      {addresses.map(address => {
        return <AddressItem key={address.id} {...address} />;
      })}
    </ul>
  );
}

버튼에 따라 위임해보자

function AddressItem({ id, address, saved }: 주소) {
  return (
    <li key={id}>
      {address}
      {saved ? <SaveAddressButton id={id} /> : <UnsaveAddressButton id={id} />}
    </li>
  )
}

function SaveAddressButton({ id }: { id: string }) {
  const handleSaveClick = () => {
    // save
  }
  return <button onClick={handleSaveClick}>저장</button>
}

function UnsaveAddressButton({ id }: { id: string }) {
  const handleUnsaveClick = () => {
    // unsave
  }
  return <button onClick={handleUnsaveClick}>삭제</button>
}

3) 일반적인 인터페이스 설계

일반적인 인터페이스란,

  1. 특정 도메인에 대한 맥락을 가지고 있지 않아 어느 도메인에든 사용할 수 있는 컴포넌트
  2. 사용자 입장에서 내부를 보지 않고도 컴포넌트의 역할을 이해할 수 있는 인터페이스

따라서, 우리는 위에 살펴본 컴포넌트에서 문제를 발견할 수 있다.
바로, '주소'라는 특정 도메인과 강하게 연결되어 있다는 것인데,
이를 해결하기 위해서 도메인 맥락을 제거해야 한다.
제거하면 코드는 다음과 같아진다.

// before: AddressItem
function FlexListItem({ text, button }: { text: string, button: ReactNode }) {
  return (
    <FlexLi>
      {text}
      {button}
    </FlexLi>
  )
}
const FlexLi = styled('li')``

이렇게 수정된 FlexListItem 컴포넌트는 다른 도메인에서도 재사용할 수 있다.

🎴 디자인 패턴

🕐 패턴이란?

양식, 무늬, 변동, 모범 즉 되풀이되는 사건이나 물체의 형태

🕑 디자인 패턴이란?

재사용성을 지닌 객체지향 소프트웨어 요소
소트프웨어 개발을 할 때 일반적으로 발생하는 문제들에 대한 가장 효과적인 해결 방법이다. 즉, 특정 구현을 직접 제공하지는 않지만, 특정한 상황에서 구조적인 문제해결하는 방식이다.

🕒디자인 패턴의 장점

  1. 좋은 설계는 구현을 쉽게 한다.
  2. 효율적인 의사소통이 가능하다.
  3. 객체 지향 원칙을 지킬 수 있다, 따라서 유연하고 확장가능한 코드 설계가 가능하다.
  4. 설계 우선 주의, 바람직한 설계가 가능하다.
  5. High Level View, 큰 그림을 볼 수 있어, 이해도가 높아진다.

🕓 디자인 패턴의 역할

  1. 쉽게 변경하는 방법을 제공
  2. 추상화의 위임: 변경되는 부분을 추상화하고 인터페이스를 통해 모듈에 위임
  3. 상속보다 구성으로 해결: 상속보다 더욱 유연함

🕔 언제 사용할까?

특정 부분이 계속 변경될 것이라고 예상되는 경우

  • 🌀 MVC구조
    -> 사용자의 정신 모델(이렇게 될 것이다)와 컴퓨터에 존재하는 디지털 모델 사이의 격차를 해소하는 것, 즉, 역할에 따라 모듈을 분리한다.
    -> Controller가 View를 통해서 Event를 생성하면, Modal에게 명령을 수행하고, View를 통해 사용자에게 전달(View의 갱신)

🕕 디자인 패턴의 종류

💬 Layout Components

페이지의 전체 구조와 배치를 정의하는 역할을 하며,
이를 통해 개발자는 컴포넌트들의 위치와 독립되어 개별적으로 관리하고 구성할 수 있다. 따라서, 재사용성과 유지보수성이 향상된다.
컴포넌트들이 그들이 어디에 위치해야 하는지에 대한 장소 정보 알 필요가 없다는 개념에서 출발하였다.

are used for,,,


분활하면이나, 리스트, 모달 등에 사용된다.
이를 적용하면,
1. 컴포넌트 트리 깊이가 낮아진다.
2. 레이아웃 컴포넌트는 무상태이므로, 리렌더링되지 않는다.
3. 시각적인 테스트에 유용하다.

examples

// App Component acting as the parent component to render a Layout Component

import { SplitScreen } from "./SplitScreen";

const LeftComponent = ({ name }) => {...}
const RightComponent = ({ message }) => {...}

function App() {
  return (
    <SplitScreen leftWeight={1} rightWeight={3}>
      <LeftComponent name="hyein" />
      <RightComponent message="She is always sleepy......." />
    </SplitScreen>
  );
}

export default App;

-> 해당 코드에서 SplitScreen 컴포넌트가 LeftComponentRightComponent를 나누어서 보여주고 있는 것을 알 수 있다.
width 값을 조정해서 자식 컴포넌트의 너비값을 조절하고 있다.

import styled from "styled-components";

export const SplitScreen = (props) => {
  const {children, leftWeight, rightWeight} = props;
  const [left, right] = children;

  return (
    <Container>
      <Pane weight={leftWeight}>{left}</Pane>
      <Pane weight={rightWeight}>{right}</Pane>
    </Container>
  );
};

const Container = styled.div`
  display: flex;
`;

const Pane = styled.div`
  flex: ${(props) => props.weight};
`;

-> SplitScreen 컴포넌트는 다음의 예시와 같다.
각각 왼쪽, 오른쪽의 요소들을 props로 내려 받고 있으며, main을 감싸는 Container 컴포넌트와 이를 나누고 있는 Pane 컴포넌트를 볼 수 있다.

이렇게, 유연하고 재사용성이 있는 SplitScreen 컴포넌트를 만들 수 있다.
결론적으로, 이를 활용하면, screen을 나누고 싶을 때 언제든지 재사용할 수 있으며,
각 컴포넌트들은 자신들의 위치와 상관없이 독립적으로 관리될 수 있다.

💬 HOC (Higher Order Component) Pattern

HOC는 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수이다.

are used for,,,

  • 많은 컴포넌트들에서 복잡하지만 같은 로직을 공유하여 코드를 반복해야 할 때 주로 사용한다.
  • 원래 있었던 컴포넌트에 추가적인 기능을 넣고 싶을 때에도 사용한다.

    즉, HOC를 이용하면, 코드를 다시 쓰지 않고도, 로직을 사용할 수 있다.
    HOC는 순수함수로 기대된다. 이때 HOC는 다른 컴포넌트들처럼 대문자로 시작하지 않아도 된다. (jsx가 안에 완전히 들어가는 게 아니므로)

HOC -> SomeComponent ---> New Component

examples

function withStyles(Component) {
  return props => {
    const style = { padding: '0.2rem', margin: '1rem' }
    return <Component style={style} {...props} />
  }
}

const Button = () = <button>Click me!</button>
const Text = () => <p>Hello World!</p>

const StyledButton = withStyles(Button)
const StyledText = withStyles(Text)

-> 위의 코드에서 각 Button, Text 컴포넌트들은 StyledButtonStyledText로 변환되어 withStyles 로직이 적용될 수 있다.

💬 Provider Pattern

여러 컴포넌트들이 데이터를 사용할 수 있게 해야 하는 상황일 때 사용한다.

are used for...

계속해서 props를 내리는 방식은, 규모가 커질 수록 관리하기 어려워지며
데이터가 필요하지 않는 컴포넌트일지라도, props를 받게 될 수 있다는 단점이 있다.
즉, props drilling에 의존하지 않고 직접 데이터에 접근할 수 있도록 설계한다.

examples

const DataContext = React.createContext()

function App() {
  const data = { ... }

  return (
    <div>
      <DataContext.Provider value={data}>
        <SideBar />
        <Content />
      </DataContext.Provider>
    </div>
  )
}

-> 컴포넌트들을 Provider로 감싼 뒤, createContext 메서드를 통해 Context 객체를 만들어 낸다. 이후 Provider 컴포넌트는 value라는 prop으로 하위 컴포넌트들에게 내려줄 데이터를 받는다. 해당 컴포넌트의 모든 자식 컴포넌트들은 해당 provider를 통해 value prop에 접근 가능하다.(이떄, Provider는 HOC로 Context 객체 제공)

이후 자식 컴포넌트들은 useContext 를 활용하여 data에 접근할 수 있다.

💬 Compound Component Pattern

부모 컴포넌트가 자식 컴포넌트와 암시적으로 state를 공유할 수 있는 방인이다.
여러 컴포넌트들이 모여서 하나의 동작을 할 수 있도록 도와준다.
하나의 완벽한 컴포넌트를 구성하는 암시적 상태 공유 컴포넌트 API 집합을 제공하는 방법이다.

are used for...

select 구성요소에서 이와 비슷한 맥락을 볼 수 있다.

//html
<select>
  <option value="1">Option 1</option>
  <option value="2">Option 2</option>
</select>

//CustionSelect.js
<CustomSelect
  options={[
    {value: '1', display: 'Option 1'},
    {value: '2', display: 'Option 2'},
  ]}
/>

위에서는 option은 굉장히 유연하지만, 아래의 경우, option의 상태를 외부에서 관리하고 있다.
참고

examples

이를 ContextAPICompound Component Design Pattern 으로 해결할 수 있다.

//내부 상태를 암시적으로 공유할 Context와 Context Provider Component를 만든다.
const ToggleContext = React.createContext()
ToggleContext.displayName = 'ToggleContext'

function Toggle({children}) {
  const [on, setOn] = React.useState(false)
  const toggle = () => setOn(!on)

  return (
    <ToggleContext.Provider value={{on, toggle}}>
      {children}
    </ToggleContext.Provider>
  )
}


//useContext 훅을 만든다.
function useToggle() {
  const context = React.useContext(ToggleContext)
  if (context === undefined) {
    throw new Error('useToggle must be used within a <Toggle />')
  }
  return context
}

//서브 컴포넌트들의 api를 받는다
function ToggleOn({children}) {
  const {on} = useToggle()
  return on ? children : null
}

function ToggleOff({children}) {
  const {on} = useToggle()
  return on ? null : children
}

function ToggleButton({...props}) {
  const {on, toggle} = useToggle()
  return <Switch on={on} onClick={toggle} {...props} />
}

//적용
//App.jsx
function App() {
  return (
    <div>
      <Toggle>
        <ToggleOn>The button is on</ToggleOn>
        <ToggleOff>The button is off</ToggleOff>
        <div>
          <ToggleButton />
        </div>
      </Toggle>
    </div>
  )

💬 Container/Presentational Pattern

비즈니스 로직으로부터 뷰를 분리하는 방식이다.

1) Presentational Components
props를 통해 데이터를 받는 컴포넌트로,
데이터를 화면에 표시하는 것이 주기능이며 styleSheet를 포함한다.
즉, 어떻게 화면에 표시할 것인가를 담당하는 컴포넌트이다.
하지만, 데이터를 수정하거나 가져오지 않는다. 그저 화면에 보여주기만 하고, state를 따로 가지거나 외부로부터 데이터를 가져오는 동작을 하지는 않는다.

2) Container Components
Presentational 컴포넌트에 데이터를 전달하는 것이다. Container 컴포넌트 자체는 화면에 아무 렌더링을 일으키지 않는다. 따라서 스타일시트도 포함되지 않는다.

how?

이러한 패턴은 React Hook으로 대체 가능하다.
아래 코드와 같이 커스텀 훅을 만들 수 있다.

export default function useDogImages() {
  const [dogs, setDogs] = useState([])

  useEffect(() => {
    fetch('https://dog.ceo/api/breed/labrador/images/random/6')
      .then(res => res.json())
      .then(({ message }) => setDogs(message))
  }, [])

  return dogs
}

💬 uncontrolled component

스스로 내부 상태를 관리하는 컴포넌트
제어되는 컴포넌트(controlled component)는 onPropChange 콜백과 값 prop 쌍을 필요로 하며, 컴포넌트 내 표현은 이에 의해 완전히 제어된다.

💬 Module Function

모듈 스코프 내에 변수를 선언하고, 명시적으로 외부에 export 하지 않으면 바깥에서 해당 변수를 접근할 수 없게 하는 패턴이다.

how?

math.js 모듈이 몇 가지 함수를 가지고 있다.

  return x + y;
}
function multiply(x) {
  return x * 2;
}
function subtract(x, y) {
  return x - y;
}
function square(x) {
  return x * x;
}

이때 함수들을 외부에서 사용하려면 이들을 export 해야 한다.
또한, 함수를 쓰는 쪽에서 import 구문을 사용하여 이들을 불러와야 한다.

import { add, multiply, subtract, square } from './math.js'
출처

리액트디자인패턴
리액트디자인패턴patterns
리액트디자인패턴넥스트리블로그
SLASH22_지속가능한성장과컴포넌트
변경에유연한컴포넌트
변경에유연한컴포넌트2
FEConf2023

profile
new blog: https://hae0-02ni.tistory.com/

11개의 댓글

comment-user-thumbnail
2023년 11월 14일

컴포넌트 설계를 단계에 따라 하나하나 리팩토링 해나가니 이해하기 수월했습니다! 뚱땅뚱땅 코드를 작성하다보면 하나의 컴포넌트가 여러 개의 기능을 수행하게 된 적이 많이 있는데 다시 리팩토링을 해봐야겠다는 생각이 들었습니다!! 컴포넌트에 대한 재사용을 생각하면서 코드를 작성하다 보면 이쪽에서 필요한 기능과 저쪽에서 필요한 기능이 일치하지 않아 끼워맞추다보니 점점 코드가 이상해지고, 여러 수정이 필요하게 되는 경우가 자주 발생했는데 기능을 잘게 쪼개지 않아서 그런 건가 싶기도 합니다,, 디자인 패턴 중 layout component의 장점으로 트리 깊이가 낮아진다랑 리렌더링 되지 않는다고 하셨는데, 왜 그런 건지 궁금증이 생겼습니다 이에 대해서 좀 더 공부해본 뒤 댓글에 추가해보겠습니다 :) 디자인 패턴에 대해 알아가다 보니 코드 재사용에 대한 시야가 조금씩 트이는 거 같다는 느낌이 들었습니다 아티클 작성하느라 고생하셨습니다! 감사합니다 :)

답글 달기
comment-user-thumbnail
2023년 11월 16일

아티클 잘 읽었습니다! 평소에 컴포넌트 설계에 대한 고민을 많이 하는 편인데, 제가 산발적으로 떠올렸던 기준들이 SOLID라는 원칙으로 이렇게 정의되어있는줄 몰랐네요!! 불명확했던 저의 의견과 판단을 원칙에 의거하여 더 확실히 확립시킬 수 있어서 좋은 기회였습니다 ㅎㅎ
또, 디자인 패턴에 대해서는 배경지식이 전무후무한 만큼 Provider 패턴의 작동 방식도 전혀 몰랐는데, 아티클을 읽으면서 처음 배우게 되면서 동시에 큰 생각 없이 양식에 맞춰서 쓰기만 했던 ThemeProvider가 여기서 나온거구나! 이런 이유와 원리로 만들어진거구나! 라는걸 깨달을 수 있었네요!

저희가 지난 세미나 생각과제로 컴포넌트 분리의 기준에 대해 고민해볼 수 있는 기회가 주어졌었는데, 그런 과제를 먼저 가진 뒤에 이 주제의 아티클을 읽으니 훨씬 잘 이해하고 공감하면서 읽을 수 있었습니다 💚

답글 달기
comment-user-thumbnail
2023년 11월 16일

✨썸네일✨까지,,정성이 어마무시하네요!! 좋은 아티클 잘 읽었습니다.
일반적인 인터페이스 설계에서 도메인과 강력하게 연결되어있는 도메인 맥락을 제거해야 하는 부분을 이전의 컴포넌트 설계 다른 아티클을 보면서 이해가 와닿지 않았는데 예시 코드를 보고 명확하게 이해할 수 있었던 것 같습니다. 즉, 처음 코드 설계를 독립적인 작은 컴포넌트부터 바라보는 방식을 통해 추상화하는 능력을 성장 시키는 것이 꽤나 효율적인 코드를 위해서 필요하다는 것을 깨달았습니다! ✍️

디자인 패턴 부분에서 이제 Provider Pattern 을 인상깊게 읽었던 것 같습니다. 생각과제였던 props drilling 에 대해 한 층 더 깊이있게 이해할 수 있었습니다! 추가적으로 제 응애이슈로 이해를 위해 덧붙여보자면 ..,, React.createContext() 이 컨텍스트를 사용하면 구성 요소 트리의 각 수준을 통해 수동으로 소품을 전달하지 않고도 구성 요소 간에 데이터를 공유할 수 있는 역할이라 합니다. 또한
구성 요소 트리 상단에 있는 DataContext.ProvideruseContext 후크를 통해 그 아래에 있는 모든 하위 구성 요소가 데이터에 액세스할 수 있도록 하고,, -> 이는 props 드릴링을 방지하고 애플리케이션의 여러 부분에서 공통 데이터를 효율적으로 공유하는 방법임을 이해하게 되었습니다 ✍️✍️

답글 달기
comment-user-thumbnail
2023년 11월 16일

컴포넌트 설계와 디자인 패턴에 대한 아티클 잘 읽었습니다 !

항상 당연하게 컴포넌트를 분리하고 각 컴포넌트에는 하나의 책임만 담당하도록 구현하고 했었는데, 제가 컴포넌트 설계 시 사용했던 방식이 하나의 원칙으로 정의되어 있었다니 신기하고 재밌네요 ..!
개인적으로 디자인 패턴에 대해서는 잘 알지 못해서 이 또한 인지하고 있지 못했는데요, 아티클을 읽다보니 제가 기능 구현 시 고려했던 부분이나 실제로 적용했던 부분들은 물론이고, 기존에 적용했던 방식외에도 많은 이론과 예시가 정리되어 있어서 흥미롭게 읽을 수 있었어요!

지난 앱잼을 진행하며 아무렇게나 코드를 짜는 것이 아닌, 미리 설계하고 고민한 뒤에 코드를 짜는 것의 중요성에 대해 많이 배울 수 있었어요 ..! 예를 들면 어떤 부분을 추상화할 것인지, 해당 코드를 정말 추상화를 할 필요가 있는지, 이 부분은 왜 이렇게 구현했는지 등 끊임없이 고민했던 것 같아요.
그 과정에서 나름의 기준을 잡을 수 있었는데요, 하나의 컴포넌트에는 여러 기능이 들어가지 않도록 하고 컴포넌트 분리 시에는 역할 중심으로 분리해주고자 했어요! 그게 가장 가독성 높고 추후 유지보수, 리팩토링을 진행할 때 유리하다고 느껴지더라구요 ..! 그렇지만 언제나 그렇듯, 앞으로는 더욱 다양한 방식을 접해보고 프로젝트의 특성에 맞게 선택해나가려고 합니당 !

꼼꼼하게 정리해주신 아티클 덕분에 디자인 패턴에 대해 새롭게 알아갈 수 있었습니다! 특히 나는 어떤 방식으로 컴포넌트를 설계했나에 대해 깊게 생각해볼 수 있는 시간이 되었어요..! 너무너무 수고 많으셨습니당 :-)

답글 달기
comment-user-thumbnail
2023년 11월 16일

다양한 리액트 디자인 패턴을 알게되는 좋은 시간이었습니다.
저는 presentational and container 패턴을 옛날부터 관심있게 봤는데 결론적으로 이 패턴을 만든 당사자가 지금은 사용하지 않길 권하고 있지만 뷰와 로직을 분리하여 컴포넌트가 조금 더 자신에 한가지 책임에 집중할 수 있게 한다는 접근이 굉장히 흥미로웠습니다. 최근 커스텀 훅을 공부하면서 커스텀훅을 사용하는 것이 단순히 반복되는 코드를 사용하는 것 뿐만 아니라 뷰와 로직을 분리해서 더 나은 가독성과 유지보수성을 제공한다는 사실을 알게 됐습니다.

또 제어 컴포넌트와 비제어 컴포넌트에 대해 알아보았는데 데이터와 UI에서 입력한 값이 항상 동기화되고 있는 컴포넌트고 비제어 컴포넌트는 어떠한 동작 (form 을 제출하는 버튼)이 실행되었을 때 비로소 데이터와 UI가 동기화되는 컴포넌트를 말한다고 합니다.

답글 달기
comment-user-thumbnail
2023년 11월 16일

리액트를 처음 배우는 입장에서 이런 아티클 너무 소중해요.. 예시와 함께 보니까 더 이해하기 쉬웠던거 같습니다! 컴포넌트를 어떻게 어느정도까지 쪼개는지도 중요하지만, 쪼갠 컴포넌트를 어떻게 유기적으로 연결해서 사용하는지도 중요하다는 것을 다시 한번 깨닫게 되었어요!

디자인 패턴 부분을 읽으면서 HOC 패턴이 가장 인상 깊었던 것 같습니다! 이전 과제를 할 때 여러개의 컴포넌트들에 동일한 역할을 하면서 반복되는 코드가 많이 존재한다는걸 저도 알고 있었지만, 이를 어떻게 분리해서 구성해야 할지는 알아내지 못했었거든요.. 이번에 아티클을 읽으면서 HOC 패턴으로 해결할 수 있을 것 같다는 생각이 들었습니다! 동일한 로직은 HOC로 처리하고, 컴포넌트들은 서로 다른 패턴에 집중해 더 효율적인 코드 작성이 가능하다는 것을 새로 배워갑니다! HOC를 정리하자면 컴포넌트를 인자로 받아 새로운 컴포넌트로 변환해서 반환하는 함수!라고 할 수 있을 것 같네요!

좋은 아티클 너무 감사드려요! 🫶

답글 달기
comment-user-thumbnail
2023년 11월 16일

좋은 아티클 감사합니닷!! 디자인 패턴 아이콘 화투인거 넘 귀엽네용 ㅎ.ㅎ
디자인패턴에는 정말 다양한 종류가 있다는 것을 새삼 깨닫고 ,, 실습때 잘 적용해보며 익숙해지고싶어요!! 필요에따라 디자인패턴을 적재적소에 잘 활용하는 날이 오기를 ,, 말씀해주신 디자인패턴들
1. Layout Components
2. HOC Pattern
3. Provider Pattern
4. Compound Component Pattern
5. Container/Presentational Pattern
6. uncontrolled component
이 친구들 다 덕분에 처음 알게 되어서 넘 유익했습니닷!!
Layout Components를 읽다가, 컴포넌트 트리 깊이가 낮아진다. && 레이아웃 컴포넌트는 무상태이므로, 리렌더링되지 않는다.라는 의미가 낯설어서 좀 더 찾아보았는데요,
우선 컴포넌트 트리 깊이가 낮아진다.는 즉 props를 전달하는 경로가 짧아진다 정도로 이해했습니닷! props 전달 경로가 짧아지니 트리 깊이도 낮아지는? 그런 느낌스,,
그리고 레이아웃 컴포넌트는 무상태이므로, 리렌더링되지 않는다. 에서 '무상태'는 stateless이고, 이때 state가 우리가 리액트에서 매번 사용하는 그 state였습니다 ,, 저는 이게 굉장히 새삼스러웠어요 하핫 한국말로 바꿔놓으니 넘 초면이었던,, 그래서 결론은!! 레이아웃 컴포넌트는 stateful한 컴포넌트들을 부모->자식 방향으로 전달만 받을뿐이라, 리렌더링 되지 않는다고 합니다! layout component가 호출될 떄, 자식 컴포넌트들이 children으로 전달되고, 해당 컴포넌트로 props로서 전달된 children 객체들의 props 객체 자체가 그대로이기 때문이라네요,, 꽤나 어렵네용 헝헝

제가 넘넘 궁금해왔던 디자인패턴!!! 실습이 넘 기대됩니닷!!!

답글 달기
comment-user-thumbnail
2023년 11월 16일

여러가지 개념들에 대한 설명도 있어서 아티클을 이해하기 수월했어요! 아직 내용들을 완전히 이해했다고 말할 수는 없지만 글을 읽으면서 이번과제에서 적용해보면 좋을 디자인 패턴들이 몇개 보였어요!! 그리고 제가 짰던 코드가 어떤 디자인 패턴이었는지 그 이름도 알 수 있었어요!! Container/Presentational Pattern을 저는 주로 사용하고 있었다는 것을 처음 알게되었습니다..🤓 또 앞으로 구조를 짤 때 고려할 수 있는 선택지가 더 많아진 것 같습니다. 좋은 아티클 감사합니다!

답글 달기
comment-user-thumbnail
2023년 11월 16일

작은 단위의 개념 정의부터 찬찬히 설명해주셔서 처음 보는 개념들을 이해하는데 정말 도움이 많이 되었습니다!! ㅎㅎㅎ
디자인패턴에 대한 이야기를 읽다보니 공통컴포넌트에 대한 생각이 많이 들었어요. 지난 앱잼에서 공통컴포넌트를 나름 적극적으로 활용해보려고 했는데, 생각보다 디자인 베리에이션이 너무 다양하고 이에 전부 만족시키기는 너무 까다롭고, 결국 딱 하나 다른 페이지를 위해서 공통 컴포넌트의 prop을 하나더 늘리고.. 이런 일들이 일어나니까 아 공통 컴포넌트가 마냥 좋은 것만은 아니구나.. 싶더라구요. 또다양한 디자인 패턴에 대해 알아가면서 흠 말로는 쉬운데 실제로 적용하기는 참 어렵겠다라는 생각도 들구요...
아티클에서 Compound Component라는 개념에 대해 좀 더 알아보고 싶어서 찾아봤는데, 말그대로 컴포넌트의 단위를 세세히 쪼개서 서로 합성하여 만드는 것으로 이해했습니다. 처음에는 그러면 prop drilling이 오히려 늘어나는거 아닌가..? 라는 생각이 들었는데 다시 이해해보니 부모 컴포넌트에서 prop을 넘겨주는게 아니라 하위 컴포넌트가 각자의 prop을 알아서(?) 붙들고있는 형태를 의미하는 듯 합니다. 그래서 책임 소재를 명확히 분리할 수 있고 조각조각 떼었다 붙였다 하기 좋지만, 오히려 유연성이 너무 높아져서 필요없는 컴포넌트가 존재하거나 순서가 잘못되는 등 예상치 못한 문제가 발생할 수도 있다고 하네요.뭐든 적당히가 중요하다는 느낌인데.. 컴포넌트 설계는 많이 시도해보고 변경해보면서 프로젝트에 알맞는 형태를 만들어나가야 할 것 같습니다!

답글 달기
comment-user-thumbnail
2023년 11월 16일

컴포넌트 설걔와 디자인 패턴에 대해 정리해 주신 글 잘 읽었습니다! 썸네일까지.. 너무 완벽한데요 🙊..!

컴포넌트를 어느 수준에서, 어떻게 설계할지에 대해 항상 고민이 많았지만 명확한 답이 없어 참 어렵다고 생각했었는데, 이렇게 SOLID 원칙, 디자인 패턴에 대입하여 정리한 글을 통해 그동한 제가 했던 고민들에 대한 실마리를 찾을수 있었어요!

디자인 패턴이 어렵고 생소한 개념이라고 생각했었는데 이렇게 사례 코드까지 다뤄주시니까 더 이해가 잘 갔던것 같아요!! 특히 데이터 중심으로 나누는 디자인 패턴이 인상 깊어서 관련 내용을 더 찾아봤는데, Headless component에 대한 내용을 찾아볼수 있었습니다!
Headless component는 데이터와 UI를 분리해서 디자인 시스템을 사용하려는 환경을 고려하지 않아도 쓸 수 있도록 설계된 디자인 시스템이라고 합니다!
함께 알면 좋을것 같아 공유합니다!

아티클 쓰느라 수고 너무 많으셨어요~!

1개의 답글