React项目中hook实现展示对话框功能

目录
  • 思路:使用全局状态管理所有对话框
  • 尝试设计一个API去做对话框的全局管理
  • 实现:创建NiceModal组件和相关API
  • 处理对话框的返回值
  • 总结

React中使用对话框并不容易,主要因为:

  1. 对话框需要在父组件中声明,才能在子组件中控制其是否显示
  2. 给对话框传递参数只能由props传入,这意味着所有状态管理都必须在更高阶的组件中。而实际上这个对话框的参数只在子组件中才会维护。这时就需要我们使用自定义事件将参数传回

这些问题的本质就是:如何用一个统一的方式去管理对话框,从而让对话框相关的业务逻辑更加模块化,以及和其他业务逻辑进行解耦。

下面的方式只是经验总结,并不是唯一或者最佳实现:

思路:使用全局状态管理所有对话框

对话框本质上是独立于其他界面的一个窗口,用于完成一个独立的功能。

所以,定义一个对话框,定位等价于定义一个具有唯一URL路径的页面。只是前者由弹出层实现,后者是页面的切换。

对话框UI弹出过程和页面URL的切换非常类似,那么我们就可以给每一个对话框定义一个全局唯一的ID,然后通过这个ID去显示或者隐藏一个对话框,并且给它传递参数。

尝试设计一个API去做对话框的全局管理

假设我们实现的对话框为NiceModal,那么我们的目标是如下去使用:

const UserInfoModal = NiceModal.create(
    'user-info-modal',
    RealUserInfoModal
)

// 创建一个useNiceModal 这样的hook去获取某个id的对话框的操作对象
const modal = useNiceModal('user-info-modal')

// 通过 modal.show 显示一个对话框,并能够给它传递参数
modal.show(args)
modal.hide()

可以看到,如果有这样的API,那么无论在哪个层级的组件,只要知道某个Modal的ID,那么就都可以统一使用这些对话框,而不再需要考虑该在哪个层级的组件去定义了。

实现:创建NiceModal组件和相关API

创建一个处理所有对话框的action creator 和 reducer

function showModal(modalId, args) {
    return {
        type: "nice-modal/show",
        payload: {
            modalId,
            args
        }
    }
}

function hideModal(modalId, force) {
    return {
        type: "nice-modal/hide",
        payload: {
            modalId,
            force
        }
    }
}
const modalReducer = (state = { hiding: {} }, action) {
    switch (action.type) {
        case "nice-modal/show":
            const {modalId, args} = action.payload
            return {
                ...state,
                // 如果存在 modalId 对应的状态(即args),就显示这个对话框
                // 只要有参数就认为对话框应该显示,如果没有传递args,在reducer中使用默认值true
                [modalId]: args || true,
                // 定义一个hiding 状态, 用于处理对话框关闭动画
                hiding: {
                    ...state.hiding,
                    [modalId]: false,
                }
            }
        case "nice-modal/hide":
            const { modalId, force: boolean } = action.payload
            // 只有force时才真正移除对话框,否则就是隐藏中hiding
            return action.payload.force
                ? {
                    ...state,
                    [modalId]: false,
                    hiding: { [modalId]: false }
                  }
                : { ...state, hiding: { [modalId]: true } }
            default:
                return state
    }
}

这段代码的主要思路就是通过Redux的store去存储每个对话框状态和参数。在这里设计了两个action,分别显示和隐藏对话框。

特别注意的是,这里加入了hiding这样的一个状态,用来处理对话框关闭过程动画。

根据使用顺序,首先实现 createNiceModal,

使用容器模式,在对话框不可见时直接返回null,从而不渲染任何内容,

确保即使页面上定义了100个对话框,也不会影响性能。

createNiceModal = (modalId, Comp) => {
    return (props) => {
        const { visible, args } = useNiceModal(modalId)
        if (!visible) return null
        return <Comp {...args} {...props} />
    }
}

// 使用
const MyModal = createNiceModal('my-modal', () => {
    return (
        <NiceModal id="my-modal" title="Nice modal">
            Hello NiceModal
        </NiceModal>
    )
})

实现useNiceModal,根据id,封装一些逻辑。

让Redux的action使用起来更方便,在其内部封装对store的操作,从而实现对话框状态管理的逻辑重用。

const modalCallbacks = {}

