수요일, 4월 24, 2024

재사용 가능한 반응 구성 요소를 만들기 위한 실용적인 가이드

Must read

Ae Dong-Yul
Ae Dong-Yul
"트위터를 통해 다양한 주제에 대한 생각을 나누는 아 동율은 정신적으로 깊이 있습니다. 그는 맥주를 사랑하지만, 때로는 그의 무관심함이 돋보입니다. 그러나 그의 음악에 대한 열정은 누구보다도 진실합니다."

React는 세계에서 가장 인기 있고 가장 많이 사용되는 프론트 엔드 프레임워크 중 하나이지만, 많은 개발자는 재사용성을 개선하기 위한 리팩토링과 관련하여 여전히 어려움을 겪고 있습니다. React 애플리케이션 전체에서 동일한 코드 스니펫을 반복하는 자신을 발견했다면 올바른 기사를 찾은 것입니다.

이 튜토리얼에서는 재사용 가능한 React 구성 요소를 빌드할 시간임을 나타내는 가장 일반적인 세 ​​가지 지표에 대해 알아봅니다. 그런 다음 재사용 가능한 레이아웃과 몇 가지 흥미로운 React 후크를 구축하여 몇 가지 실습 데모를 살펴보겠습니다.

다 읽고 나면 스스로 이해할 수 있을 것이다. 언제 재사용 가능한 React 컴포넌트를 만드는 것이 편리하고, 어떻게 해보자

이 기사는 React 및 React hooks에 대한 기본 지식을 가정합니다. 이러한 주제에 대해 알고 싶다면 “React 시작하기“수동 및”후크의 상호 작용에 대한 유도“.

재사용 가능한 반응 성분에 대한 상위 3가지 표시

먼저 몇 가지 포인터를 살펴보겠습니다. 언제 그렇게 하고 싶을 수도 있습니다.

동일한 CSS 스타일의 래퍼 재귀 생성

재사용 가능한 구성 요소를 생성할 때를 아는 가장 좋아하는 표시는 동일한 CSS 스타일을 자주 사용한다는 것입니다. 이제 “잠깐만요. 동일한 CSS 스타일을 공유하는 요소에 동일한 클래스 이름을 할당하는 것은 어떨까요?”라고 생각할 수 있습니다. 당신이 절대적으로 옳습니다. 일부 요소가 동일한 패턴에서 다른 구성 요소를 공유할 때마다 재사용 가능한 구성 요소를 만드는 것은 좋은 생각이 아닙니다. 사실 불필요한 합병증을 유발할 수 있습니다. 따라서 한 가지 더 질문해야 합니다. 이러한 요소가 일반적인 디자인입니까? 커버?

예를 들어 다음 로그인 및 가입 페이지를 고려하십시오.


import './common.css';

function Login() {
  return (
    <div className='wrapper'>
      <main>
        {...}
      </main>
      <footer className='footer'>
        {...}
      </footer>
    </div>
  );
}

import './common.css';

function Signup() {
  return (
    <div className='wrapper'>
      <main>
        {...}
      </main>
      <footer className='footer'>
        {...}
      </footer>
    </div>
  );
}

동일한 스타일이 컨테이너( . 파일 <div> 구성 요소) 및 각 구성 요소의 바닥글. 따라서 이 경우 두 개의 재사용 가능한 구성 요소를 만들 수 있습니다. <Wrapper /> 그리고 <Footer /> – 그리고 소품으로 아이들을 전달하십시오. 예를 들어 로그인 구성 요소는 다음과 같이 다시 빌드할 수 있습니다.


import Footer from "./Footer.js";

function Login() {
  return (
    <Wrapper main={{...}} footer={<Footer />} />
  );
} 

결과적으로 더 이상 가져올 필요가 없습니다. common.css 여러 페이지에서 또는 동일한 것을 생성 <div> 요소는 모든 것을 감쌉니다.

이벤트 리스너의 빈번한 사용

이벤트 리스너를 요소에 연결하려면 내부에서 처리할 수 있습니다. useEffect() 그를 좋아하다:


import { useEffect } from 'react';

function App() {
  const handleKeydown = () => {
    alert('key is pressed.');
  }

  useEffect(() => {
    document.addEventListener('keydown', handleKeydown);
    return () => {
      document.removeEventListener('keydown', handleKeydown);
    }
  }, []);

  return (...);
}

또는 다음 버튼 구성 요소에 표시된 것처럼 JSX 내부에서 직접 수행할 수 있습니다.


