ฉันจะแสดงกล่องโต้ตอบโมดัลใน Redux ที่ดำเนินการแบบอะซิงโครนัสได้อย่างไร


240

ฉันกำลังสร้างแอพที่จำเป็นต้องแสดงข้อความยืนยันในบางสถานการณ์

สมมติว่าฉันต้องการลบบางอย่างจากนั้นฉันจะส่งการกระทำเช่นdeleteSomething(id)นั้นบางตัวลดจะจับเหตุการณ์นั้นและจะเติมตัวลดการโต้ตอบเพื่อแสดง

ข้อสงสัยของฉันเกิดขึ้นเมื่อกล่องโต้ตอบนี้ส่ง

  • ส่วนประกอบนี้จะส่งการกระทำที่เหมาะสมตามการกระทำแรกที่ส่งไปได้อย่างไร
  • ผู้สร้างแอ็คชั่นควรจัดการกับตรรกะนี้หรือไม่?
  • เราสามารถเพิ่มการกระทำภายในตัวลดได้หรือไม่?

แก้ไข:

เพื่อให้ชัดเจน:

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

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

ดังนั้นฉันจึงพยายามใช้องค์ประกอบการโต้ตอบใหม่ การแสดง / ซ่อนกล่องโต้ตอบไม่ใช่ปัญหาเพราะสามารถทำได้อย่างง่ายดายในตัวลด สิ่งที่ฉันพยายามระบุก็คือวิธีส่งการกระทำจากด้านขวาตามการกระทำที่เริ่มต้นการไหลทางด้านซ้าย


1
ฉันคิดว่าในกรณีของคุณสถานะของการสนทนา (ซ่อน / แสดง) เป็นของท้องถิ่น ฉันจะเลือกที่จะใช้สถานะการตอบสนองในการจัดการการแสดงโต้ตอบ / ซ่อน ด้วยวิธีนี้คำถามของ "การกระทำที่เหมาะสมตามการกระทำแรก" จะหายไป
Ming

คำตอบ:


516

วิธีที่ฉันแนะนำคือ verbose เล็กน้อย แต่ฉันพบว่ามันทำให้สเกลค่อนข้างดีในแอพที่ซับซ้อน เมื่อคุณต้องการที่จะแสดงคำกริยาไฟดำเนินการอธิบายที่กิริยาช่วยที่คุณต้องการที่จะเห็น:

ส่งการกระทำเพื่อแสดงเป็นกิริยาช่วย

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

(สตริงอาจเป็นค่าคงที่แน่นอน; ฉันใช้สตริงแบบอินไลน์เพื่อความเรียบง่าย)

เขียน Reducer เพื่อจัดการ Modal State

จากนั้นตรวจสอบให้แน่ใจว่าคุณมีตัวลดที่เพิ่งยอมรับค่าเหล่านี้:

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จะอัปเดตเพื่อรวมข้อมูลเกี่ยวกับหน้าต่างโมดัลที่มองเห็นได้ในปัจจุบัน

การเขียนคอมโพเนนต์ Modal Root

ที่รากของลำดับชั้นส่วนประกอบของคุณเพิ่ม<ModalRoot>คอมโพเนนต์ที่เชื่อมต่อกับที่เก็บ Redux มันจะฟัง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)

พวกเราทำอะไรที่นี่? ModalRootอ่านปัจจุบันmodalTypeและmodalPropsจากstate.modalที่มันเชื่อมต่อและแสดงองค์ประกอบที่สอดคล้องกันเช่นDeletePostModalหรือConfirmLogoutModalหรือทุกคำกริยาเป็นองค์ประกอบ!

การเขียนส่วนประกอบของคำกริยาเฉพาะ

ไม่มีกฎทั่วไปที่นี่ พวกเขาเป็นเพียงการตอบสนองส่วนประกอบที่สามารถส่งการกระทำอ่านอะไรจากรัฐจัดเก็บและเพิ่งเกิดขึ้นจะเป็นโครงสร้างข้างล่าง

