Logo
Section 1 / 9

添加模态对话框支持

用 Redux 描述并渲染 React 模态对话框,搭建 ModalManager 与 reducer

导入

添加模态对话框支持

用 Redux 描述并渲染 React 模态对话框,搭建 ModalManager 与 reducer

正如你可能已经注意到的,本课程的目的并不是构建一个功能完整且有意义的应用程序。它的主要目标是让我有机会展示一些实用的 React 和 Redux 技术。这意味着其中会有一些明显过度设计的地方。比如模态对话框——这个应用其实并不需要模态对话框,但没关系:我们还是要给 Project Mini-Mek 加上模态对话框 :)

在 React 中处理模态对话框并用 Redux 驱动它们的基本概念已经在其他地方被多次详细描述过。我的参考链接列表中包含许多相关文章,以下是精选阅读清单:

我们将把这些概念付诸实践,并在某些方面进一步拓展。

用 Redux 驱动 React 模态对话框

我们先来搭建显示单个模态对话框所需的各个组件。我们需要以下几个部分:

  • 一个 ModalManager 组件,它接收要显示的模态组件的描述、该模态应传入的 props,以及一个可用模态组件的查找表,并根据描述渲染对应的模态组件;
  • 用于存储和清除当前模态描述的动作(actions)和 reducers;
  • 一个实际要显示的模态组件。

提交 a19f234:添加对单个模态对话框的基本处理

features/modals/modalsReducer.js

1import { 2 MODAL_CLOSE, 3 MODAL_OPEN 4} from "./modalConstants"; 5 6import {createReducer} from "common/utils/reducerUtils"; 7 8const initialState = null; 9 10export function openModal(state, payload) { 11 const {modalType, modalProps} = payload; 12 return {modalType, modalProps}; 13} 14 15export function closeModal(state, payload) { 16 return null; 17} 18 19export default createReducer(initialState, { 20 [MODAL_OPEN]: openModal, 21 [MODAL_CLOSE]: closeModal 22});

这个 reducer 的逻辑非常简单:我们只需存储一个包含模态类型名称和其应接收的 props 的对象,然后设置或清空该值。

features/modals/TestModal.jsx

1import React, {Component} from "react"; 2import {connect} from "react-redux"; 3import { 4 Modal, 5} from "semantic-ui-react"; 6 7import {closeModal} from "features/modals/modalActions"; 8 9const actions = {closeModal}; 10 11export class TestModal extends Component { 12 render() { 13 return ( 14 <Modal 15 closeIcon="close" 16 open={true} 17 onClose={this.props.closeModal} 18 > 19 <Modal.Header>模态 #1</Modal.Header> 20 <Modal.Content image> 21 <Modal.Description> 22 <p>这是一个模态对话框。很酷吧?</p> 23 </Modal.Description> 24 </Modal.Content> 25 <Modal.Actions> 26 </Modal.Actions> 27 </Modal> 28 ) 29 } 30} 31 32export default connect(null, actions)(TestModal);

Semantic-UI-React 已经提供了一个 Modal 类,可以方便地渲染标题、内容和操作按钮。由于本文的重点是展示如何通过 React 和 Redux 来操控模态对话框,而不是从零开始构建模态组件,因此我们将直接使用 SUI-React 内置的 Modal 组件,而不是自行实现。

注意我们向 <Modal> 传递了 open={true} 属性。这是因为 SUI-React 的 Modal 可以处于打开或关闭状态,而在我们的场景中,每当显示 <TestModal> 时,内部的 <Modal> 都应该被展示出来。

features/modals/ModalManager.jsx

1import React, {Component} from "react"; 2import {connect} from "react-redux"; 3 4import TestModal from "./TestModal"; 5 6const modalComponentLookupTable = { 7 TestModal 8}; 9 10const mapState = (state) => ({currentModal: state.modals}); 11 12export class ModalManager extends Component { 13 render() { 14 const {currentModal} = this.props; 15 16 let renderedModal; 17 18 if (currentModal) { 19 const {modalType, modalProps = {}} = currentModal; 20 const ModalComponent = modalComponentLookupTable[modalType]; 21 22 renderedModal = <ModalComponent {...modalProps} />; 23 } 24 25 return <span>{renderedModal}</span> 26 } 27} 28 29export default connect(mapState)(ModalManager);

现在我们进入了基本模态对话框架构的核心部分。不过,在深入之前,先回顾一下背后的理论。

在典型的面向对象 GUI 工具包中,你可能会写类似 const myModal = new MyModal(); myModal.show() 的代码,由模态类自己负责显示。jQuery 风格的 UI 插件也是如此。此时,你的应用程序中存在某种“隐式状态”:应用正在“显示一个模态”,但除了创建了 MyModal 实例,或者在网页中新增了元素之外,没有明确的方式来追踪这一状态。这种“是否正在显示模态”的状态本质上是隐式的,而并未被显式记录。

但在 React(尤其是结合 Redux 时),我们被鼓励尽可能让所有状态都变得显式。 这也是为什么你会经常看到像 requesting: true 这样的状态值——因为现在你可以明确地根据是否有正在进行的 AJAX 请求来决定 UI 行为(例如显示加载动画)。在我们的场景中,我们希望显式地跟踪当前是否正在显示模态,甚至更进一步,记录下当前模态的具体描述信息。

我们的 <ModalManager> 组件将位于应用组件树的较顶层。它从 Redux 中读取当前的模态描述。如果存在描述对象,我们就需要真正地显示一个模态。

我们建立一个查找表,键是某个字符串标识符,值是具体的模态组件。在这个例子中,我们直接使用变量名作为键(利用 ES6 对象字面量的简写语法),因此当 modalType 是 "TestModal" 时,就会取出对应的组件类。一旦获得了正确的模态组件类,就可以使用标准的 JSX 语法进行渲染。同时,我们还可以将描述中包含的任何 props 对象传递给该模态组件。

最终效果如下:

Course Image

一个模态对话框!还有背景遮罩!是不是很激动? :)

从代码角度看,这个思路的核心就这些:使用一个描述性值来判断是否应该显示模态,如果要显示,则确定是哪一个,然后渲染它并传入所需 props。但我们可以在此基础上进行大量有用的扩展。

表单草稿数据管理

使模态框可堆叠