> 웹 프론트엔드 > JS 튜토리얼 > 실존적 React 질문과 완벽한 모달 대화 상자

실존적 React 질문과 완벽한 모달 대화 상자

Patricia Arquette
풀어 주다: 2025-01-03 03:44:41
원래의
652명이 탐색했습니다.

Existential React questions and a perfect Modal Dialog

React에서 가장 복잡한 것이 무엇이라고 생각하시나요? 다시 렌더링하시겠습니까? 문맥? 포털? 동시성?

아니요.

React에서 가장 어려운 부분은 주변의 React가 아닌 모든 것입니다. "위에 나열된 것들은 어떻게 작동합니까?"라는 질문에 대한 답변입니다. 간단합니다. 알고리즘을 따르고 메모만 하면 됩니다. 결과는 확실하며 항상 동일합니다(올바르게 추적한 경우). 그것은 단지 과학이고 사실일 뿐입니다.

그런데 "구성요소를 좋은 만드는 요소는 무엇입니까?"는 어떻습니까? 또는 "…(무언가)를 구현하는 올바른 방법은 무엇입니까?" 또는 "라이브러리를 사용해야 하나요, 아니면 나만의 솔루션을 구축해야 하나요?" 여기서 실제로 유일하게 정답은 "상황에 따라 다릅니다."입니다. 가장 도움이 되지 않는 방법입니다.

새 기사에서는 이보다 더 좋은 것을 찾고 싶었습니다. 그러나 이러한 유형의 질문에 대한 간단한 대답과 보편적인 해결책은 있을 수 없기 때문에 이 기사는 "이것이 답입니다. 항상 하십시오"라기보다는 내 사고 과정을 자세히 설명하는 것이 었습니다. 여전히 유용하길 바랍니다.

그렇다면 아이디어의 기능을 즉시 생산 가능한 솔루션으로 옮기려면 무엇이 필요합니까? 간단한 Modal Dialog를 구현해보고 살펴보겠습니다. 그것에 대해 무엇이 복잡할 수 있습니까? ?

1단계: 가장 간단한 솔루션으로 시작

잠재적인 솔루션을 탐색하고 추가 요구 사항을 수집하는 데 도움이 될 수 있는 가장 간단한 구현인 '스파이크'부터 시작해 보겠습니다. 나는 모달 대화 상자를 구현하고 있다는 것을 알고 있습니다. 다음과 같은 예쁜 디자인이 있다고 가정해 보겠습니다.

Existential React questions and a perfect Modal Dialog

대화상자는 기본적으로 버튼 등을 클릭할 때 나타나는 화면의 요소입니다. 이것이 바로 제가 시작하는 곳입니다.

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

상태, 클릭을 수신하는 버튼, 상태가 true일 때 표시되는 미래 대화상자입니다. 대화 상자에는 "닫기" 동작도 있어야 합니다.

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

또한 콘텐츠를 오버레이하고 클릭 시 모달이 사라지도록 하는 클릭 가능한 반투명 div인 "배경"도 있습니다.

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

모두 함께:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <>
          <div
            className="backdrop"
            onClick={() => setIsOpen(false)}
          ></div>
          <div className="dialog">
            <button
              className="close-button"
              onClick={() => setIsOpen(false)}
            >
              Close
            </button>
          </div>
        </>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

저도 보통 초반에 괜찮은 스타일을 추가하는 편이에요. 내가 구현하고 있는 기능이 예상한 것과 동일한 모양으로 화면에 나타나는 것을 보면 생각하는 데 도움이 됩니다. 또한 기능의 레이아웃을 알려줄 수 있으며, 이는 바로 이 대화 상자에서 발생하는 일입니다.

배경에 CSS를 빠르게 추가해 보겠습니다. 특별한 것은 아니며 전체 화면을 차지하는 위치: 고정이 있는 div의 반투명 배경입니다.

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

대화상자는 화면 중앙에 위치해야 하기 때문에 좀 더 흥미롭습니다. 물론 CSS에서 이를 달성하는 방법은 1001가지가 있지만 제가 가장 좋아하고 아마도 가장 간단한 방법은 다음과 같습니다.

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