ตัวอย่างเช่น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 แต่คุณมีส่วนประกอบใช่มั้ย ดังนั้นคุณสามารถแยกงานนำเสนอได้ <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>สามารถยอมรับได้ในแอปพลิเคชันของคุณ แต่ฉันคิดว่าคุณอาจมี modal หลายชนิด (เช่น modal ข้อมูล, modal ยืนยัน, ฯลฯ ) และหลายสไตล์สำหรับพวกเขา

การเข้าถึงและการซ่อนคลิกที่ด้านนอกหรือปุ่ม Escape

ส่วนที่สำคัญสุดท้ายเกี่ยวกับ modals คือโดยทั่วไปแล้วเราต้องการซ่อนพวกเขาเมื่อผู้ใช้คลิกนอกหรือกด Escape

แทนที่จะให้คำแนะนำกับคุณเกี่ยวกับการดำเนินการนี้ฉันขอแนะนำให้คุณไม่ใช้ด้วยตนเอง เป็นการยากที่จะพิจารณาการเข้าถึงที่ถูกต้อง

แต่ฉันขอแนะนำให้คุณใช้สามารถเข้าถึงได้ปิด -the-shelf react-modalกิริยาส่วนประกอบเช่น มันสามารถปรับแต่งได้อย่างสมบูรณ์คุณสามารถใส่ทุกอย่างที่คุณต้องการได้ แต่มันจัดการการเข้าถึงได้อย่างถูกต้องเพื่อให้คนตาบอดยังคงสามารถใช้คำกริยาของคุณได้

คุณสามารถห่อreact-modalของคุณเอง<Modal>ที่ยอมรับอุปกรณ์ประกอบฉากเฉพาะสำหรับแอปพลิเคชันของคุณและสร้างปุ่มลูกหรือเนื้อหาอื่น ๆ มันเป็นแค่ส่วนประกอบ!

แนวทางอื่น ๆ

มีมากกว่าหนึ่งวิธีที่จะทำ

บางคนไม่ชอบการใช้คำฟุ่มเฟือยของวิธีการนี้และชอบที่จะมี<Modal>ส่วนประกอบที่สามารถแสดงผลภายในส่วนประกอบด้วยเทคนิคที่เรียกว่า "พอร์ทัล" พอร์ทัลช่วยให้คุณแสดงองค์ประกอบภายในของคุณในขณะที่จริงมันจะแสดงผลในสถานที่ที่กำหนดไว้ใน DOM ซึ่งสะดวกมากสำหรับ modals

ในความเป็นจริงreact-modalฉันเชื่อมโยงกับก่อนหน้านี้แล้วว่าภายในดังนั้นในทางเทคนิคคุณไม่จำเป็นต้องแสดงมันจากด้านบน ฉันยังพบว่ามันดีที่จะแยกโมดอลที่ต้องการแสดงจากส่วนประกอบที่แสดง แต่คุณสามารถใช้react-modalโดยตรงจากส่วนประกอบของคุณและข้ามสิ่งที่ฉันเขียนด้านบนไปส่วนใหญ่

ฉันขอแนะนำให้คุณพิจารณาทั้งสองวิธีทดลองกับพวกเขาและเลือกสิ่งที่คุณพบว่าใช้ได้ผลดีที่สุดสำหรับแอปและทีมของคุณ


35
สิ่งหนึ่งที่ฉันแนะนำคือให้ตัวลดมีรายการโมเดอเรเตอร์ที่สามารถผลักและผุดได้ มันฟังดูแปลก ๆ ฉันพบเจอกับสถานการณ์ที่นักออกแบบ / ประเภทผลิตภัณฑ์ต้องการให้ฉันเปิดคำกริยาจากคำกริยาและมันก็ดีที่อนุญาตให้ผู้ใช้ "ย้อนกลับ"
Kyle

9
ใช่แน่นอนนี่คือสิ่งที่ Redux สร้างได้ง่ายเพราะคุณสามารถเปลี่ยนสถานะของคุณให้เป็นอาร์เรย์ได้ โดยส่วนตัวแล้วฉันได้ทำงานร่วมกับนักออกแบบที่ตรงกันข้ามต้องการให้ modals เป็นเอกสิทธิ์ดังนั้นวิธีที่ฉันเขียนขึ้นแก้ปัญหาการทำรังโดยไม่ตั้งใจ แต่ใช่คุณสามารถมีได้ทั้งสองวิธี
Dan Abramov