function Button() {
  return (
    <button type="button" onClick={() => { alert('Hi!')}}>
      Click me!
    </button>
  );
};

이벤트 리스너를 추가하려는 경우 document 또는 window, 첫 번째 방법을 따라야 합니다. 그러나 이미 깨달았을 수도 있지만 첫 번째 방법은 다음을 포함하는 더 많은 코드가 필요합니다. useEffect()그리고 addEventListener() 그리고 removeEventListener(). 따라서 이러한 경우 사용자 정의 후크를 생성하면 구성 요소를 더 간결하게 만들 수 있습니다.

이벤트 리스너를 사용하는 네 가지 시나리오가 있습니다.

  • 동일한 이벤트 리스너, 동일한 이벤트 핸들러
  • 동일한 이벤트 리스너, 다른 이벤트 핸들러
  • 다른 이벤트 리스너, 동일한 이벤트 핸들러
  • 다른 이벤트 리스너, 다른 이벤트 핸들러

첫 번째 시나리오에서는 이벤트 리스너와 이벤트 핸들러가 모두 정의된 후크를 만들 수 있습니다. 다음 후크를 고려하십시오.


import { useEffect } from 'react';

export default function useKeydown() {
  const handleKeydown = () => {
    alert('key is pressed.');
  }

  useEffect(() => {
    document.addEventListener('keydown', handleKeydown);
    return () => {
      document.removeEventListener('keydown', handleKeydown);
    }
  }, []);
};

그런 다음 다음과 같이 모든 구성 요소에서 이 후크를 사용할 수 있습니다.


import useKeydown from './useKeydown.js';

function App() {
  useKeydown();
  return (...);
};

다른 세 가지 시나리오의 경우 이벤트와 이벤트 처리 기능을 props로 수신하는 후크를 만드는 것이 좋습니다. 예를 들어 주문하겠습니다. keydown 그리고 handleKeydown 내 사용자 정의 후크에 대한 브래킷으로. 다음 후크를 고려하십시오.


import { useEffect } from 'react';

export default function useEventListener({ event, handler} ) {
  useEffect(() => {
    document.addEventListener(event, props.handler);
    return () => {
      document.removeEventListener(event, props.handler);
    }
  }, []);
};

그런 다음 다음과 같이 모든 구성 요소에서 이 후크를 사용할 수 있습니다.


import useEventListener from './useEventListener.js';

function App() {
  const handleKeydown = () => {
    alert('key is pressed.');
  }
  useEventListener('keydown', handleKeydown);
  return (...);
};

동일한 GraphQL 스크립트의 반복 사용

GraphQL 코드를 재사용 가능하게 만들 때 태그를 찾을 필요가 없습니다. 복잡한 응용 프로그램의 경우 쿼리 또는 변형을 위한 GraphQL 스크립트는 주문할 속성이 많기 때문에 쉽게 30-50줄의 코드를 사용합니다. 같은 GraphQL 스크립트를 한두 번 이상 사용한다면 커스텀 링크를 할 가치가 있다고 생각합니다.

다음 예를 고려하십시오.

import { gql, useQuery } from "@apollo/react-hooks";

const GET_POSTS = gql`
  query getPosts {
    getPosts {
    user {
      id
      name
      ...
      }
      emojis {
         id
         ...
      }
      ...
  }
`;

const { data, loading, error } = useQuery(GET_POSTS, {
  fetchPolicy: "network-only"
});

백엔드에서 게시물을 요청하는 모든 페이지에서 이 코드를 반복하는 대신 이 API에 대한 React 후크를 만들어야 합니다.

import { gql, useQuery } from "@apollo/react-hooks";

function useGetPosts() {
  const GET_POSTS = gql`{...}`;
  const { data, loading, error } = useQuery(GET_POSTS, {
    fetchPolicy: "network-only"
  });
  return [data];
}

const Test = () => {
  const [data] = useGetPosts();
  return (
    <div>{data?.map(post => <h1>{post.text}</h1>)}</div>
  );
};

세 가지 재사용 가능한 반응 구성 요소 구축

이제 우리는 다음과 같은 몇 가지 일반적인 징후를 보았습니다. 언제 상호 작용 응용 프로그램 전체에서 공유할 수 있는 새 구성 요소를 만들기 위해 이 지식을 실제로 적용하고 세 가지 실습 데모를 작성해 보겠습니다.

1. 기획 구성요소