레이아웃 제약에서 벗어나기 위해 "고정" 위치를 사용하고, 왼쪽과 위쪽에 50%를 추가하여 div를 중간으로 이동하고 다시 50% 변환합니다. 왼쪽과 위쪽은 화면을 기준으로 계산되고 변환은 div 자체의 너비/높이를 기준으로 계산되므로 결과적으로 화면의 너비나 너비에 관계없이 가운데에 바로 표시됩니다.

이 단계의 CSS 마지막 부분은 대화상자 자체와 '닫기' 버튼의 스타일을 적절하게 지정하는 것입니다. 여기에 복사하여 붙여넣지는 않을 것입니다. 실제 스타일은 그다지 중요하지 않습니다. 예를 살펴보세요.

2단계: 멈추고, 질문하고 생각하세요.

이제 기능을 대략적으로 구현했으므로 이제 이를 "실제"로 만들 차례입니다. 그러기 위해서는 우리가 여기서 해결하려는 것이 정확히 무엇인지, 누구를 위해 해결하려는 것인지 자세히 이해해야 합니다. 기술적으로 말하면 코딩 을 이해해야 하므로 이 단계는 1단계

인 경우가 많습니다.

이 대화 상자는 가능한 한 빨리 구현하고 투자자에게 한 번 보여주고 다시는 사용하지 않아야 하는 프로토타입의 일부인가요? 아니면 npm 및 오픈 소스에 게시할 일반 라이브러리의 일부일까요? 아니면 5,000명 규모의 조직에서 사용할 설계 시스템의 일부일까요? 아니면 소규모 3명으로 구성된 팀을 위한 내부 도구의 일부인가요? 아니면 TikTok 같은 회사에서 일하고 있는데 이 대화 상자가 모바일에서만 사용할 수 있는 웹 앱의 일부가 될까요? 아니면 정부 전용 앱을 작성하는 대행사에서 일하고 계시나요?

이러한 질문에 답하면 코딩과 관련해 다음에 무엇을 해야 할지 방향이 정해집니다.

한 번 써볼 만한 프로토타입이라면 이미 충분할지도 모르겠습니다.

라이브러리의 일부로 오픈 소스로 제공되려면 전 세계 모든 개발자가 사용하고 이해할 수 있는 매우 우수한 범용 API, 많은 테스트 및 우수한 문서가 필요합니다.

5,000명 규모의 조직 디자인 시스템의 일부인 대화는 조직의 디자인 지침을 준수해야 하며 저장소에 가져오는 외부 종속성이 제한될 수 있습니다. 따라서 npm install new-fancy-tool을 수행하는 대신 처음부터 많은 것을 구현해야 할 수도 있습니다.

정부를 위해 설립된 기관의 대화는 아마도 세상에서 가장 접근하기 쉽고 규정을 준수하는 대화여야 할 것입니다. 그렇지 않으면 기관이 정부 계약을 잃고 파산할 수도 있습니다.

등등.

이 기사의 목적에 따라 대화 상자는 매일 전 세계에서 수천 명의 사용자가 사용하는 기존 대형 상업 웹사이트를 새롭게 재설계하는 과정의 일부라고 가정해 보겠습니다. 재설계가 너무 진행 중이어서 제가 받은 대화가 포함된 유일한 디자인은 다음과 같습니다.

Existential React questions and a perfect Modal Dialog

나머지는 나중에 나오겠지만 디자이너들은 너무 바빠요. 또한 저는 단일 프로젝트를 위해 고용된 외부 계약자가 아닌 앞으로 웹사이트를 재설계하고 유지하는 상설팀의 일원입니다.

이 경우 이 사진만 가지고 우리 회사의 목표를 알면 합리적인 가정을 하고 대화의 90%를 구현할 수 있는 충분한 정보를 얻을 수 있습니다. 나머지 10%는 나중에 미세 조정할 수 있습니다.

