Làm cách nào để hiển thị hộp thoại phương thức trong Redux thực hiện các hành động không đồng bộ?


240

Tôi đang xây dựng một ứng dụng cần hiển thị hộp thoại xác nhận trong một số trường hợp.

Giả sử tôi muốn xóa một cái gì đó, sau đó tôi sẽ gửi một hành động như deleteSomething(id)vậy để một số trình giảm tốc sẽ bắt sự kiện đó và sẽ điền vào trình giảm hộp thoại để hiển thị nó.

Nghi ngờ của tôi đến khi hộp thoại này gửi.

  • Làm thế nào thành phần này có thể gửi hành động thích hợp theo hành động đầu tiên được gửi đi?
  • Người tạo hành động nên xử lý logic này?
  • Chúng ta có thể thêm các hành động bên trong bộ giảm tốc không?

biên tập:

để làm cho nó rõ ràng hơn:

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

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

Vì vậy, tôi đang cố gắng sử dụng lại thành phần hộp thoại. Hiển thị / ẩn hộp thoại không phải là vấn đề vì điều này có thể dễ dàng thực hiện trong bộ giảm tốc. Những gì tôi đang cố gắng chỉ định là làm thế nào để gửi hành động từ phía bên phải theo hành động bắt đầu dòng chảy ở phía bên trái.


1
Tôi nghĩ trong trường hợp của bạn, trạng thái của hộp thoại (ẩn / hiển thị) là cục bộ. Tôi sẽ chọn sử dụng trạng thái phản ứng để quản lý hiển thị / ẩn hộp thoại. Theo cách này, câu hỏi về "hành động đúng theo hành động đầu tiên" sẽ không còn nữa.
Minh

Câu trả lời:


516

Cách tiếp cận tôi đề xuất là hơi dài dòng nhưng tôi thấy nó có khả năng mở rộng khá tốt thành các ứng dụng phức tạp. Khi bạn muốn hiển thị một phương thức, bắn một hành động mô tả phương thức bạn muốn xem:

Gửi một hành động để hiển thị phương thức

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

(Tất nhiên, chuỗi có thể là hằng số; Tôi đang sử dụng chuỗi nội tuyến để đơn giản.)

Viết một Reducer để quản lý trạng thái phương thức

Sau đó, đảm bảo bạn có một bộ giảm tốc chỉ chấp nhận các giá trị này:

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 */
})

Tuyệt quá! Bây giờ, khi bạn gửi một hành động, state.modalsẽ cập nhật để bao gồm thông tin về cửa sổ phương thức hiện có thể nhìn thấy.

Viết thành phần gốc Modal

Tại thư mục gốc của cấu trúc phân cấp, hãy thêm một <ModalRoot>thành phần được kết nối với cửa hàng Redux. Nó sẽ lắng nghestate.modal và hiển thị một thành phần phương thức thích hợp, chuyển tiếp các đạo cụ từ 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)

Chúng ta đã làm gì ở đây? ModalRootđọc hiện tại modalTypemodalProps từ state.modalđó nó được kết nối và kết xuất một thành phần tương ứng như DeletePostModalhoặc ConfirmLogoutModal. Mỗi phương thức là một thành phần!

Viết các thành phần phương thức cụ thể

Không có quy tắc chung ở đây. Chúng chỉ là các thành phần React có thể gửi hành động, đọc thứ gì đó từ trạng thái cửa hàng, và tình cờ là phương thức .

Ví dụ: DeletePostModalcó thể trông như:

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)

Các DeletePostModal được kết nối với cửa hàng để nó có thể hiển thị tiêu đề bài đăng và hoạt động giống như bất kỳ thành phần nào được kết nối: nó có thể gửi các hành động, kể cả hideModalkhi cần phải ẩn chính nó.

Trích xuất một thành phần trình bày

Sẽ rất khó khăn khi sao chép-dán cùng một logic bố cục cho mỗi phương thức cụ thể của Wap. Nhưng bạn có các thành phần, phải không? Vì vậy, bạn có thể trích xuất một trình bày <Modal> thành phần mà không biết phương thức cụ thể nào, nhưng xử lý chúng trông như thế nào.

Sau đó, các phương thức cụ thể như DeletePostModalcó thể sử dụng nó để kết xuất:

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)

Tùy thuộc vào bạn để đưa ra một bộ đạo cụ <Modal>có thể chấp nhận trong ứng dụng của bạn nhưng tôi sẽ tưởng tượng rằng bạn có thể có một số loại phương thức (ví dụ: phương thức thông tin, phương thức xác nhận, v.v.) và một số kiểu cho chúng.

