React inc.hooks의 컨테이너/뷰 패턴

Ornio는 깨끗하고 읽기 쉬운 코드를 좋아합니다. 이를 달성하기 위해 우리는 코드를 가능한 한 강력하게 만드는 새로운 기술과 방법을 끊임없이 찾고 있습니다.

몇 년 전에 우리는 Ember에서 React로 전환했습니다. 처음에 React는 모든 것이 이치에 맞고 아무것도 되지 않는 이상한 미개척 영역처럼 보였습니다.
질문이 생기기 시작했습니다. 구성 요소를 만드는 가장 좋은 방법은 무엇입니까? 언제 만들까요? 가능한 한 재사용 가능하게 유지하는 방법은 무엇입니까?

답을 찾다가 프레젠테이션 및 컨테이너 구성 요소에 대한 Dan Abramov의 이 기사를 발견했습니다. 그것을 읽은 후 나는 그것이 나타내는 아이디어에 즉시 사랑에 빠졌습니다.

컨테이너/뷰 패턴이란 무엇입니까?



컨테이너/뷰 패턴(Presentational/Container, Thick/thin, Smart/Dumb이라고도 함)은 구성 요소를 상태 저장 논리 및 데이터 가져오기를 담당하는 '컨테이너'와 데이터 프레젠테이션을 담당하는 '뷰'로 분할하는 기술입니다. .

이 패턴을 올바르게 사용하면 React 애플리케이션에서 엄청난 확장 옵션을 사용할 수 있습니다. 뷰의 로직을 깨끗하게 유지함으로써 원하는 만큼 뷰를 재사용할 수 있습니다. 그러나 이제 우리의 모든 논리가 컨테이너 안에 포함되어 있으므로 더 빠르고 쉽게 디버깅할 수 있습니다.

다음은 이 패턴을 구현하는 방법에 대한 간단한 예입니다.

뷰 구성 요소를 만드는 것으로 시작하겠습니다. 우리의 경우 사용자의 프로필 사진, 이름, 위치, 성별 및 이메일을 보여주는 간단한 사용자 카드가 될 것입니다.

import style from "./Card.module.css";

const Card = ({ title, location, email, gender, image }) => (
  <section className={style.card}>
    <img
      className={style.cardImage}
      src={image}
      alt={title}
    />
    <div className={style.cardContent}>
      <h3 className={style.cardTitle}>{title}</h3>
      <span className={style.cardLocation}>{location}</span>
      <div className={style.cardContact}>
        <span className={style.cardMail}>{`email: ${email}`}</span>
        <span className={style.cardGender}>{`gender: ${gender}`}</span>
      </div>
    </div>
  </section>
);

export default Card;


이제 스타일을 추가하여 예쁘게 만들어 보겠습니다.

.card {
  display: flex;
  align-self: center;
  width: fit-content;
  background: #ffffff;
  box-shadow: 0px 2px 4px rgba(119, 140, 163, 0.06),
    0px 4px 6px rgba(119, 140, 163, 0.1);
  border-radius: 8px;
  padding: 24px;
  margin: 0 auto;
}

.cardImage {
  height: 80px;
  width: 80px;
  border-radius: 100px;
}

.cardContent {
  font-family: sans-serif;
  line-height: 0;
  margin-left: 20px;
}

.cardContact {
  display: flex;
  flex-direction: column;
}

.cardTitle {
  font-size: 20px;
  color: #112340;
  margin-bottom: 20px;
}

.cardLocation {
  font-size: 12px;
  color: #112340;
  margin-bottom: 22px;
  opacity: 0.85;
}

.cardMail,
.cardGender {
  font-size: 12px;
  color: #112340;
  margin-top: 15px;
  opacity: 0.65;
}


짜잔. 카드가 완성되어 사용할 준비가 되었습니다.



이제 마법이 일어나는 곳입니다. CardContainer라는 새 구성 요소를 만들 것입니다. 이 구성 요소 내부에서 논리가 발생합니다. 임의의 사용자 API에서 사용자를 가져와 카드에 데이터를 표시할 것입니다.

import { useState, useEffect } from "react";

import axios from "axios";

import Card from "@components/Card";