const useNiceModal = (modalId) => {
    const dispatch = useDispatch()

    // 封装Redux action 用于显示对话框
    const show = useCallback(
        (args) => {
            dispatch(showModal(modalId, args))
        },
        [dispatch, modalId]
    )

    // 封装Redux action 用于隐藏对话框 (force: boolean)
    const hide = useCallback(
        (force) => {
            dispatch(hideModal(modalId, force))
        },
        [dispatch, modalId]
    )

    const args = useSelector((s) => s[modalId])
    const hiding = useSelector((s) => s.hiding[modalId])

    // 只要有参数就认为对话框应该显示,如果没有传递args,在reducer中使用默认值true
    return { args, hiding, visible: !!args, show, hide }
}

这样,我们就实现了一个NiceModal这样的全局对话管理框架。

这样使用:

import { Button } from 'antd'
import NiceModal, {
    createNiceModal,
    useNiceModal
} from "./NiceModal"

const MyModal = createNiceModal("my-modal", () => {
    return (
        <NiceModal id="my-modal" title="Nice Modal">
            Hello World
        </NiceModal>
    )
})

function MyModalExample() {
    const modal = useNiceModal("my-modal")
    return (
        <>
            <Button type="primary" onClick={() => modal.show()}>
                Show my modal
            </Button>
            <MyModal />
        </>
    )
}

处理对话框的返回值

如果说对话框和页面这两种UI模式基本上是一致的,都是独立窗口完成独立逻辑。但是在用户交互上,有一定的差别:

  • 对话框可能需要返回值给调用者
  • 而页面切换一般不会关心页面执行的结果是什么

基于上面的NiceModal实现逻辑,现在考虑如何让调用者获得返回值。

我们可以把用户在对话框中的操作看成一个异步操作逻辑,那么用户在完成对话框中内容的操作后,就认为异步操作逻辑完成了。因此我们可以利用Promise来完成这样的逻辑。

那么,我们要实现的API如下:

const modal = useNiceModal('my-modal')
// 实现一个 promise API 来处理返回值
modal.show(args).then(res => {})

事实上,要实现这样一个机制并不困难,就是在 useNiceModal 这个 Hook 的实现中提供一个 modal.resolve 这样的方法,能够去 resolve modal.show 返回的 Promise。

代码的核心思路就是将show 和 resolve 两个函数通过 Promise 联系起来。因此两个函数调用位置不一样,所以我们使用一个局部的临时变量,来存放resolve回调函数。

// 使用一个 object 缓存 promise 的 resolve 回调函数
const modalCallbacks = {};
export const useNiceModal = (modalId) => {
  const dispatch = useDispatch();
  const show = useCallback(
    (args) => {
      return new Promise((resolve) => {
        // 显示对话框时,返回 promise 并且将 resolve 方法临时存起来
        modalCallbacks[modalId] = resolve;
        dispatch(showModal(modalId, args));
      });
    },
    [dispatch, modalId],
  );
  const resolve = useCallback(
    (args) => {
      if (modalCallbacks[modalId]) {
        // 如果存在 resolve 回调函数,那么就调用
        modalCallbacks[modalId](args);
        // 确保只能 resolve 一次
        delete modalCallbacks[modalId];
      }
    },
    [modalId],
  );

  // 其它逻辑...

  // 将 resolve 也作为返回值的一部分
  return { show, hide, resolve, visible, hiding };
};

总结