위의 정보를 바탕으로 제가 내릴 수 있는 가정은 다음과 같습니다.

  • 기존 웹사이트에는 매일 전 세계 수천 명의 사용자가 있기 때문에 최소한 대화 상자가 대형 화면과 모바일 화면은 물론 다양한 브라우저에서도 작동하는지 확인해야 합니다. 이상적으로는 확실히 하기 위해 기존 분석을 확인해야 하지만 꽤 안전한 방법입니다.

  • 두 명 이상의 개발자가 이를 위한 코드를 작성하고 있으며 코드는 그대로 유지됩니다. 웹사이트는 규모가 크고 이미 수천 명의 사용자를 보유하고 있습니다. 투자자들에게는 빠른 프로토타입이 아닙니다. 따라서 코드를 읽을 수 있는지, API가 의미가 있는지, 사용 및 유지 관리가 가능한지, 눈에 띄는 문제가 없는지 확인해야 합니다.

  • 회사는 이미지와 웹사이트의 품질에 신경을 씁니다. 그렇지 않다면 왜 디자인을 다시 디자인하겠습니까? (여기서 긍정적인 의도를 가정해 볼까요?) 이는 일정 수준의 품질이 기대된다는 의미이며, 아직 현재 설계에 포함되지 않더라도 일반적인 시나리오와 극단적인 경우를 미리 생각하고 예상해야 합니다.

  • 많은 사용자가 웹사이트와 상호작용할 때 마우스만 사용하는 것은 아니라는 의미일 것입니다. 대화 상자는 키보드 상호 작용이나 스크린 리더와 같은 보조 기술을 통해서도 사용할 수 있어야 합니다.

  • 기존 코드베이스가 크다는 것은(재설계라는 점을 기억하세요!) 이 기능에 가져올 수 있는 외부 종속성에 제한이 있을 수 있음을 의미합니다. 모든 외부 종속성은 특히 크고 오래된 코드베이스에서 비용이 발생합니다. 글의 목적상 외부 라이브러리를 사용할 수 있다고 가정하지만 이에 대한 충분한 근거가 필요합니다.

  • 마지막으로 더 많은 디자인이 나오고 있기 때문에 디자인과 사용자 관점에서 어느 방향으로 갈 수 있을지 예상하고 코드가 이를 조기에 처리할 수 있는지 확인해야 합니다.

3단계: Modal Dialog API 강화

이제 요구 사항을 알고 합리적인 추측을 했으니 실제 대화 구성 요소를 만들 수 있습니다. 우선, 이 코드에서:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

대화 상자 부분을 재사용 가능한 구성 요소로 추출해야 합니다. 구현할 대화 상자 기반 기능이 많이 있을 것입니다.

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

대화상자에는 onClose 소품이 있습니다. "닫기" 버튼이나 배경화면을 클릭하면 상위 구성요소에 이를 알립니다. 그러면 상위 구성 요소는 여전히 상태를 유지하고 다음과 같이 대화 상자를 렌더링합니다.

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

이제 디자인을 다시 살펴보고 대화에 대해 좀 더 생각해 보겠습니다.

Existential React questions and a perfect Modal Dialog

작업 버튼이 있는 대화상자에 "바닥글" 부분이 분명히 있을 것입니다. 아마도 해당 버튼에는 하나, 둘, 셋, 왼쪽, 오른쪽 정렬, 사이에 공백 등 다양한 변형이 있을 것입니다. 또한 이 대화 상자에는 헤더가 없지만, 그럴 가능성이 매우 높습니다. 일부 헤더가 있는 대화 상자는 매우 일반적인 패턴입니다. 여기에는 확인 텍스트부터 양식, 대화형 경험, 아무도 읽지 않는 스크롤 가능한 매우 긴 "이용 약관" 텍스트에 이르기까지 완전히 임의의 콘텐츠가 포함된 콘텐츠 영역이 있습니다.

마지막으로 크기입니다. 디자인의 대화 상자는 작으며 확인 대화 상자일 뿐입니다. 큰 형식이나 긴 텍스트는 거기에 맞지 않습니다. 따라서 2단계에서 수집한 정보를 고려하면 대화 상자의 크기를 변경해야 한다고 가정하는 것이 매우 안전합니다. 현재 디자이너에게 디자인 지침이 있을 가능성이 높다는 점을 고려하면 "소형", "중형", "대형"의 세 가지 변형 대화 상자가 있다고 가정할 수 있습니다.