Khả năng truy cập và ẩn trên Nhấp vào bên ngoài hoặc Phím thoát

Phần quan trọng cuối cùng về phương thức là nói chung chúng tôi muốn ẩn chúng khi người dùng nhấp vào bên ngoài hoặc nhấn Escape.

Thay vì cho bạn lời khuyên về việc thực hiện điều này, tôi khuyên bạn không nên tự mình thực hiện nó. Thật khó để có được quyền xem xét khả năng tiếp cận.

Thay vào đó, tôi sẽ đề nghị bạn sử dụng một thành phần phương thức có sẵn có thể truy cập như react-modal. Nó hoàn toàn có thể tùy chỉnh, bạn có thể đặt bất cứ thứ gì bạn muốn vào bên trong nó, nhưng nó xử lý khả năng truy cập chính xác để người mù vẫn có thể sử dụng phương thức của bạn.

Bạn thậm chí có thể react-modaltự mình <Modal>chấp nhận các đạo cụ cụ thể cho các ứng dụng của mình và tạo các nút con hoặc nội dung khác. Tất cả chỉ là thành phần!

Phương pháp khác

Có nhiều hơn một cách để làm điều đó.

Một số người không thích sự dài dòng của phương pháp này và thích có một <Modal>thành phần mà họ có thể kết xuất ngay bên trong các thành phần của mình bằng một kỹ thuật có tên là Cổng portals. Cổng cho phép bạn kết xuất một thành phần bên trong của bạn trong khi thực sự nó sẽ kết xuất tại một địa điểm được xác định trước trong DOM, rất thuận tiện cho các phương thức.

Trong thực tế, react-modaltôi đã liên kết với trước đó đã làm điều đó trong nội bộ vì vậy về mặt kỹ thuật, bạn thậm chí không cần phải hiển thị nó từ đầu. Tôi vẫn thấy tốt khi tách rời phương thức tôi muốn hiển thị từ thành phần hiển thị nó, nhưng bạn cũng có thể sử dụng react-modaltrực tiếp từ các thành phần của mình và bỏ qua hầu hết những gì tôi đã viết ở trên.

Tôi khuyến khích bạn xem xét cả hai cách tiếp cận, thử nghiệm với chúng và chọn những gì bạn thấy phù hợp nhất với ứng dụng của bạn và cho nhóm của bạn.


35
Một điều tôi khuyên là nên có bộ giảm tốc duy trì một danh sách các phương thức có thể được đẩy và bật. Nghe có vẻ ngớ ngẩn, tôi đã liên tục gặp phải tình huống trong đó các nhà thiết kế / loại sản phẩm muốn tôi mở một phương thức từ một phương thức, và thật tuyệt khi cho phép người dùng "quay lại".
Kyle

9
Vâng, chắc chắn, đây là loại điều Redux dễ dàng xây dựng vì bạn chỉ có thể thay đổi trạng thái của mình thành một mảng. Cá nhân tôi đã làm việc với các nhà thiết kế, ngược lại, muốn các phương thức là độc quyền, vì vậy cách tiếp cận tôi đã viết ra để giải quyết việc lồng ghép tình cờ. Nhưng vâng, bạn có thể có cả hai cách.
Dan Abramov

4
Theo kinh nghiệm của tôi, tôi muốn nói: nếu phương thức có liên quan đến một thành phần cục bộ (như phương thức xác nhận xóa có liên quan đến nút xóa), thì việc sử dụng cổng thông tin sẽ đơn giản hơn, nếu không thì sử dụng các hành động chuyển hướng. Đồng ý với @Kyle người ta sẽ có thể mở một phương thức từ một phương thức. Nó cũng hoạt động theo mặc định với các cổng vì chúng được thêm vào để ghi lại cơ thể để các cổng xếp chồng lên nhau một cách độc đáo (cho đến khi bạn làm rối tung mọi thứ với chỉ số z: p)
Sebastien Lorber

4
@DanAbramov, giải pháp của bạn rất tuyệt, nhưng tôi có một vấn đề nhỏ. Không có gì nghiêm trọng. Tôi sử dụng Material-ui trong dự án, khi đóng chế độ, nó chỉ tắt nó đi, thay vì "chơi" hoạt hình mờ dần. Có lẽ cần phải làm một số loại chậm trễ? Hoặc giữ mọi phương thức ở đó như một danh sách bên trong ModalRoot? Gợi ý?
gcerar

