itsource

비동기 작업을 수행하는 Redx 모달 대화 상자를 표시하려면 어떻게 해야 합니까?

mycopycode 2023. 1. 28. 09:37
반응형

비동기 작업을 수행하는 Redx 모달 대화 상자를 표시하려면 어떻게 해야 합니까?

상황에 따라서는 확인 다이얼로그를 표시할 필요가 있는 앱을 만들고 있습니다.

예를 들어 무언가를 삭제하고 싶다고 하면, 다음과 같은 액션을 송신합니다.deleteSomething(id)따라서 일부 리듀서가 이벤트를 포착하고 이를 보여주기 위해 대화 상자를 채웁니다.

이 대화 상자가 제출될 때 내 의심이 든다.

  • 이 컴포넌트는 첫 번째 디스패치된 액션에 따라 적절한 액션을 디스패치하려면 어떻게 해야 합니까?
  • 액션 크리에이터가 이 논리를 처리해야 합니까?
  • 리듀서 내부에 액션을 추가할 수 있습니까?

편집:

알기 쉽게 하기 위해서:

deleteThingA(id) => show dialog with Questions => deleteThingARemotely(id)

createThingB(id) => Show dialog with Questions => createThingBRemotely(id)

그래서 대화 상자를 다시 사용하려고 합니다.대화상자를 표시하거나 숨기는 것은 문제가 되지 않습니다.리듀서에서는 쉽게 할 수 있기 때문입니다.제가 지정하려는 것은 왼쪽에서 플로우를 시작하는 액션에 따라 오른쪽에서 액션을 디스패치하는 방법입니다.

제가 제안하는 접근방식은 좀 장황하지만 복잡한 어플리케이션으로 잘 확장된다는 것을 알게 되었습니다.모달 표시 시 표시할 모달에 대해 설명하는 액션을 실행합니다.

모달 표시를 위한 액션 디스패치

this.props.dispatch({
  type: 'SHOW_MODAL',
  modalType: 'DELETE_POST',
  modalProps: {
    postId: 42
  }
})

(스트링은 물론 상수일 수 있습니다.간단하게 하기 위해서 인라인 스트링을 사용하고 있습니다).

모듈 상태 관리를 위한 리듀서 쓰기

그런 다음 다음 값만 사용할 수 있는 리덕터가 있는지 확인합니다.

const initialState = {
  modalType: null,
  modalProps: {}
}

function modal(state = initialState, action) {
  switch (action.type) {
    case 'SHOW_MODAL':
      return {
        modalType: action.modalType,
        modalProps: action.modalProps
      }
    case 'HIDE_MODAL':
      return initialState
    default:
      return state
  }
}

/* .... */

const rootReducer = combineReducers({
  modal,
  /* other reducers */
})

하면,state.modal는 현재 표시되는 모달창에 대한 정보를 포함하도록 갱신됩니다.

루트 모달 컴포넌트 쓰기

에 「」를 합니다.<ModalRoot>컴포넌트 중 하나가 Redux 스토어에 연결되어 있습니다. It will listen to 그것은 들을 것이다.state.modal절 한 적 달 포 모 시 넌 로 달 the포 and전다프,한ate를트 display넌 from표ing an컴여 component하를트 forward props modal appropri thestate.modal.modalProps.

// These are regular React components we will write soon
import DeletePostModal from './DeletePostModal'
import ConfirmLogoutModal from './ConfirmLogoutModal'

const MODAL_COMPONENTS = {
  'DELETE_POST': DeletePostModal,
  'CONFIRM_LOGOUT': ConfirmLogoutModal,
  /* other modals */
}

const ModalRoot = ({ modalType, modalProps }) => {
  if (!modalType) {
    return <span /> // after React v15 you can return null here
  }

  const SpecificModal = MODAL_COMPONENTS[modalType]
  return <SpecificModal {...modalProps} />
}