이 모든 것은 ModalDialog에 소품이 있어야 함을 의미합니다. 바닥글과 머리글은 ReactNode를 허용하는 일반 소품일 뿐이고, 크기는 문자열의 합집합일 뿐이며, 주요 부분인 콘텐츠 영역은 아이들:

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <>
          <div
            className="backdrop"
            onClick={() => setIsOpen(false)}
          ></div>
          <div className="dialog">
            <button
              className="close-button"
              onClick={() => setIsOpen(false)}
            >
              Close
            </button>
          </div>
        </>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

props에서 오는 추가 className을 사용하여 대화 상자의 크기를 제어하겠습니다. 실제 생활에서는 저장소에서 사용되는 스타일링 솔루션에 따라 크게 달라집니다.

그러나 이 변형에서는 대화가 너무 유연하여 거의 모든 것이 어디든 갈 수 있습니다. 예를 들어, 바닥글에서는 대부분 버튼 한두 개만 나올 것으로 예상할 수 있습니다. 그리고 해당 버튼은 웹사이트 전체에 걸쳐 일관되게 배열되어야 합니다. 이를 정렬하는 래퍼가 필요합니다.

.backdrop {
  background: rgba(0, 0, 0, 0.3);
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
로그인 후 복사
로그인 후 복사

콘텐츠와 동일합니다. 최소한 콘텐츠 주변에 패딩과 스크롤 기능이 필요합니다. 그리고 헤더에는 텍스트에 대한 몇 가지 스타일이 필요할 수 있습니다. 따라서 레이아웃은 다음과 같습니다.

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

하지만 안타깝게도 이를 보장할 수는 없습니다. 어떤 시점에서는 누군가가 바닥글에 버튼 외에 더 많은 것을 추가하고 싶어할 가능성이 높습니다. 또는 일부 대화 상자에는 판매 배경에 헤더가 있어야 합니다. 또는 콘텐츠에 패딩이 필요하지 않은 경우도 있습니다.

제가 여기서 말하고 있는 것은 언젠가 머리글/내용/바닥글 부분의 스타일을 지정할 수 있어야 한다는 것입니다. 그리고 아마도 예상보다 빨리 이루어질 것입니다.

물론 해당 구성을 props와 함께 전달하고 headerClassName, contentClassName 및 footerClassName props와 같은 것을 가질 수도 있습니다. 어떤 경우에는 실제로 괜찮을 수도 있습니다. 하지만 멋진 재설계를 위한 멋진 대화 같은 경우에는 더 잘할 수 있습니다.

이 문제를 해결하는 정말 깔끔한 방법은 다음과 같이 머리글/내용/바닥글을 자체 구성 요소로 추출하는 것입니다.

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

ModalDialog 코드를 래퍼가 없는 코드로 되돌립니다.

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

그러면 상위 앱에서 대화 상자 부분에 대한 기본 디자인을 갖고 싶다면 다음과 같은 작은 구성 요소를 사용합니다.

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <>
          <div
            className="backdrop"
            onClick={() => setIsOpen(false)}
          ></div>
          <div className="dialog">
            <button
              className="close-button"
              onClick={() => setIsOpen(false)}
            >
              Close
            </button>
          </div>
        </>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

완전히 사용자 정의된 항목을 갖고 싶다면 ModalDialog 자체를 건드리지 않고 고유한 사용자 정의 스타일을 사용하여 새 구성 요소를 구현합니다.

.backdrop {
  background: rgba(0, 0, 0, 0.3);
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
로그인 후 복사
로그인 후 복사

그 문제에서는 머리글과 바닥글 소품도 더 이상 필요하지 않습니다. DialogHeader 및 DialogFooter를 하위 항목에 전달하고 ModalDialog를 더욱 단순화하며 어디에서나 일관된 디자인을 유지하면서 동일한 수준의 유연성을 갖춘 훨씬 더 멋진 API를 가질 수 있습니다.

상위 구성 요소는 다음과 같습니다.

.dialog {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
로그인 후 복사

대화상자의 API는 다음과 같습니다.

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <>
          <div
            className="backdrop"
            onClick={() => setIsOpen(false)}
          ></div>
          <div className="dialog">
            <button
              className="close-button"
              onClick={() => setIsOpen(false)}
            >
              Close
            </button>
          </div>
        </>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

지금까지는 꽤 만족스럽습니다. 디자인에 필요한 방식으로 확장할 수 있을 만큼 유연할 뿐만 아니라 전체 앱에서 일관된 UI를 쉽게 구현할 수 있을 만큼 명확하고 합리적입니다.

연습해 볼 수 있는 실제 예는 다음과 같습니다.

4단계: 성능 및 다시 렌더링

이제 Modal의 API가 적절한 형태를 갖추었으므로 제가 구현한 명백한 발총을 다룰 차례입니다. 내 기사를 충분히 읽었다면 아마도 "뭐하는 거야??? 재렌더링!!"이라고 큰 소리로 외쳤을 것입니다. 지난 10분 동안? 물론, 당신 말이 맞습니다:

const ModalDialog = ({ onClose }) => {
  return (
    <>
      <div className="backdrop" onClick={onClose}></div>
      <div className="dialog">
        <button className="close-button" onClick={onClose}>
          Close
        </button>
      </div>
    </>
  );
};
로그인 후 복사

여기의 페이지 구성 요소에는 상태가 있습니다. 모달이 열리거나 닫힐 때마다 상태가 변경되고 전체 구성 요소와 내부의 모든 항목이 다시 렌더링됩니다. 그렇습니다. "성급한 최적화는 모든 악의 근원입니다." 그렇습니다. 성능을 실제로 측정하기 전에 최적화하지 마십시오. 이 경우에는 일반적인 통념을 안전하게 무시할 수 있습니다.

두 가지 이유가 있습니다. 첫째, 앱 전체에 많은 모달이 흩어져 있을 것이라는 사실을 알고 있습니다. 아무도 사용하지 않을 일회성 숨겨진 기능이 아닙니다. 따라서 누군가가 이와 같은 API를 사용하면 안 되는 곳에 상태를 넣을 가능성은 상당히 높습니다. 둘째, 처음부터 다시 렌더링 문제가 발생하는 것을 방지하는 데 많은 시간과 노력이 필요하지 않습니다. 단 1분만 노력하면 여기서는 성능에 대해 전혀 생각할 필요가 없습니다.

우리가 해야 할 일은 상태를 캡슐화하고 "제어되지 않는 구성 요소"라는 개념을 도입하는 것뿐입니다.

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

BaseModalDialog는 이전에 사용했던 대화 상자와 정확히 동일하므로 이름을 바꿨습니다.

그런 다음 대화 상자를 트리거하는 구성 요소를 트리거 소품으로 전달합니다.

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

페이지 구성 요소는 다음과 같습니다.

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

페이지 내부에 더 이상 상태가 없으며 잠재적으로 위험한 재렌더링도 더 이상 없습니다.

대부분의 경우 사용자가 대화 상자를 표시하려면 무언가를 클릭해야 하기 때문에 이와 같은 API는 사용 사례의 95%를 처리해야 합니다. 예를 들어 바로가기나 온보딩의 일부로 대화 상자를 독립적으로 표시해야 하는 드문 경우에도 여전히 BaseModalDialog를 사용하여 상태를 수동으로 처리할 수 있습니다.

5단계: 극단적인 경우와 접근성 처리

ModalDialog 구성 요소의 API는 React 관점에서 볼 때 매우 견고하지만 작업이 거의 완료되지 않았습니다. 2단계에서 모은 필수 항목을 고려하면 아직 몇 가지 문제를 더 수정해야 합니다.

문제 1: 트리거를 추가 범위로 ​​래핑하고 있습니다. 어떤 경우에는 페이지 레이아웃이 손상될 수 있습니다. 어떻게든 포장지를 없애야겠어요.

문제 2: 새 스택 컨텍스트를 생성하는 요소 내부에 대화 상자를 렌더링하면 모달이 일부 요소 아래에 나타납니다. 지금처럼 레이아웃 내부가 아닌 포털 내부에서 렌더링해야 합니다.

문제 3: 현재 키보드 액세스가 꽤 나쁩니다. 적절하게 구현된 모달 대화 상자가 열리면 포커스가 안으로 이동해야 합니다. 닫히면 대화 상자를 트리거한 요소로 포커스가 돌아가야 합니다. 대화 상자가 열리면 포커스가 내부에 "트랩"되어야 하며 외부 요소에는 포커스를 지정할 수 없어야 합니다. ESC 버튼을 누르면 대화상자가 닫힙니다. 현재로서는 이 중 어느 것도 구현되지 않습니다.

1번과 2번 문제는 약간 짜증나지만 비교적 빨리 해결할 수 있습니다. 그러나 문제 3은 수동으로 수행하기에는 엄청난 고통입니다. 게다가 확실히 문제는 해결되었습니다. 모든 대화 상자에는 이 기능이 필요할 것입니다.

'혼자서 하기에는 엄청난 고통', '확실히 문제가 해결된 것 같다'라는 조합이 바로 기존 라이브러리를 찾는 곳이다.

이미 했던 모든 사전 작업을 고려하면 이제 올바른 것을 선택하는 것이 쉽습니다.

Ant Design이나 Material UI와 같은 기존 UI 구성 요소 라이브러리로 이동하여 거기에서 대화 상자를 사용할 수 있습니다. 그러나 재설계에서 그것들을 사용하지 않는다면, 그들의 디자인을 나에게 필요한 것으로 조정하는 것은 그들이 해결하는 것보다 더 많은 고통을 가져올 것입니다. 따라서 이 사건은 즉시 NO입니다.

Radix나 React Aria와 같은 "헤드리스" UI 라이브러리 중 하나를 사용할 수 있습니다. 이는 상태, 트리거 및 모든 접근성과 같은 기능을 구현하지만 디자인은 소비자에게 맡깁니다. 해당 API를 살펴보면서 대화 상자를 수동으로 트리거하려는 경우 실제로 필요한 경우 대화 상자 상태를 제어할 수 있는지 다시 확인해야 합니다(그렇습니다).

어떤 이유로 헤드리스 라이브러리를 사용할 수 없다면 최소한 포커스 트랩 기능을 처리하는 라이브러리를 사용해 보도록 하겠습니다.

기사를 위해 내가 원하는 어떤 라이브러리든 가져올 수 있다고 가정해 보겠습니다. 이 경우에는 Radix를 사용하겠습니다. 사용하기가 매우 쉽고 대화 상자의 API가 이미 구현한 것과 매우 유사하므로 리팩토링이 매우 쉽습니다.

대화상자 자체의 API를 약간 변경해야 합니다.

export default function Page() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Click me
      </button>
      {isOpen ? (
        <div className="dialog">some content</div>
      ) : null}
    </>
  );
}
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

이전과 거의 똑같습니다. 단, 모든 곳의 div 대신 Radix 프리미티브를 사용합니다.

제어되지 않은 대화 상자 사용법은 전혀 변경되지 않습니다.

<button
  className="close-button"
  onClick={() => setIsOpen(false)}
>
  Close
</button>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

제어된 대화 상자가 약간 변경됩니다. 조건부 렌더링 대신 소품을 전달해야 합니다.

<div
  className="backdrop"
  onClick={() => setIsOpen(false)}
></div>
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사
로그인 후 복사

아래 예를 확인하고 키보드를 사용하여 탐색해 보세요. 모든 것이 내가 필요로 하는 대로 작동합니다. 얼마나 멋진가요?

보너스로 Radix는 포털 문제도 처리하며 트리거를 범위로 래핑하지 않습니다. 더 이상 해결해야 할 극단적인 사례가 없으므로 마지막 단계로 넘어갈 수 있습니다.

6단계: 최종 마무리

아직 기능이 완성되지 않았습니다! ? 대화 상자는 이제 매우 견고해 보이고 느껴지므로 이 단계에서는 구현에 있어 중요한 내용을 변경하지 않겠습니다. 하지만 제가 해결하고 있는 사용 사례에 대한 "완벽한" 대화로 간주되려면 여전히 몇 가지 사항이 필요합니다.

하나: 디자이너가 아직 요청하지 않은 경우 가장 먼저 요청하는 것은 대화 상자가 열릴 때 미묘한 애니메이션을 추가하는 것입니다. 이를 예상하고 React에서 애니메이션을 수행하는 방법을 기억해야 합니다.

: 작은 화면에서도 여전히 괜찮아 보이도록 대화 상자에 최대 너비와 최대 높이를 추가해야 합니다. 그리고 매우 큰 화면에서 어떻게 보일지 생각해 보세요.

: 모바일에서 대화 상자가 어떻게 작동해야 하는지 디자이너와 논의해야 합니다. 대화 상자의 크기에 관계없이 화면의 대부분을 차지하는 슬라이드인 패널로 만들어 달라고 요청할 가능성이 높습니다.

4개: 최소한 DialogTitle 및 DialogDescription 구성 요소를 도입해야 합니다. Radix는 접근성 목적으로 이를 사용하도록 요청할 것입니다.

다섯: 테스트! 대화는 계속 유지되며 다른 사람이 유지 관리하므로 이 경우 테스트가 거의 필수입니다.

그리고 아마도 지금은 잊어버린 수많은 작은 것들이 나중에 나올 것입니다. 대화 내용의 실제 디자인을 구현하는 것은 말할 것도 없습니다.

몇 가지 추가 생각

위의 "대화 상자"를 "SomeNewFeature"로 바꾸면 이는 거의 모든 새로운 기능을 구현하는 데 사용하는 알고리즘입니다.

솔루션의 빠른 "급증" → 기능에 대한 요구 사항 수집 → 작동하게 만들기 → 성능 향상 → 완료하기 → 완벽하게 만들기.

지금까지 수백 번 구현한 실제 대화 같은 작업은 머릿속에서 10초 만에 첫 번째 단계를 수행하고 바로 2단계부터 시작하겠습니다.

매우 복잡하고 알려지지 않은 작업의 경우 1단계가 더 길어질 수 있으며 다양한 솔루션과 라이브러리를 바로 탐색해야 할 수도 있습니다.

정확하게 알려지지 않은 기능, 즉 '우리가 수행해야 하는 일반적인 기능'인 경우 탐색할 항목이 없을 수 있으므로 1단계를 건너뛸 수 있습니다.

특히 "민첩한" 환경에서는 요구사항이 점진적으로 제공되고 종종 변경되는 직선보다는 나선형에 더 가깝고 정기적으로 처음 두 단계로 돌아갑니다.


이러한 유형의 기사가 도움이 되었기를 바랍니다! ?? 이와 같은 콘텐츠를 더 원하거나 일반적인 "작동 방식"을 선호하는 경우 알려주시기 바랍니다.

그리고 이 과정이 여러분의 머리 속에서 어떻게 다른지 듣고 싶습니다 ?


원본은 https://www.developerway.com에 게시되었습니다. 웹사이트에 이런 기사가 더 있나요?

React 지식을 한 단계 더 발전시키려면 Advanced React 책을 살펴보세요.

뉴스레터를 구독하거나 LinkedIn에 연결하거나 Twitter에서 팔로우하면 다음 기사가 나오는 즉시 알림을 받을 수 있습니다.


그런데 마지막으로 한 가지 말씀드리자면, 곧 새 프로젝트를 시작할 예정인데 설명된 대로 디자인 경험을 다듬을 디자이너나 시간이 없다면, 저는 최근에 새로운 프로젝트를 구현하는 데 몇 시간을 소비했습니다. 이 경우에 대한 UI 구성 요소 라이브러리입니다. 복사하여 붙여넣을 수 있는 구성 요소와 공통 패턴, Radix 및 Tailwind, 어두운 모드, 접근성 및 기본적으로 모바일 지원 기능을 갖추고 있습니다. 위의 완벽한 모달 대화 상자를 포함합니다! ?

한번 시도해 보세요: https://www.buckets-ui.com/

Existential React questions and a perfect Modal Dialog

위 내용은 실존적 React 질문과 완벽한 모달 대화 상자의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
저자별 최신 기사
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