7
Đôi khi tôi muốn gọi một số chức năng nhất định sau khi phương thức đóng (ví dụ gọi các chức năng với các giá trị trường đầu vào bên trong phương thức). Tôi sẽ chuyển các chức năng này như là modalPropshành động. Điều này vi phạm quy tắc giữ trạng thái tuần tự hóa mặc dù. Làm thế nào tôi có thể khắc phục vấn đề này?
chmanie

98

Cập nhật : React 16.0 giới thiệu cổng thông qua ReactDOM.createPortal liên kết

Cập nhật : các phiên bản tiếp theo của React (Sợi: có thể là 16 hoặc 17) sẽ bao gồm một phương thức để tạo cổng: ReactDOM.unstable_createPortal() liên kết


Sử dụng cổng

Dan Abramov trả lời phần đầu là tốt, nhưng liên quan đến rất nhiều nồi hơi. Như ông nói, bạn cũng có thể sử dụng cổng. Tôi sẽ mở rộng một chút về ý tưởng đó.

Ưu điểm của cổng thông tin là cửa sổ bật lên và nút vẫn rất gần với cây React, với giao tiếp cha / con rất đơn giản bằng cách sử dụng đạo cụ: bạn có thể dễ dàng xử lý các hành động không đồng bộ với cổng hoặc để phụ huynh tùy chỉnh cổng.

Cổng thông tin là gì?

Cổng thông tin cho phép bạn kết xuất trực tiếp bên trong document.bodymột phần tử được lồng sâu vào cây React của bạn.

Ý tưởng là ví dụ bạn kết xuất thành thân cây React sau:

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

Và bạn nhận được như đầu ra:

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

Các inside-portalnút đã được dịch bên trong <body>, thay vì, nơi sâu-lồng nhau bình thường của nó.

Khi nào nên sử dụng cổng thông tin

Một cổng thông tin đặc biệt hữu ích để hiển thị các yếu tố nên vượt lên trên các thành phần React hiện có của bạn: cửa sổ bật lên, danh sách thả xuống, đề xuất, điểm nóng

Tại sao nên sử dụng cổng thông tin

Không có vấn đề về chỉ số z nữa : một cổng thông tin cho phép bạn kết xuất <body>. Nếu bạn muốn hiển thị cửa sổ bật lên hoặc thả xuống, đây là một ý tưởng thực sự hay nếu bạn không muốn phải chiến đấu chống lại các vấn đề về chỉ số z. Các thành phần cổng thông tin được thêm vào làm document.bodytheo thứ tự gắn kết, có nghĩa là trừ khi bạn chơi với z-index, hành vi mặc định sẽ là xếp chồng các cổng lên nhau, theo thứ tự gắn kết. Trong thực tế, điều đó có nghĩa là bạn có thể mở một cửa sổ bật lên một cách an toàn từ bên trong một cửa sổ bật lên khác và chắc chắn rằng cửa sổ bật lên thứ 2 sẽ được hiển thị trên đầu trang đầu tiên, mà không cần phải suy nghĩ vềz-index .

Trong thực tế

Đơn giản nhất: sử dụng trạng thái React cục bộ: nếu bạn nghĩ, đối với một cửa sổ bật lên xác nhận xóa đơn giản, không đáng để có bản tóm tắt Redux, thì bạn có thể sử dụng một cổng thông tin và nó đơn giản hóa rất nhiều mã của bạn. Đối với trường hợp sử dụng như vậy, trong đó tương tác rất cục bộ và thực sự là một chi tiết triển khai, bạn có thực sự quan tâm đến việc tải lại nóng, du hành thời gian, ghi nhật ký hành động và tất cả những lợi ích mà Redux mang lại cho bạn không? Cá nhân, tôi không và sử dụng nhà nước địa phương trong trường hợp này. Mã trở nên đơn giản như:

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>
    );
  }
}

Đơn giản: bạn vẫn có thể sử dụng trạng thái Redux : nếu bạn thực sự muốn, bạn vẫn có thể sử dụng connectđể chọn xem có DeleteConfirmationPopuphiển thị hay không. Vì cổng vẫn được lồng sâu trong cây React của bạn, nên việc tùy chỉnh hành vi của cổng này rất đơn giản vì cha mẹ bạn có thể truyền đạo cụ cho cổng. Nếu bạn không sử dụng cổng, bạn thường phải hiển thị cửa sổ bật lên của mình ở đầu cây React của bạn choz-index lý do và thường phải suy nghĩ về những điều như "làm cách nào để tùy chỉnh DeleteConfirmationPopup chung được xây dựng theo trường hợp sử dụng ". Và thông thường, bạn sẽ tìm thấy các giải pháp khá rắc rối cho vấn đề này, như gửi một hành động có chứa các hành động xác nhận / hủy bỏ lồng nhau, khóa gói dịch hoặc thậm chí tệ hơn là chức năng kết xuất (hoặc một cái gì đó không thể xác định được). Bạn không cần phải làm điều đó với các cổng và chỉ có thể vượt qua các đạo cụ thông thường, vì DeleteConfirmationPopupnó chỉ là một đứa trẻ củaDeleteButton