4
จากประสบการณ์ของฉันฉันจะพูดว่า: ถ้า modal เกี่ยวข้องกับส่วนประกอบในตัวเครื่อง (เช่น modal การยืนยันการลบเกี่ยวข้องกับปุ่มลบ) มันง่ายกว่าที่จะใช้พอร์ทัลมิฉะนั้นจะใช้การกระทำของ redux เห็นด้วยกับ @Kyle หนึ่งควรจะสามารถเปิดเป็นกิริยาช่วยจาก modal นอกจากนี้ยังใช้งานได้ตามค่าเริ่มต้นกับพอร์ทัลเนื่องจากมีการเพิ่มเพื่อจัดทำเอกสารเพื่อให้พอร์ทัลมีกองซ้อนกัน (จนกว่าคุณจะทำทุกอย่างยุ่งกับดัชนี z: p)
Sebastien Lorber

4
@ DanAbramov ทางออกของคุณยอดเยี่ยม แต่ฉันมีปัญหาเล็กน้อย ไม่มีอะไรจริงจัง ฉันใช้ Material-ui ในโปรเจคเมื่อปิดโมดอลมันก็แค่ปิดมันแทนการ "เล่น" อนิเมชั่นจางหายไป อาจจะต้องมีการล่าช้าบ้างไหม? หรือเก็บทุก modal ไว้ในรายการภายใน ModalRoot ข้อเสนอแนะ?
gcerar

7
บางครั้งฉันต้องการเรียกบางฟังก์ชั่นหลังจากโมดัลปิด (เช่นเรียกฟังก์ชั่นที่มีค่าฟิลด์อินพุตภายในโมดอล) ฉันจะส่งผ่านฟังก์ชั่นเหล่านี้เป็นการmodalPropsกระทำ สิ่งนี้ละเมิดกฎการรักษาสถานะที่เป็นอนุกรมแม้ว่า ฉันจะเอาชนะปัญหานี้ได้อย่างไร
chmanie

98

อัปเดต : ตอบสนอง 16.0 แนะนำพอร์ทัลผ่านReactDOM.createPortal ลิงก์

อัปเดต : React เวอร์ชันถัดไป (ไฟเบอร์: อาจเป็น 16 หรือ 17) จะรวมวิธีการสร้างพอร์ทัล: ReactDOM.unstable_createPortal() ลิงก์


ใช้พอร์ทัล

Dan Abramov ตอบส่วนแรกได้ดี แต่เกี่ยวข้องกับสำเร็จรูปจำนวนมาก อย่างที่เขาพูดคุณสามารถใช้พอร์ทัล ฉันจะขยายความคิดนั้นเล็กน้อย

ข้อได้เปรียบของพอร์ทัลคือป๊อปอัพและปุ่มนั้นยังคงอยู่ใกล้กับแผนผัง React ด้วยการสื่อสารผู้ปกครอง / ลูกอย่างง่ายโดยใช้อุปกรณ์ประกอบฉาก: คุณสามารถจัดการแอ็คชั่น async กับพอร์ทัลหรือให้ผู้ปกครองปรับแต่งพอร์ทัล

พอร์ทัลคืออะไร

พอร์ทัลอนุญาตให้คุณแสดงผลโดยตรงภายในdocument.bodyองค์ประกอบที่ซ้อนกันอย่างลึกในทรีปฏิกิริยาของคุณ

แนวคิดคือตัวอย่างเช่นคุณแสดงผลต้นไม้ตอบสนองต่อร่างกาย:

<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 อีกต่อไป<body>คือใบอนุญาตพอร์ทัลคุณจะทำให้การ หากคุณต้องการแสดงป๊อปอัพหรือดรอปดาวน์นี่เป็นความคิดที่ดีมากหากคุณไม่ต้องการต่อสู้กับปัญหาดัชนี z องค์ประกอบพอร์ทัลที่ได้รับการเพิ่มทำdocument.bodyตามลำดับการติดตั้งซึ่งหมายความว่าถ้าคุณไม่เล่นด้วยz-indexพฤติกรรมเริ่มต้นคือการสแต็กพอร์ทัลที่ด้านบนของกันและกันในลำดับการติดตั้ง ในทางปฏิบัติมันหมายความว่าคุณสามารถเปิดป๊อปอัพจากภายในป๊อปอัพอีกและให้แน่ใจว่าป๊อปอัพที่ 2 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>
    );
  }
}

