javascript react modal - 如何在Redux中顯示執行異步操作的模態對話框?




2 Answers

我建議的方法有點冗長,但我發現它可以很好地擴展到復雜的應用程序。 當你想要顯示一個模態時,觸發一個描述你想要看到的模態的動作:

調度一個動作來顯示模態

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

(當然,字符串可以是常量;為了簡單,我使用內聯字符串。)

編寫一個Reducer來管理模態狀態

然後確保你有一個接受這些值的reducer:

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將會更新以包含關於當前可見模態窗口的信息。

編寫根模態組件

在組件層次結構的根目錄中,添加連接到Redux存儲的<ModalRoot>組件。 它會監聽state.modal並顯示一個合適的模式組件,並從state.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)

我們在這裡做了什麼? ModalRootstate.modal連接的ModalRoot讀取當前的modalTypemodalProps ,並呈現相應的組件,如DeletePostModalConfirmLogoutModal 。 每個模態都是一個組件!

編寫特定的模態組件

這裡沒有一般規則。 它們只是React組件,可以分派操作,從商店狀態讀取某些內容, 並且恰好是模式

例如, 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

提取演示組件

複製粘貼每個“特定”模式的相同佈局邏輯會很尷尬。 但是你有組件,對吧? 所以你可以提取一個不知道特定模態做什麼的presentational <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>在你的應用程序中可以接受的道具,但我會想像你可能有幾種模態(例如信息模式,確認模式等),以及它們的幾種樣式。

訪問和隱藏點擊外部或退出鍵

關於模態的最後一個重要部分是,我們通常希望在用戶在外麵點擊或按下Escape時隱藏它們。

我建議你不要自己實現它,而不是給你實施這個建議。 考慮無障礙是很難得到正確的。

相反,我建議您使用可訪問的現成模態組件,如react-modal 。 它是完全可定制的,你可以把任何你想要的東西放在它裡面,但它能正確處理可訪問性,這樣盲人仍然可以使用你的模態。

你甚至可以在你自己的<Modal>中包裝react-modal ,它接受特定於你的應用程序的道具並生成子按鈕或其他內容。 這都是組件!

其他方法

有多種方法可以做到這一點。

有些人不喜歡這種方法的冗長性,並且傾向於使用<Modal>組件,它們可以使用稱為“門戶”的技術在其組件內進行渲染。 門戶允許你在你的內部渲染一個組件,而實際上它將在DOM中的預定位置渲染,這對於模態非常方便。

實際上,與之前鏈接的react-modal已經在內部完成了,因此在技術上你甚至不需要從頂部渲染它。 我仍然覺得很好解耦我想從顯示它的組件中顯示的模式,但是您也可以直接從組件中使用react-modal ,並跳過上面我寫的大部分內容。

我鼓勵你考慮兩種方法,嘗試一下,選擇你最適合你的應用和你的團隊。

我正在構建一個需要在某些情況下顯示確認對話框的應用程序。

比方說,我想刪除一些東西,然後我會派發一個像deleteSomething(id)這樣的動作,這樣一些reducer就會捕獲該事件並填充對話框deleteSomething(id)器以顯示它。

當這個對話框提交時,我懷疑。

  • 該組件如何根據第一個動作分派適當的動作?
  • 行動創造者是否應該處理這種邏輯?
  • 我們可以在減速器內添加動作嗎?

編輯:

使其更清晰:

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

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

所以我試圖重用對話框組件。 顯示/隱藏對話框不是問題,因為這可以輕鬆地在減速器中完成。 我試圖說明的是如何根據左側的流動開始的動作從右側分配動作。




JS社區的知名專家可以在這裡找到很多很好的解決方案和寶貴的評論。 這可能是一個指標,並不像看起來那麼簡單。 我認為這就是為什麼它可能成為這個問題的疑慮和不確定性的根源。

這裡的基本問題是,在React中,只允許將組件加載到它的父級,這並不總是所需的行為。 但如何解決這個問題?

我提出解決方案,解決這個問題。 更詳細的問題定義,src和示例可以在這裡找到: https://github.com/fckt/react-layer-stack#rationalehttps://github.com/fckt/react-layer-stack#rationale

合理

react / react-dom帶有2個基本的假設/想法:

  • 每個UI自然都是分層的。 這就是為什麼我們有構思相互包裝的想法
  • react-dom默認情況下(實際上)將子組件添加到其父DOM節點

問題是有時第二個屬性不是你想要的。 有時,您想要將組件安裝到不同的物理DOM節點中,並同時保持父級和子級之間的邏輯連接。

典型示例是類似Tooltip的組件:在開發過程的某個時候,您可能會發現需要為UI element添加一些說明:它將呈現在固定圖層中並應知道其坐標(即UI element坐標或鼠標coords),同時它需要信息是否需要立即顯示,其內容和來自父組件的一些背景。 此示例顯示有時邏輯層次結構與物理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>)
// ...



Related