React는 복잡한 웹 애플리케이션을 빌드하는 데 자주 사용됩니다. 이것은 React에서 많은 수의 페이지를 개발해야 한다는 것을 의미하며, 애플리케이션의 각 페이지가 다른 레이아웃을 가질지 의심됩니다. 예를 들어, 30페이지 웹 앱은 일반적으로 5개 미만의 다른 레이아웃을 사용합니다. 따라서 다양한 페이지에서 사용할 수 있는 유연하고 재사용 가능한 레이아웃을 구축하는 것이 필요합니다. 이렇게 하면 매우 많은 수의 코드 행을 절약할 수 있으므로 엄청난 시간이 절약됩니다.

다음 기능적 React 구성 요소를 고려하십시오.


import React from "react";
import style from "./Feed.module.css";

export default function Feed() {
  return (
    <div className={style.FeedContainer}>
      <header className={style.FeedHeader}>Header</header>
      <main className={style.FeedMain}>
        {
          <div className={style.ItemList}>
            {itemData.map((item, idx) => (
              <div key={idx} className={style.Item}>
                {item}
              </div>
            ))}
          </div>
        }
      </main>
      <footer className={style.FeedFooter}>Footer</footer>
    </div>
  );
}

const itemData = [1, 2, 3, 4, 5];

이것은 확장자가 있는 일반적인 웹 페이지입니다. <header>, NS <main> 그리고 <footer>. 이와 같은 웹 페이지가 30개 더 있으면 HTML 태그를 반복적으로 입력하고 같은 스타일을 반복적으로 적용하는 데 쉽게 지칠 것입니다.

또는 다음을 수신하는 레이아웃 구성 요소를 만들 수 있습니다. <header>그리고 <main> 그리고 <footer> 다음 코드에서와 같이 소품으로:


import React from "react";
import style from "./Layout.module.css";
import PropTypes from "prop-types";

export default function Layout({ header, main, footer }) {
  return (
    <div className={style.Container}>
      <header className={style.Header}>{header}</header>
      <main className={style.Main}>{main}</main>
      <footer className={style.Footer}>{footer}</footer>
    </div>
  );
}

Layout.propTypes = {
  main: PropTypes.element.isRequired,
  header: PropTypes.element,
  footer: PropTypes.element
};

이 구성 요소는 필요하지 않습니다 <header> 그리고 <footer>. 따라서 머리글이나 바닥글이 있는지 여부에 관계없이 페이지에 이 동일한 레이아웃을 사용할 수 있습니다.

이 레이아웃 구성 요소를 사용하면 피드 페이지를 더 복잡한 코드 블록으로 바꿀 수 있습니다.


import React from "react";
import Layout from "./Layout";
import style from "./Feed.module.css";

export default function Feed() {
  return (
    <Layout
      header={<div className={style.FeedHeader}>Header</div>}
      main={
        <div className={style.ItemList}>
          {itemData.map((item, idx) => (
            <div key={idx} className={style.Item}>
              {item}
            </div>
          ))}
        </div>
      }
      footer={<div className={style.FeedFooter}>Footer</div>}
    />
  );
}

const itemData = [1, 2, 3, 4, 5];

고정 요소가 있는 레이아웃 생성을 위한 전문가 팁

많은 개발자가 .파일을 사용하는 경향이 있습니다. position: fixed 또는 position: absolute 뷰포트 상단에 머리글을 붙여넣거나 하단에 바닥글을 붙여넣고 싶을 때. 그러나 레이아웃의 경우 이를 피해야 합니다.

레이아웃 요소는 전달된 소품의 원래 요소이므로 레이아웃 요소의 스타일을 가능한 한 단순하게 유지하고 싶습니다. <header>그리고 <main>, 또는 <footer> 의도 한대로. 그래서 추천하는 앱은 position: fixed 그리고 display: flex 기획 및 준비의 외부 요소에 overflow-y: scroll 나에게 <main> 아이템.

다음은 예입니다.


.Container {
  
  display: flex;
  flex-direction: column;

  
  width: 100%;
  height: 100%;

  
  overflow: hidden;
  position: fixed;
}

.Main {
  
  width: 100%;
  height: 100%;

  
  overflow-y: scroll;
}

이제 피드 페이지에 몇 가지 스타일을 적용하고 생성한 내용을 살펴보겠습니다.


.FeedHeader {
  
  height: 70px;

  
  background-color: teal;
  color: beige;
}

.FeedFooter {
  
  height: 70px;

  
  background-color: beige;
  color: teal;
}

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

.Item {
  
  height: 300px;

  
  color: teal;
}