ง่าย: คุณยังคงสามารถใช้สถานะ Redux : ถ้าคุณต้องการจริงๆคุณยังสามารถใช้connectเพื่อเลือกว่าDeleteConfirmationPopupจะแสดงหรือไม่ เนื่องจากพอร์ทัลยังคงซ้อนอยู่ในทรีปฏิกิริยาของคุณมันง่ายมากในการปรับแต่งพฤติกรรมของพอร์ทัลนี้เพราะผู้ปกครองของคุณสามารถส่งอุปกรณ์ประกอบฉากไปยังพอร์ทัล หากคุณไม่ได้ใช้พอร์ทัลคุณจะต้องแสดงป๊อปอัปที่ด้านบนของแผนผังการตอบสนองของคุณz-indexเหตุผลและมักจะต้องคิดเกี่ยวกับสิ่งต่าง ๆ เช่น "ฉันจะกำหนด DeleteConfirmationPopup ทั่วไปที่ฉันสร้างขึ้นตามกรณีการใช้งาน" ได้อย่างไร และโดยปกติคุณจะพบวิธีแก้ไขปัญหาแฮ็คค่อนข้างมากเช่นการส่งการกระทำที่มีการยืนยันซ้อน / ยกเลิกการดำเนินการคีย์มัดแปลหรือแย่กว่านั้นฟังก์ชั่นการแสดงผล (หรือสิ่งอื่นที่ไม่สามารถระบุได้) คุณไม่จำเป็นต้องทำเช่นนั้นกับพอร์ทัลและสามารถส่งผ่านอุปกรณ์ประกอบฉากปกติได้เนื่องจากDeleteConfirmationPopupเป็นเพียงลูกของDeleteButton

ข้อสรุป

พอร์ทัลมีประโยชน์อย่างมากในการทำให้รหัสของคุณง่ายขึ้น ฉันทำไม่ได้หากไม่มีพวกเขาอีกต่อไป

โปรดทราบว่าการใช้งานพอร์ทัลสามารถช่วยคุณในคุณสมบัติที่มีประโยชน์อื่น ๆ เช่น

  • การเข้าถึง
  • ทางลัด Espace เพื่อปิดพอร์ทัล
  • จัดการกับการคลิกภายนอก (ปิดพอร์ทัลหรือไม่)
  • คลิกลิงก์ที่จับ (ปิดพอร์ทัลหรือไม่)
  • ทำซ้ำบริบทให้พร้อมใช้งานในทรีพอร์ทัล

react-portalหรือreact-modalเหมาะสำหรับป๊อปอัปโมดและการซ้อนทับที่ควรเป็นแบบเต็มหน้าจอโดยทั่วไปจะอยู่กึ่งกลางหน้าจอ

ตอบสนองโยงไม่เป็นที่รู้จักมากที่สุดตอบสนองนักพัฒนายังเป็นหนึ่งในเครื่องมือที่มีประโยชน์มากที่สุดคุณจะพบว่ามี Tetherอนุญาตให้คุณสร้างพอร์ทัล แต่จะวางตำแหน่งพอร์ทัลโดยอัตโนมัติโดยสัมพันธ์กับเป้าหมายที่กำหนด นี่เหมาะสำหรับเคล็ดลับเครื่องมือดร็อปดาวน์ฮอตสปอตความช่วยเหลือ ... หากคุณเคยมีปัญหากับตำแหน่งabsolute/ relativeและz-indexหรือเลื่อนลงไปด้านนอกวิวพอร์ตของคุณ Tether จะแก้ปัญหาทั้งหมดให้คุณ