export default connect(
  state => state.modal
)(ModalRoot)

무슨 짓을 한 거야?우리가 여기서 뭘 한 거지? ModalRoot reads the current 전류를 읽다modalType ★★★★★★★★★★★★★★★★★」modalProps부에서state.modal연결되면 다 음 과 은 응 포 더 as, to렌컴다니합 connected it is링 which트를 and renders대같 suchding a넌는하 component correspon, to connected it whichDeletePostModal ★★★★★★★★★★★★★★★★★」ConfirmLogoutModal★★★★★★★★★★★★★★★★★★★★★★★★★★★★!

특정 모달 컴포넌트 쓰기

여기에는 일반적인 규칙이 없습니다.이들은 액션을 디스패치하고 스토어 스테이트에서 무언가를 읽을 수 있는 Respect 컴포넌트일 뿐이며 모드일 뿐입니다.

를 들어, 「」라고 하는 것은,DeletePostModal음음음같 뭇매하다

import { deletePost, hideModal } from '../actions'

const DeletePostModal = ({ post, dispatch }) => (
  <div>
    <p>Delete post {post.name}?</p>
    <button onClick={() => {
      dispatch(deletePost(post.id)).then(() => {
        dispatch(hideModal())
      })
    }}>
      Yes
    </button>
    <button onClick={() => dispatch(hideModal())}>
      Nope
    </button>
  </div>
)

export default connect(
  (state, ownProps) => ({
    post: state.postsById[ownProps.postId]
  })
)(DeletePostModal)

DeletePostModal에, 할 수 되어 있는 컴포넌트와 은, 「」를 포함한 할 수 .다음과 같은 액션을 디스패치 할 수 있습니다.hideModal★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

프레젠테이션 컴포넌트 추출

모든 "특정" 모달에 대해 동일한 레이아웃 로직을 복사 붙여넣는 것은 어렵습니다.하지만 부품은 있죠?프레젠테이션을 추출할 수 있습니다. <Modal>특정 모달의 기능은 모르지만 외관은 처리되는 컴포넌트입니다.

다음에 ', 하다, 하다, 하다'와 같은 특정한 이 있어요.DeletePostModal렌더링에 사용할 수 있습니다.

import { deletePost, hideModal } from '../actions'
import Modal from './Modal'

const DeletePostModal = ({ post, dispatch }) => (
  <Modal
    dangerText={`Delete post ${post.name}?`}
    onDangerClick={() =>
      dispatch(deletePost(post.id)).then(() => {
        dispatch(hideModal())
      })
    })
  />
)

export default connect(
  (state, ownProps) => ({
    post: state.postsById[ownProps.postId]
  })
)(DeletePostModal)

.<Modal>당신의 어플리케이션에서는 받아들일 수 있지만, 저는 당신이 몇 가지 종류의 모달(정보 모달, 확인 모달 등)과 그것들을 위한 몇 가지 스타일을 가지고 있다고 생각합니다.

Click Outside 또는 Escape 키 접근성 및 숨기기

모달에 관한 마지막 중요한 부분은 일반적으로 사용자가 Outside를 클릭하거나 Escape를 누를 때 숨기기를 원한다는 것입니다.

이 구현에 대한 조언을 제공하는 대신 직접 구현하지 않는 것이 좋습니다.접근성을 고려했을 때 제대로 맞추기가 어렵다.

대신 등 접근 가능한 기성 모달컴포넌트를 사용하는 것이 좋습니다.완전히 커스터마이즈 할 수 있어 원하는 것을 넣을 수 있지만, 시각장애인이 계속 사용할 수 있도록 접근성을 올바르게 처리합니다.

도 할 수 요.react-modal 생각하여<Modal>어플리케이션 고유의 소품을 받아들여 자버튼이나 기타 콘텐츠를 생성합니다.★★★★★★★★★★★★★★★★★★★★★★★!

기타 접근법