Phần kết luận

Cổng rất hữu ích để đơn giản hóa mã của bạn. Tôi không thể làm gì nếu không có họ nữa.

Lưu ý rằng việc triển khai cổng thông tin cũng có thể giúp bạn với các tính năng hữu ích khác như:

  • Khả năng tiếp cận
  • Phím tắt Espace để đóng cổng
  • Xử lý nhấp chuột bên ngoài (đóng cổng hoặc không)
  • Xử lý nhấp vào liên kết (đóng cổng hoặc không)
  • Phản ứng bối cảnh có sẵn trong cây cổng thông tin

Reac-Portal hoặc Reac-modal là tốt cho các cửa sổ bật lên, phương thức và lớp phủ nên toàn màn hình, thường tập trung ở giữa màn hình.

Reac-tether chưa được biết đến với hầu hết các nhà phát triển React, nhưng đây là một trong những công cụ hữu ích nhất mà bạn có thể tìm thấy ở đó. Tether cho phép bạn tạo các cổng, nhưng sẽ tự động định vị cổng thông tin, liên quan đến một mục tiêu nhất định. Điều này là hoàn hảo cho các chú giải công cụ, danh sách thả xuống, điểm nóng, hộp trợ giúp ... Nếu bạn đã từng gặp bất kỳ vấn đề nào với vị trí absolute/ relativez-indexhoặc trình đơn thả xuống của bạn bên ngoài chế độ xem của bạn, Tether sẽ giải quyết tất cả cho bạn.

Ví dụ, bạn có thể dễ dàng triển khai các điểm nóng trên tàu, mở rộng thành một tooltip sau khi nhấp:

Điểm nóng trên tàu

Mã sản xuất thực sự ở đây. Không thể đơn giản hơn :)

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

Chỉnh sửa : vừa phát hiện cổng phản ứng cho phép kết xuất cổng vào nút bạn chọn (không nhất thiết phải là cơ thể)

Chỉnh sửa : có vẻ như Reac-popper có thể là một sự thay thế hợp lý cho Reac-tether. PopperJS là một thư viện chỉ tính toán một vị trí thích hợp cho một phần tử, mà không cần chạm trực tiếp vào DOM, cho phép người dùng chọn vị trí và thời điểm anh ta muốn đặt nút DOM, trong khi Tether gắn trực tiếp vào phần thân.

Chỉnh sửa : cũng có phản ứng-slot-fill rất thú vị và có thể giúp giải quyết các vấn đề tương tự bằng cách cho phép kết xuất một phần tử thành một vị trí phần tử dành riêng mà bạn đặt bất cứ nơi nào bạn muốn trong cây của mình


Trong ví dụ của bạn đoạn trích, cửa sổ bật lên xác nhận sẽ không đóng nếu bạn xác nhận hành động (trái ngược với khi bạn nhấp vào Hủy)
dKab

Sẽ rất hữu ích khi bao gồm nhập Cổng thông tin của bạn vào đoạn mã. Thư viện nào <Portal>đến từ đâu? Tôi đoán đó là cổng thông tin phản ứng, nhưng chắc chắn sẽ rất tốt nếu biết.
đá

1
@skypecakes vui lòng coi việc triển khai của tôi là mã giả. Tôi đã không kiểm tra nó chống lại bất kỳ thư viện cụ thể. Tôi chỉ cố gắng để dạy các khái niệm ở đây không phải là một thực hiện cụ thể. Tôi đã quen với cổng thông tin phản ứng và mã ở trên sẽ hoạt động tốt với nó, nhưng nó sẽ hoạt động tốt với hầu hết các lib tương tự.
Sebastien Lorber

cổng phản ứng là tuyệt vời! Nó hỗ trợ kết xuất phía máy chủ :)
cyrilluce