ตัวอย่างเช่นคุณสามารถนำฮอตสปอตไปใช้งานได้อย่างง่ายดายซึ่งขยายไปยังเคล็ดลับเครื่องมือเมื่อคลิกแล้ว:

ออนบอร์ดฮอตสปอต

รหัสการผลิตจริงที่นี่ ไม่ง่ายกว่านี้ :)

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

แก้ไข : เพิ่งค้นพบreact-gatewayซึ่งอนุญาตให้เรนเดอร์พอร์ทัลเป็นโหนดที่คุณเลือก (ไม่จำเป็นต้องเป็น body)

แก้ไข : ดูเหมือนว่าreact-popperอาจเป็นทางเลือกที่ดีในการตอบสนองต่อโยง PopperJSเป็นห้องสมุดที่คำนวณเฉพาะตำแหน่งที่เหมาะสมสำหรับองค์ประกอบโดยไม่ต้องสัมผัส DOM โดยตรงให้ผู้ใช้เลือกตำแหน่งและเวลาที่เขาต้องการใส่โหนด DOM ในขณะที่ Tether ต่อท้ายโดยตรงกับร่างกาย

แก้ไข : นอกจากนี้ยังมีreact-slot-fillที่น่าสนใจและสามารถช่วยแก้ปัญหาที่คล้ายกันได้โดยอนุญาตให้แสดงองค์ประกอบไปยังสล็อตองค์ประกอบที่สงวนไว้ซึ่งคุณใส่ทุกที่ที่คุณต้องการในต้นไม้


ในตัวอย่างของคุณตัวอย่างป๊อปอัพการยืนยันจะไม่ปิดหากคุณยืนยันการกระทำ (ตรงข้ามกับเมื่อคุณคลิกที่ยกเลิก)
dKab

การรวมการนำเข้าพอร์ทัลของคุณในตัวอย่างโค้ดจะเป็นประโยชน์ ห้องสมุดอะไร<Portal>มาจากไหน ฉันเดาว่ามันตอบสนองต่อพอร์ทัล แต่มันก็ดีที่จะรู้แน่นอน
ศิลา

1
@skypecakes โปรดพิจารณาการใช้งานของฉันเป็นรหัสหลอก ฉันไม่ได้ทดสอบกับห้องสมุดที่เป็นรูปธรรม ฉันแค่พยายามสอนแนวคิดที่นี่ไม่ใช่การดำเนินการอย่างเป็นรูปธรรม ฉันคุ้นเคยกับการตอบโต้พอร์ทัลและรหัสด้านบนควรทำงานได้ดี แต่ควรทำงานได้ดีกับ lib ที่คล้ายกันเกือบทุกชนิด
Sebastien Lorber

react-gateway น่ากลัว! มันรองรับการเรนเดอร์ฝั่งเซิร์ฟเวอร์ :)
cyrilluce

ฉันเริ่มต้นสวยดังนั้นจะมีความสุขมากสำหรับคำอธิบายเกี่ยวกับวิธีการนี้ แม้ว่าคุณจะทำให้เป็นกิริยาช่วยจริงในที่อื่นในวิธีนี้คุณจะต้องตรวจสอบทุกปุ่มลบหากคุณควรแสดงตัวอย่างเฉพาะของ Modal ในวิธีการ redux ฉันมีเพียงหนึ่งตัวอย่างของคำกริยาที่แสดงหรือไม่ มันเกี่ยวกับประสิทธิภาพหรือไม่?
Amit Neuhaus

9

โซลูชั่นที่ดีและข้อคิดเห็นที่มีค่ามากมายโดยผู้เชี่ยวชาญที่รู้จักจากชุมชน JS ในหัวข้อสามารถพบได้ที่นี่ มันอาจเป็นตัวบ่งชี้ว่ามันไม่ใช่ปัญหาเล็ก ๆ น้อย ๆ ที่ดูเหมือน ฉันคิดว่านี่เป็นสาเหตุที่อาจเป็นสาเหตุของความสงสัยและความไม่แน่นอนเกี่ยวกับปัญหา

ปัญหาพื้นฐานที่นี่คือในการตอบสนองคุณจะได้รับอนุญาตให้ติดตั้งส่วนประกอบกับผู้ปกครองเท่านั้นซึ่งไม่ใช่พฤติกรรมที่ต้องการเสมอไป แต่จะแก้ไขปัญหานี้อย่างไร