그것을 하는 방법은 여러 가지가 있다.

어떤 사람들은 이 접근법의 장황함을 좋아하지 않고, 그것을 가지는 것을 선호합니다.<Modal>컴포넌트 내부에 렌더링할 수 있는 컴포넌트입니다.포털을 사용하면 내부에 컴포넌트를 렌더링할 수 있지만 실제로는 DOM의 미리 정해진 위치에 렌더링되므로 모달에 매우 편리합니다.

실제로 앞서 링크한 에 이미 내부적으로 링크되어 있기 때문에 기술적으로 처음부터 렌더링할 필요가 없습니다.보여 주는 컴포넌트에서 보여주고 싶은 모달은 분리할 수 있지만 사용할 수도 있습니다.react-modal컴포넌트에서 직접 추출하여 위에서 설명한 대부분의 내용을 건너뜁니다.

두 가지 방법을 모두 고려하여 실험한 후 앱과 팀에 가장 적합한 방법을 선택하는 것이 좋습니다.

업데이트: React 16.0 도입 포털:ReactDOM.createPortal 표시

업데이트: React의 다음 버전(파이버: 16 또는 17)에는 포털을 만드는 방법이 포함됩니다.ReactDOM.unstable_createPortal() 표시


포털 사용

Dan Abramov는 첫 번째 부분은 괜찮지만, 많은 보일러 플레이트를 포함합니다.그의 말대로 포털도 이용할 수 있어요.저는 그 아이디어를 조금 더 자세히 설명하겠습니다.

포털의 장점은 팝업과 버튼이 React 트리에 매우 가깝게 유지되고 소품을 사용한 매우 간단한 부모/자녀 통신으로 포털과의 비동기 액션을 쉽게 처리하거나 부모가 포털을 맞춤화할 수 있다는 것입니다.

포털이란?

하면 직접 수 .document.body★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

예를 들어 다음과 같은 React 트리를 본문에 렌더링하는 것입니다.

<div className="layout">
  <div className="outside-portal">
    <Portal>
      <div className="inside-portal">
        PortalContent
      </div>
    </Portal>
  </div>
</div>

출력은 다음과 같습니다.

<body>
  <div class="layout">
    <div class="outside-portal">
    </div>
  </div>
  <div class="inside-portal">
    PortalContent
  </div>
</body>

inside-portal되었습니다.<body>평범하고 깊은 곳에 있는 대신 말이야

포털을 사용하는 경우

포털은 팝업, 드롭다운, 제안, 핫스팟 등 기존 React 컴포넌트 위에 표시해야 할 요소를 표시할 때 특히 유용합니다.

포털을 사용하는 이유

z-index 문제 없음: 포털에서 렌더링 가능<body>팝업이나 드롭다운을 표시하고 싶다면 z-index 문제와 싸우고 싶지 않다면 매우 좋은 방법입니다. 됩니다.document.body 순서로, 즉 '마운트 순서'를 놀지 한 '마운트 순서'를 합니다.z-index디폴트 동작은 포털을 마운트 순서로 스택하는 것입니다.을 열 수 번째 에 두 할수.이러한 팝업은 조차 할 필요가 없습니다.z-index.

실제로

가장 간단한 방법: 로컬 리액트 상태 사용: 간단한 삭제 확인 팝업을 위해 Redux 보일러 플레이트를 사용할 필요가 없다고 생각되면 포털을 사용하면 코드를 크게 단순화할 수 있습니다.상호 작용이 매우 로컬하고 실제로 구현 세부 사항인 이러한 사용 사례의 경우, 핫 새로고침, 시간 이동, 작업 기록 및 Redux가 제공하는 모든 이점에 대해 정말로 관심이 있습니까?개인적으로, 저는 이 경우 지역 주를 사용하지 않습니다.코드는 다음과 같이 단순해집니다.

class DeleteButton extends React.Component {
  static propTypes = {
    onDelete: PropTypes.func.isRequired,
  };