const CardContainer = () => {
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios("https://randomuser.me/api/");
      const user = result.data.results[0];
      setUserData({
        gender: user.gender,
        email: user.email,
        location: `${user.location.city}, ${user.location.country}`,
        title: `${user.name.title}. ${user.name.first} ${user.name.last}`,
        image: user.picture.thumbnail,
      });
    };

    fetchData();
  }, []);

  return (
    <Card
      title={userData?.title || "N/A"}
      location={userData?.location || "N/A"}
      email={userData?.email || "N/A"}
      gender={userData?.gender || "N/A"}
      image={userData?.image || ""}
    />
  );
};

export default CardContainer;


컨테이너의 모든 논리를 분리하여 볼 수 있듯이 뷰 구성 요소가 깨끗하고 원하는 만큼 재사용할 준비가 되었습니다.

React의 후크 소개



Dan의 블로그에서 후크 소개를 볼 수 있듯이 이와 같은 구성 요소를 패키징할 필요가 없습니다. 후크를 사용하면 내부의 논리를 격리한 다음 필요에 따라 호출할 수 있으므로 컨테이너의 필요성이 서서히 사라지고 있습니다.

그러나 Hooks는 훌륭하지만 모든 문제를 해결하지는 못하므로 이 접근 방식이 여전히 널리 사용되는 이유입니다.

먼저 컨테이너 논리를 useUserData라는 사용자 지정 후크로 이동해 보겠습니다.

import { useState, useEffect } from "react";

import axios from "axios";

export const useUserData = () => {
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios("https://randomuser.me/api/");
      const user = result.data.results[0];
      setUserData({
        gender: user.gender,
        email: user.email,
        location: `${user.location.city}, ${user.location.country}`,
        title: `${user.name.title}. ${user.name.first} ${user.name.last}`,
        image: user.picture.thumbnail,
      });
    };

    fetchData();
  }, []);

  return {
    gender: userData?.gender || "N/A",
    email: userData?.email || "N/A",
    location: userData?.location || "N/A",
    title: userData?.title || "N/A",
    image: userData?.image || "",
  };
};


좋아 보인다. 이제 우리의 논리는 컨테이너 대신 후크 안에 있습니다.
하지만 지금 어떻게 혼합합니까?
래퍼를 만들어 볼 수 있습니다.
그걸하자.

import { useUserData } from '@hooks/useUserData';

import Card from "@componets/Card";

const UserCardContainer = () => {
  const {
    title,
    location,
    email,
    gender,
    image,
  } = useUserData();

  return (
    <Card
      title={title}
      location={location}
      email={email}
      gender={gender}
      image={image}
    />
  );
};

export default UserCardContainer;


이제 이것은 또 다른 컨테이너가 아닙니까? 이렇게 하면 논리가 3개의 다른 파일로 분리되는 새로운 임의 분할이 생성됩니다.
나에게 이것은 정말 해키적인 방법이었고 내가 기대했던 것만 큼 깨끗하지 않았습니다.
나는 후크와 컨테이너/뷰 패턴에 대한 아이디어를 좋아했기 때문에 아직 포기할 준비가 되지 않았습니다.
인터넷으로!
온라인에서 파헤친 후 react-hooks-compose라는 라이브러리 형태의 솔루션을 찾았습니다.

이 라이브러리를 사용하면 컨테이너가 필요 없는 사용자 지정 후크로 뷰를 구성할 수 있습니다.

useUserData 후크 및 카드 구성 요소를 구성해 보겠습니다.

import composeHooks from "react-hooks-compose";
import { useUserData } from "@hooks/useUserData";
import Card from "@components/Card";
import CardContainer from "@containers/CardContainer"

// composing card with our hook
const ComposedCard = composeHooks({ useUserData })(Card);

const App = () => {
  return (
    <div className="app">
      <ComposedCard />
      <CardContainer />
    </div>
  );
};

export default App;


드디어 성공 🎉 🎉

개인적으로 나는 어떤 모양이나 형태의 컨테이너/뷰 패턴이 관심사를 분리하고 가능한 한 코드를 재사용할 수 있도록 유지하는 좋은 방법이라고 생각합니다.
Ornio는 이 접근 방식을 좋아하며 더 빠르게 확장하고 구성 요소를 훨씬 더 쉽게 구축하고 테스트할 수 있도록 도와주므로 계속 사용할 것입니다.

이 기사가 도움이 되었기를 바랍니다.

연결:
Dan's original post
react-hooks-compose
code

좋은 웹페이지 즐겨찾기