.FeedHeader,
.FeedFooter,
.Item {
  
  display: flex;
  justify-content: center;
  align-items: center;

  
  border: 1px solid teal;

  
  font-size: 35px;
}

고정된 머리글 및 바닥글 표시

다음은 실행 중인 코드입니다.

이것은 데스크탑 화면에서 보이는 것입니다.

모바일 화면에서는 이렇게 보입니다.

모바일_레이아웃

이 레이아웃은 iOS 기기에서도 의도한 대로 작동합니다! iOS는 웹 앱 개발에 예상치 못한 위치 관련 문제를 일으키는 것으로 악명이 높습니다.

2. 이벤트 리스너

종종 동일한 이벤트 리스너가 웹 애플리케이션에서 두 번 이상 사용됩니다. 이런 경우 커스텀 React hook을 만드는 것이 좋습니다. 파일을 개발하여 수행하는 방법을 알아봅시다. useScrollSaver 페이지에서 사용자의 장치 스크롤 위치를 저장하는 후크 – 사용자가 맨 위에서 다시 스크롤할 필요가 없습니다. 이 후크는 게시물 및 댓글과 같이 많은 수의 항목이 나열되는 웹 페이지에 유용합니다. 스크롤을 저장하지 않고 Facebook, Instagram 및 Twitter의 피드 페이지를 상상해 보십시오.

다음 코드를 분해해 보겠습니다.


import { useEffect } from "react";

export default function useScrollSaver(scrollableDiv, pageUrl) {
  
  const handleScroll = () => {
    sessionStorage.setItem(
      `${pageUrl}-scrollPosition`,
      scrollableDiv.current.scrollTop.toString()
    );
  };
  useEffect(() => {
    if (scrollableDiv.current) {
      const scrollableBody = scrollableDiv.current;
      scrollableBody.addEventListener("scroll", handleScroll);
      return function cleanup() {
        scrollableBody.removeEventListener("scroll", handleScroll);
      };
    }
  }, [scrollableDiv, pageUrl]);

  
  useEffect(() => {
    if (
      scrollableDiv.current &&
      sessionStorage.getItem(`${pageUrl}-scrollPosition`)
    ) {
      const prevScrollPos = Number(
        sessionStorage.getItem(`${pageUrl}-scrollPosition`)
      );
      scrollableDiv.current.scrollTop = prevScrollPos;
    }
  }, [scrollableDiv, pageUrl]);
}

파일임을 알 수 있습니다 useScrollSaver 후크는 두 가지 요소를 수신해야 합니다. scrollableDiv, 파일처럼 스크롤 가능한 컨테이너여야 합니다. <main> 위 레이아웃의 컨테이너 및 pageUrl, 여러 페이지의 스크롤 위치를 저장할 수 있도록 페이지 식별자로 사용됩니다.

1단계: 스크롤 위치 저장

우선 스크롤 가능한 컨테이너에 ‘scroll’ 이벤트 리스너를 바인딩해야 합니다.

const scrollableBody = scrollableDiv.current;
scrollableBody.addEventListener("scroll", handleScroll);
return function cleanup() {
  scrollableBody.removeEventListener("scroll", handleScroll);
};

이제 매번 scrollableDiv 함수라고 하는 사용자가 전달합니다. handleScroll 실행됩니다. 이 기능에서는 둘 중 하나를 사용해야 합니다. localStorage 또는 sessionStorage 스크롤 위치를 저장합니다. 차이점은 데이터가 localStorage 데이터가 있는 동안 만료되지 않습니다. sessionStorage 페이지 세션이 종료되면 지워집니다. 당신이 사용할 수있는 setItem(id: string, value: string) 임의의 저장소에 데이터를 저장하려면:

const handleScroll = () => {
  sessionStorage.setItem(
    `${pageUrl}-scrollPosition`,
    scrolledDiv.current.scrollTop.toString()
  );
};

2단계: 스크롤 위치 복원

사용자가 웹 페이지로 돌아오면 사용자는 이전 스크롤 위치(있는 경우)로 이동해야 합니다. 이 위치 데이터는 현재 . 형식으로 저장되어 있습니다. sessionStorage, 꺼내서 사용해야 합니다. 당신이 사용할 수있는 getItem(id: string) 저장소에서 데이터를 가져옵니다. 그 후, 당신은 단순히 조정해야 scroll-top 스크롤 가능한 컨테이너에서 다음 값을 얻었습니다.