  state = { confirmationPopup: false };

  open = () => {
    this.setState({ confirmationPopup: true });
  };

  close = () => {
    this.setState({ confirmationPopup: false });
  };

  render() {
    return (
      <div className="delete-button">
        <div onClick={() => this.open()}>Delete</div>
        {this.state.confirmationPopup && (
          <Portal>
            <DeleteConfirmationPopup
              onCancel={() => this.close()}
              onConfirm={() => {
                this.close();
                this.props.onDelete();
              }}
            />
          </Portal>
        )}
      </div>
    );
  }
}

심플: Redx 상태를 계속 사용할 수 있습니다.정말로 사용하고 싶다면connectDeleteConfirmationPopup표시 여부를 나타냅니다.포털은 React 트리에 깊이 중첩된 상태로 유지되므로 부모가 포털에 소품을 전달할 수 있으므로 이 포털의 동작을 커스터마이즈하는 것이 매우 간단합니다.않는 의 맨 .z-index"사용 사례에 따라 작성한 일반적인 Delete Confirmation Popup을 커스터마이즈하려면 어떻게 해야 합니까?"와 같은 문제를 고려해야 합니다.또, 통상, 이 문제에 대해서는, 네스트 된 확인/취소 액션, 번역 번들 키, 또는 렌더링 기능(또는 시리얼 할 수 없는 것)을 포함한 액션을 디스패치 하는 등, 꽤 해답이 있는 솔루션을 발견할 수 있습니다.가 없고,소품만 .DeleteConfirmationPopup의 아이일 뿐이다.DeleteButton

결론

포털은 코드를 단순화하는 데 매우 유용합니다.나는 더 이상 그들 없이는 살 수 없었다.

포털 구현은 다음과 같은 기타 유용한 기능도 지원합니다.

  • 접근성
  • 포털을 닫기 위한 바로 가기 공간 지정
  • 외부 클릭 처리(포털 닫기 여부)
  • 링크 클릭 처리(포털 닫기 여부)
  • 포털 트리에서 사용할 수 있는 React Context

react-modal 또는 react-modal은 일반적으로 화면 중앙에 있는 전체 화면 팝업, 모달 및 오버레이에 적합합니다.

React-ther는 대부분의 React 개발자들에게는 알려지지 않았지만, 가장 유용한 툴 중 하나입니다.테더를 사용하면 포털을 만들 수 있지만 지정된 대상을 기준으로 포털이 자동으로 배치됩니다.툴팁, 드롭다운, 핫스팟, 헬프박스에 매우 적합합니다.포지션에 문제가 있었던 적이 있다면absolute/relative ★★★★★★★★★★★★★★★★★」z-index또는 드롭다운이 뷰포트를 벗어나면 테더가 모든 문제를 해결해 줍니다.

예를 들어 온보드 핫스팟을 쉽게 구현할 수 있습니다.클릭하면 툴팁으로 확장됩니다.

온보드 핫스팟

여기 실제 생산 코드가 있습니다.더 이상 단순할 수 없습니다:)

<MenuHotspots.contacts>
  <ContactButton/>
</MenuHotspots.contacts>

편집: 포털을 선택한 노드에 렌더링할 수 있는 react-gateway가 검색되었습니다(바디는 아닙니다).

편집: 리액트 포퍼가 리액트 테더의 적절한 대안이 될 수 있을 것 같습니다.PopperJS는 DOM을 직접 터치하지 않고 요소의 적절한 위치만 계산하는 라이브러리입니다.사용자는 DOM 노드를 배치할 장소와 타이밍을 선택할 수 있으며 테더는 본체에 직접 추가됩니다.

편집: react-slot-fill도 있습니다.이것은 흥미롭고 트리의 원하는 위치에 배치되어 있는 예약된 요소 슬롯에 요소를 렌더링하여 유사한 문제를 해결하는 데 도움이 됩니다.