ฉันเสนอวิธีแก้ไขเพื่อแก้ไขปัญหานี้ คุณสามารถดูคำจำกัดความปัญหาโดยละเอียด src และตัวอย่างได้ที่นี่: https://github.com/fckt/react-layer-stack#rationale

หลักการและเหตุผล

react/ react-domมาพร้อมกับสมมติฐาน / แนวคิดพื้นฐาน 2 ประการ:

  • UI ทุกตัวเป็นลำดับชั้นตามธรรมชาติ นี่คือเหตุผลที่เรามีความคิดcomponentsที่ห่อซึ่งกันและกัน
  • react-dom เมาท์ (องค์ประกอบทางกายภาพ) ชายด์ของโหนด DOM พาเรนต์โดยค่าเริ่มต้น

ปัญหาคือบางครั้งคุณสมบัติที่สองไม่ใช่สิ่งที่คุณต้องการในกรณีของคุณ บางครั้งคุณต้องการติดตั้งส่วนประกอบของคุณลงในโหนด DOM ทางกายภาพที่แตกต่างกันและระงับการเชื่อมต่อแบบโลจิคัลระหว่างพาเรนต์และชายด์ในเวลาเดียวกัน

ตัวอย่าง Canonical เป็นองค์ประกอบเหมือน Tooltip: ณ จุดหนึ่งของกระบวนการพัฒนาคุณสามารถค้นหาว่าคุณจำเป็นต้องเพิ่มคำอธิบายสำหรับของคุณUI element: มันจะแสดงในเลเยอร์คงที่และควรทราบพิกัดของมัน (ซึ่งเป็นUI elementcoord หรือเมาส์ 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>)
// ...

2

ในความคิดของฉันการดำเนินการขั้นต่ำเปล่ามีสองข้อกำหนด สถานะที่ติดตามว่า modal เปิดอยู่หรือไม่และพอร์ทัลเพื่อสร้าง modal ภายนอกแผนผังทรีปฏิกิริยา

องค์ประกอบ ModalContainer ด้านล่างใช้ข้อกำหนดเหล่านั้นพร้อมกับฟังก์ชั่นการแสดงผลที่สอดคล้องกันสำหรับ modal และทริกเกอร์ซึ่งมีหน้าที่ในการดำเนินการโทรกลับเพื่อเปิด modal

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;

ฉันใช้ฟังก์ชั่นการแสดงผลเพราะฉันต้องการแยกการจัดการสถานะและตรรกะสำเร็จรูปจากการใช้งานของโมเดอเรเตอร์ที่แสดงผลและองค์ประกอบทริกเกอร์ สิ่งนี้จะช่วยให้องค์ประกอบที่แสดงผลเป็นสิ่งที่คุณต้องการให้เป็น ในกรณีของคุณฉันคิดว่าองค์ประกอบ modal อาจเป็นองค์ประกอบที่เชื่อมต่อที่ได้รับฟังก์ชั่นการโทรกลับที่ยื้อการกระทำแบบอะซิงโครนัส

หากคุณต้องการส่งอุปกรณ์ประกอบฉากแบบไดนามิกไปยังองค์ประกอบ modal จากองค์ประกอบทริกเกอร์ซึ่งหวังว่าจะไม่เกิดขึ้นบ่อยเกินไปฉันขอแนะนำให้ห่อ ModalContainer ด้วยส่วนประกอบภาชนะที่จัดการอุปกรณ์ประกอบฉากแบบไดนามิกในรัฐของตัวเองและปรับปรุงวิธีการแสดงผลดั้งเดิมเช่น ดังนั้น.

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

ห่อโมดอลลงในคอนเทนเนอร์ที่เชื่อมต่อแล้วดำเนินการ async ที่นี่ วิธีนี้คุณสามารถเข้าถึงทั้งการแจกจ่ายเพื่อกระตุ้นการดำเนินการและการปิดบนเสาเช่นกัน ในการเข้าถึง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} />  
      ...
    )
  }

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.