const prevScrollPos = Number(
  sessionStorage.getItem(`${pageUrl}scrollPosition`)
);
scrollableDiv.current.scrollTop = prevScrollPos;

3단계: 사용 useScrollSaver 모든 웹 페이지에 대한 링크

이제 사용자 지정 후크 생성이 완료되었으므로 두 가지 필수 요소를 후크에 전달하기만 하면 원하는 모든 웹 페이지에서 후크를 사용할 수 있습니다. scrollableDiv 그리고 pageUrl. 로 돌아가자 Layout.js 그리고 거기에 후크를 사용하십시오. 이렇게 하면 이 레이아웃을 사용하는 모든 웹 페이지에서 스크롤링 클립보드를 즐길 수 있습니다.


import React, { useRef } from "react";
import style from "./Layout.module.css";
import PropTypes from "prop-types";
import useScrollSaver from "./useScrollSaver";

export default function Layout({ header, main, footer }) {
  const scrollableDiv = useRef(null);
  useScrollSaver(scrollableDiv, window.location.pathname);
  return (
    <div className={style.Container}>
      <header className={style.Header}>{header}</header>
      <main ref={scrollableDiv} className={style.Main}>
        {main}
      </main>
      <footer className={style.Footer}>{footer}</footer>
    </div>
  );
}

Scrollsaver 평가판

다음은 Sandbox에서 실행되는 코드입니다. 페이지를 스크롤한 다음 왼쪽 하단 모서리에 있는 화살표를 사용하여 앱을 새로고침하세요.

중단했던 바로 그 지점에서 자신을 찾을 수 있을 것입니다!

3. 쿼리/변이(GraphQL에만 해당)

내가 하는 것처럼 GraphQL을 React와 함께 사용하려면 GraphQL 쿼리 또는 변형을 위한 React 후크를 만들어 코드 기반을 더욱 줄일 수 있습니다.

GraphQL 쿼리를 실행하는 다음 예를 고려하십시오. getPosts():

import { gql, useQuery } from "@apollo/react-hooks";

const GET_POSTS = gql`
  query getPosts {
    getPosts {
    user {
      id
      name
      ...
      }
      emojis {
         id
         ...
      }
      ...
  }
`;

const { data, loading, error } = useQuery(GET_POSTS, {
  fetchPolicy: "network-only"
});

백엔드에서 요청할 속성이 점점 더 많아지면 GraphQL 스크립트가 더 많은 공간을 차지합니다. 따라서 GraphQL 스크립트를 반복하는 대신 useQuery 쿼리를 실행해야 할 때마다 getPosts(), 다음 React 후크를 만들 수 있습니다.


import { gql, useQuery } from "@apollo/react-hooks";

export default function useGetPosts() {
  const GET_POSTS = gql`
  query getPosts {
    getPosts {
    user {
      id
      name
      ...
      }
      emojis {
         id
         ...
      }
      ...
  }
  `;

  const { data, loading, error } = useQuery(GET_POSTS, {
    fetchPolicy: "network-only"
  });

  return [data, loading, error];
}

그 후에 파일을 사용할 수 있습니다. useGetPosts() 다음과 같이 연결합니다.


import React from "react";
import Layout from "./Layout";
import style from "./Feed.module.css";
import useGetPosts from "./useGetPosts.js";

export default function Feed() {
  const [data, loading, error] = useGetPosts();
  return (
    <Layout
      header={<div className={style.FeedHeader}>Header</div>}
      main={
        <div className={style.ItemList}>
          {data?.getPosts.map((item, idx) => (
            <div key={idx} className={style.Item}>
              {item}
            </div>
          ))}
        </div>
      }
      footer={<div className={style.FeedFooter}>Footer</div>}
    />
  );
}

결론

이 기사에서 나는 React의 재사용 가능한 컴포넌트에 대한 세 가지 가장 인기 있는 포인터와 세 가지 가장 인기 있는 사용 사례를 배웠습니다. 이제 지식이 있습니다. 언제 재사용 가능한 React 구성 요소를 만들고 어떻게 이 작업을 쉽고 전문적으로 수행합니다. 곧 코드 라인을 React 구성 요소 또는 재사용 가능한 고급 훅으로 리팩토링하는 것을 즐기는 자신을 발견하게 될 것입니다. 이러한 재구축 기술을 사용하여 당사 개발 팀은 점토 그는 우리의 칼날 베이스를 감당할 수 있는 크기로 줄일 수 있었습니다. 당신도 할 수 있기를 바랍니다!

Latest article