이 주제에 대한 JS 커뮤니티의 알려진 전문가의 많은 좋은 솔루션과 귀중한 코멘트를 여기에서 찾을 수 있습니다.그렇게 사소한 문제가 아닌 것 같습니다.이것이 그 문제에 대한 의심과 불확실성의 원인이 될 수 있는 이유라고 생각합니다.

여기서 근본적인 문제는 React에서는 컴포넌트를 부모에만 마운트할 수 있다는 것입니다.이것은 항상 바람직한 동작은 아닙니다.하지만 이 문제에 어떻게 대처해야 할까요?

이 문제를 해결하기 위해 해결 방법을 제안합니다.자세한 문제의 정의, src 및 예는 https://github.com/fckt/react-layer-stack#rationale 에서 확인할 수 있습니다.

근거

react/react-dom에는, 다음의 2개의 기본적인 전제 조건/조건이 있습니다.

  • UI를 사용하다는 '이러한 을 가지고 있다.components
  • react-dom에 (로 마운트합니다.

문제는 두 번째 자산이 경우에 따라서는 당신이 원하는 것이 아닐 수 있다는 것입니다.경우에 따라 구성 요소를 다른 물리적 DOM 노드에 마운트하고 상위 노드와 하위 노드 간의 논리적 연결을 동시에 유지할 수 있습니다.

인 예는 과 같은 컴포넌트입니다.에서는, 「Tooltip」의할 필요가 경우가 있습니다.개발 프로세스의 어느 시점에서 이 컴포넌트에 대한 설명을 추가해야 할 수 있습니다.UI element, 좌표를 ).UI element코디네이트 또는 마우스 좌표)와 동시에 정보가 필요합니다.정보가 지금 당장 표시되어야 하는지 여부, 콘텐츠 및 상위 컴포넌트의 컨텍스트가 필요합니다.이 예에서는 논리 계층이 물리적 DOM 계층과 일치하지 않을 수 있습니다.

질문에 대한 답변의 구체적인 예를 보려면 https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example을 참조하십시오.

import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
  const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
  return (
    <Cell {...props}>
        // the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
        <Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
            hideMe, // alias for `hide(modalId)`
            index } // useful to know to set zIndex, for example
            , e) => // access to the arguments (click event data in this example)
          <Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
            <ConfirmationDialog
              title={ 'Delete' }
              message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
              confirmButton={ <Button type="primary">DELETE</Button> }
              onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
              close={ hideMe } />
          </Modal> }
        </Layer>

        // this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
        <LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
          <div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
            <Icon type="trash" />
          </div> }
        </LayerContext>
    </Cell>)
// ...

내 생각에 최소한의 구현에는 두 가지 요건이 있다.모달의 오픈 여부를 추적하는 상태 및 표준 반응 트리 외부에 모달을 렌더링하는 포털입니다.

아래의 ModalContainer 컴포넌트는 이러한 요건을 모달 및 트리거에 대응하는 렌더 함수와 함께 구현합니다.이 함수는 모달 오픈을 위한 콜백 실행을 담당합니다.

import React from 'react';
import PropTypes from 'prop-types';
import Portal from 'react-portal';

class ModalContainer extends React.Component {
  state = {
    isOpen: false,
  };

  openModal = () => {
    this.setState(() => ({ isOpen: true }));
  }

  closeModal = () => {
    this.setState(() => ({ isOpen: false }));
  }

  renderModal() {
    return (
      this.props.renderModal({
        isOpen: this.state.isOpen,
        closeModal: this.closeModal,
      })
    );
  }

  renderTrigger() {
     return (
       this.props.renderTrigger({
         openModal: this.openModal
       })
     )
  }

  render() {
    return (
      <React.Fragment>
        <Portal>
          {this.renderModal()}
        </Portal>
        {this.renderTrigger()}
      </React.Fragment>
    );
  }
}