到此这篇关于React项目中hook实现展示对话框功能的文章就介绍到这了,更多相关React hook展示对话框内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 使用React手写一个对话框或模态框的方法示例

    打算用React写对话框已经很长一段时间,现在是时候兑现承诺了.实际上,写起来相当简单. 核心在于使用React的接口React.createPortal(element, domContainer).该接口将element渲染后的DOM节点嵌入domContainer(通常是document.body),并保证只嵌入一次. 所以,我们可以这样写一个对话框或模态框: function Dialog() { return React.createPortal( <div>Dialog conte

  • React项目中hook实现展示对话框功能

    目录 思路:使用全局状态管理所有对话框 尝试设计一个API去做对话框的全局管理 实现:创建NiceModal组件和相关API 处理对话框的返回值 总结 React中使用对话框并不容易,主要因为: 对话框需要在父组件中声明,才能在子组件中控制其是否显示 给对话框传递参数只能由props传入,这意味着所有状态管理都必须在更高阶的组件中.而实际上这个对话框的参数只在子组件中才会维护.这时就需要我们使用自定义事件将参数传回 这些问题的本质就是:如何用一个统一的方式去管理对话框,从而让对话框相关的业务逻辑

  • 如何在React项目中优雅的使用对话框

    目录 背景 场景一 场景二 场景三 问题一:难以扩展 问题二:维护问题 问题的本质 对话框的本质 全局的状态管理的对话框 整体的架构 具体实现 Redux - reducer 存储 Redux - action 处理对话框的显示隐藏 Hook - useCommonModal 创建对话框-容器模块 对话框返回值处理 运行实例 总结 参考 背景 对话框在前端开发应用中,是一种非常常用的界面模式.对话框作为一个独立的窗口,常常被用于信息的展示,输入信息,亦或者更多其他功能.但是项目的使用过程中,在某

  • 在 React 项目中全量使用 Hooks的方法

    目录 前言 React Hooks useState useReducer 基础用法 进阶用法 useContext useEffect useLayoutEffect useRef useImperativeHandle useCallback useMemo React Redux Hooks useSelector useDispatch React Router Hooks useHistory useLocation useParams useRouteMatch 参考 结语 前言 此

  • 实例详解ztree在vue项目中使用并且带有搜索功能

    zTree 是一个依靠 jQuery 实现的多功能 "树插件".优异的性能.灵活的配置.多种功能的组合是 zTree 最大优点. 上篇文章给大家介绍了vue中如何使用ztree,大家可以点击查看. 之前博客介绍过怎么在vue里展示以及获取点击元素的内容,此文章之介绍搜索功能 html <el-form-item label="机构" class="ztree-par"> <i class="icon_org"

  • TypeScript在React项目中的使用实践总结

    序言 本文会侧重于TypeScript(以下简称TS)在项目中与React的结合使用情况,而非TS的基本概念.关于TS的类型查看可以使用在线TS工具

  • React项目中应用TypeScript的实现

    目录 一.前言 二.使用方式 无状态组件 有状态组件 受控组件 三.总结 一.前言 单独的使用typescript 并不会导致学习成本很高,但是绝大部分前端开发者的项目都是依赖于框架的 例如和vue.react 这些框架结合使用的时候,会有一定的门槛 使用 TypeScript 编写 react 代码,除了需要 typescript 这个库之外,还需要安装@types/react.@types/react-dom npm i @types/react -s npm i @types/react-

  • 详解React项目中eslint使用百度风格

    1.安装百度Eslint Rule 插件 npm i -D eslint @babel/eslint-parser @babel/eslint-plugin @ecomfe/eslint-config // react项目 npm i -D eslint-plugin-react eslint-plugin-react-hooks // 如果需要支持typescript的话 npm i -D @typescript-eslint/parser @typescript-eslint/eslint-

  • 在React项目中使用TypeScript详情

    目录 项目目录及ts文件划分 在项目中使用TypeScript具体实践 组件声明 React Hooks使用 useState useRef useCallback useMemo useContext useReducer useImperativeHandle Axios请求/响应定义封装 前言: 本文主要记录我如何在React项目中优雅的使用TypeScript,来提高开发效率及项目的健壮性. 项目目录及ts文件划分 由于我在实际项目中大部分是使用umi来进行开发项目,所以使用umi生成的

  • React项目中className运用及问题解决

    目录 抛出问题 解决问题 组件化思想 className运用 总结问题 抛出问题 className 大家都用过吧,用它在 react 项目中设置样式.它的用法很简单,除了可以设置一个样式外,react 中也可以使用 className 引入多个类样式. 这次在写项目的时候,碰到一个非常小但是当时却一直解决不了的问题.后面在复盘的时候将它解决了.问题大致是这样的: 有两个活动页,每个活动页上都有一个活动规则图标来弹出活动规则,活动规则图标距离顶部会有一个值.现在问题就是这个活动规则在这两个活动页

  • thinkPHP5项目中实现QQ第三方登录功能

    本文实例讲述了thinkPHP5项目中实现QQ第三方登录功能.分享给大家供大家参考,具体如下: 最近用thinkPHP 5框架做了一个婚纱店的项目,在开发过程中需要用到第三方登录,腾讯官方给的案例是几个文件相互包含实现的,放到tp5里面很悲催的发现在控制器中不能通过include或者require完成预期功能,想要用腾讯官方封的类就必须对其进行修改,修改如下: 1. 找到官方SDK里面的核心文件 框架外使用的时候是include 'qqConnectAPI.php',打开这个文件可以看到它是包含

随机推荐