Tôi là người mới bắt đầu nên sẽ rất vui khi được giải thích về cách tiếp cận này. Ngay cả khi bạn thực sự kết xuất phương thức ở một nơi khác, trong phương pháp này, bạn sẽ phải kiểm tra trên mỗi nút xóa nếu bạn nên kết xuất thể hiện cụ thể của phương thức. Trong cách tiếp cận redux tôi chỉ có một thể hiện của phương thức được hiển thị hay không. Nó không phải là một mối quan tâm hiệu suất?
Amit Neuhaus

9

Rất nhiều giải pháp tốt và bình luận có giá trị của các chuyên gia nổi tiếng từ cộng đồng JS về chủ đề này có thể được tìm thấy ở đây. Nó có thể là một chỉ báo rằng nó không phải là vấn đề tầm thường như nó có vẻ. Tôi nghĩ đây là lý do tại sao nó có thể là nguồn gốc của sự nghi ngờ và không chắc chắn về vấn đề này.

Vấn đề cơ bản ở đây là trong React, bạn chỉ được phép gắn thành phần vào cha mẹ của nó, đây không phải lúc nào cũng là hành vi mong muốn. Nhưng làm thế nào để giải quyết vấn đề này?

Tôi đề xuất giải pháp, giải quyết để khắc phục vấn đề này. Định nghĩa vấn đề chi tiết hơn, src và ví dụ có thể được tìm thấy ở đây: https://github.com/fckt/react-layer-stack#rationale

Cơ sở lý luận

react/ react-domđi kèm với 2 giả định / ý tưởng cơ bản:

  • mọi UI đều được phân cấp một cách tự nhiên. Đây là lý do tại sao chúng ta có ý tưởng componentsbao bọc lẫn nhau
  • react-dom Theo mặc định, thành phần con gắn kết với nút DOM cha của nó

Vấn đề là đôi khi tài sản thứ hai không phải là thứ bạn muốn trong trường hợp của bạn. Đôi khi bạn muốn gắn kết thành phần của mình vào nút DOM vật lý khác nhau và giữ kết nối hợp lý giữa cha mẹ và con cùng một lúc.

Ví dụ Canonical là thành phần giống như Tooltip: tại một số điểm của quá trình phát triển, bạn có thể thấy rằng bạn cần thêm một số mô tả cho mình UI element: nó sẽ hiển thị trong lớp cố định và nên biết tọa độ của nó (đó là UI elementtọa độ hoặc dây chuột) và tại đồng thời nó cần thông tin cho dù nó cần được hiển thị ngay bây giờ hay không, nội dung của nó và một số bối cảnh từ các thành phần cha mẹ. Ví dụ này cho thấy rằng đôi khi hệ thống phân cấp logic không khớp với phân cấp DOM vật lý.

Hãy xem https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example để xem ví dụ cụ thể trả lời cho câu hỏi của bạn:

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>)
// ...

2

Theo tôi việc thực hiện tối thiểu trần có hai yêu cầu. Một trạng thái theo dõi xem phương thức có mở hay không và một cổng thông tin để hiển thị phương thức bên ngoài cây phản ứng chuẩn.

Thành phần ModalContainer bên dưới thực hiện các yêu cầu đó cùng với các hàm kết xuất tương ứng cho phương thức và trình kích hoạt, chịu trách nhiệm thực hiện cuộc gọi lại để mở phương thức.

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;

Và đây là một trường hợp sử dụng đơn giản ...

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;

Tôi sử dụng các hàm kết xuất, vì tôi muốn tách biệt quản lý trạng thái và logic soạn sẵn khỏi việc thực hiện thành phần kết xuất và kích hoạt. Điều này cho phép các thành phần được kết xuất là bất cứ thứ gì bạn muốn. Trong trường hợp của bạn, tôi cho rằng thành phần phương thức có thể là thành phần được kết nối nhận chức năng gọi lại gửi một hành động không đồng bộ.

Nếu bạn cần gửi đạo cụ động đến thành phần mô đun từ thành phần kích hoạt, hy vọng điều đó không xảy ra quá thường xuyên, tôi khuyên bạn nên bọc ModalContainer bằng một thành phần chứa để quản lý đạo cụ động ở trạng thái riêng và tăng cường các phương thức kết xuất ban đầu như vì thế.

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;

0

Gói phương thức vào một thùng chứa được kết nối và thực hiện thao tác không đồng bộ tại đây. Bằng cách này, bạn có thể tiếp cận cả công văn để kích hoạt các hành động và prop onC Đóng cũng vậy. Để đạt được dispatchtừ đạo cụ, không chuyển mapDispatchToPropschức năng đến connect.

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);

Ứng dụng nơi chế độ được hiển thị và trạng thái hiển thị của nó được đặt:

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

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

  ...

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

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.