ModalContainer.propTypes = {
  renderModal: PropTypes.func.isRequired,
  renderTrigger: PropTypes.func.isRequired,
};

export default ModalContainer;

여기 간단한 사용 사례가 있습니다.

import React from 'react';
import Modal from 'react-modal';
import Fade from 'components/Animations/Fade';
import ModalContainer from 'components/ModalContainer';

const SimpleModal = ({ isOpen, closeModal }) => (
  <Fade visible={isOpen}> // example use case with animation components
    <Modal>
      <Button onClick={closeModal}>
        close modal
      </Button>
    </Modal>
  </Fade>
);

const SimpleModalButton = ({ openModal }) => (
  <button onClick={openModal}>
    open modal
  </button>
);

const SimpleButtonWithModal = () => (
   <ModalContainer
     renderModal={props => <SimpleModal {...props} />}
     renderTrigger={props => <SimpleModalButton {...props} />}
   />
);

export default SimpleButtonWithModal;

렌더링된 모달 및 트리거 컴포넌트의 구현에서 상태 관리 및 보일러 플레이트 로직을 분리하기 위해 렌더 함수를 사용합니다.이렇게 하면 렌더링된 구성 요소를 원하는 대로 만들 수 있습니다.당신의 경우, 모달 컴포넌트는 비동기 액션을 디스패치하는 콜백 함수를 수신하는 연결된 컴포넌트일 수 있습니다.

트리거 컴포넌트에서 모달 컴포넌트로 다이내믹 소품을 보내야 하는 경우(자주 발생하지 않기를 바라며), 모달 컨테이너를 자체 상태에서 다이내믹 소품을 관리하고 원본 렌더링 방법을 강화하는 컨테이너 컴포넌트로 감싸는 것을 권장합니다.

import React from 'react'
import partialRight from 'lodash/partialRight';
import ModalContainer from 'components/ModalContainer';

class ErrorModalContainer extends React.Component {
  state = { message: '' }

  onError = (message, callback) => {
    this.setState(
      () => ({ message }),
      () => callback && callback()
    );
  }

  renderModal = (props) => (
    this.props.renderModal({
       ...props,
       message: this.state.message,
    })
  )

  renderTrigger = (props) => (
    this.props.renderTrigger({
      openModal: partialRight(this.onError, props.openModal)
    })
  )

  render() {
    return (
      <ModalContainer
        renderModal={this.renderModal}
        renderTrigger={this.renderTrigger}
      />
    )
  }
}

ErrorModalContainer.propTypes = (
  ModalContainer.propTypes
);

export default ErrorModalContainer;

모달은 연결된 컨테이너로 랩하고 여기서 비동기 작업을 수행합니다.이렇게 하면 액션을 트리거하는 디스패치와 onClose 프로포트에 모두 도달할 수 있습니다.dispatch소품에서, 통과하지 마세요.mapDispatchToPropsconnect.

class ModalContainer extends React.Component {
  handleDelete = () => {
    const { dispatch, onClose } = this.props;
    dispatch({type: 'DELETE_POST'});

    someAsyncOperation().then(() => {
      dispatch({type: 'DELETE_POST_SUCCESS'});
      onClose();
    })
  }

  render() {
    const { onClose } = this.props;
    return <Modal onClose={onClose} onSubmit={this.handleDelete} />
  }
}

export default connect(/* no map dispatch to props here! */)(ModalContainer);

모달 렌더링 및 가시성 상태가 설정된 앱:

class App extends React.Component {
  state = {
    isModalOpen: false
  }

  handleModalClose = () => this.setState({ isModalOpen: false });

  ...

  render(){
    return (
      ...
      <ModalContainer onClose={this.handleModalClose} />  
      ...
    )
  }

}

언급URL : https://stackoverflow.com/questions/35623656/how-can-i-display-a-modal-dialog-in-redux-that-performs-asynchronous-actions

반응형