如何优雅地解决多个 React、Vue App 之间的状态共享,web开发技术

在方案一中我们说了,使用事件触发的方式同步数据不是 React 推荐做法,那数据共享的推荐做法是什么呢?React 的推荐做法是 提升状态 到各个组件最近的父级节点,借助 React 官方文档 useContext demo 来简单理解:

// 需要共享的数据

import ReactDOM from “react-dom”;

import React, { createContext, useContext, useReducer } from “react”;

import “./styles.css”;

const ThemeContext = createContext();

const DEFAULT_STATE = {

theme: “light”

};

const reducer = (state, actions) => {

switch (actions.type) {

case “theme”:

return { …state, theme: actions.payload };

default:

return DEFAULT_STATE;

}

};

const ThemeProvider = ({ children }) => {

return (

<ThemeContext.Provider value={useReducer(reducer, DEFAULT_STATE)}>

{children}

</ThemeContext.Provider>

);

};

const ListItem = props => (

  • <Button {…props} />

    );

    const App = props => {

    const [state] = useContext(ThemeContext);

    const bg = state.theme === “light” ? “#ffffff” : “#000000”;

    return (

    className=“App”

    style={{

    background: bg

    }}

      );

      };

      const Button = ({ value }) => {

      const [state, dispatcher] = useContext(ThemeContext);

      const bgColor = state.theme === “light” ? “#333333” : “#eeeeee”;

      const textColor = state.theme === “light” ? “#ffffff” : “#000000”;

      return (

      <button

      style={{

      backgroundColor: bgColor,

      color: textColor

      }}

      onClick={() => {

      dispatcher({ type: “theme”, payload: value });

      }}

      {value}

      );

      };

      const rootElement = document.getElementById(“root”);

      ReactDOM.render(

      ,

      rootElement

      );

      真正要解决的问题

      如果是使用 React 推荐做法来实现数据共享,那么我们就需要在保证各个业务组件依旧可以挂载在页面不同的 DOM 节点的前提下,将所有业务组件都放在同一颗 React Tree 下,因为只有所有业务组件都在同一颗 React Tree 下时才能让 React 的事件冒泡、状态共享、React 的生命周期按照预期进行工作。所以我们首先需要将多入口打包的方式改成单入口打包,至少针对单页面是这样的。多入口打包的方式改成单入口打包非常简单,直接改 webpack 的配置就 ok 了。然后接着解决如何保证在同一颗 React Tree 的前提下将不同的业务组件挂载在不同的 DOM 节点

      再简单说明一下我们现在需要解决的问题。我们都知道将一个 React APP 应用挂载在某个 DOM 节点就是直接 ReactDOM.render(<App />, targetElement) 就好了,但是业务组件各自都有各自不同的挂载 DOM 节点,如果业务组件都各自执行 ReactDOM.render 的话,那就不能保证所有业务组件都在同一颗 React Tree 下,也就不能让 React 的事件冒泡、状态共享、React 的生命周期按照预期进行工作了。

      所以接下来我们要解决的问题就是:如何保证让不同的业务组件可以挂载在不同的 DOM 节点的前提下,他们依旧是在同一颗 React Tree 下的呢?

      开始解决问题

      在  ReactDOM.render 主应用后可以让子组件挂载在页面上的不同位置 ???,这让我想到了 Ant-Design 中 Modal,在需要用户处理事务,又不希望跳转页面以致打断工作流程时,可以使用 Modal 在当前页面正中打开一个浮层,承载相应的操作。Modal 其中有一个 getContainer 属性,说的是 Modal 默认的挂载位置是  document.body ,可以指定 Modal 挂载的 HTML 节点,当值为 false 时挂载在当前 DOM

      那不就意味着我们在 React 应用写的 Modal 组件,它本来的挂载位置是跟随主应用的,但是 Ant-Design 把它默认提到了  document.body 中,这不就是我们要找的解决方法吗?我们来看看  Ant-Design 源码是通过什么来实现的呢?

      我们先找到 Ant-Design 的  Modal 组件的弹窗,发现弹窗是通过 rc-dialog 包实现的。

      那么我们接着找 rc-dialog 的实现,然后我们发现 rc-dialog 在挂载时候使用了 Portal 组件包了一层。

      那我们接着找 rc-util 包看看他的 Portal 组件是如何实现的。

      唉,我一说 “ 啪 ” 就 Github 撸了起来,很快啊!然后上来就是,一个 Ant-Design Modal,吭,一个 rc-dialog,一个  re-util,我全部找到了,找到了啊!找到以后,自然是,传统 React API 以点到为止。ReactDOM 放在了鼻子上,我没看文档。我笑一下,准备关掉 Github,因为这时间,按传统 Github 的点到为止,最终我已经找到了答案 —— ReactDOM.CreatePortal

      最终我们发现  ReactDOM.createPortal 可以将组件放在 HTML 的任意 DOM 中,被 Portal 的组件行为和普通的 React 子节点行为一致,因为它仍然在 React Tree  中, 且与 DOM Tree 中的位置无关,也就是说像 context 、事件冒泡以及 React 的生命周期这样的 Feature 依旧可以使用。

      我们对 ReactDOM.createPoral 进行简单封装就可以随处使用啦

      interface IWrapPortalProps {

      elementId: string // 创建带 id 的 createPortal container

      effect: (container: HTMLElement, targetDom: Element) => void // 获取挂载位置,将 container 插入目标节点

      targetDom?: Element

      }

      /**

      • 通过 createPortal 实现在不同的 DOM 上挂载依旧在同一颗 React tree 上

      • @param {*} IWrapPortalProps

      • @returns

      */

      export const WrapPortal: React.FC = (props) => {

      const [container] = useState(document.createElement(‘div’))

      useEffect(() => {

      container.id = props.elementId

      if (!props.targetDom) {

      return

      }

      props.effect(container, props.targetDom, props.elementId)

      return () => {

      container.remove()

      }

      }, [container, props])

      return ReactDOM.createPortal(props.children, container)

      }

      // 使用

      const effect = (container: HTMLElement, targetDom: Element) => {

      targetDom!.insertAdjacentElement(‘afterbegin’, container)

      }

      const targetDom = document.body

      <WrapPortal effect={effect} targetDom={targetDom} elementId={‘modal-root’}>

      Modal

      传送门


      接下来我们就复习一下 React、VuePortal(传送门)的知识以及使用场景

      传送门可以将组件放在 HTML 的任意 DOM 中,被 Portal 的组件行为和普通的 React、Vue 子节点行为一致,因为它仍然在 React、Vue Tree 中, 且与 DOM Tree 中的位置无关,也就是说像 context 、事件冒泡以及 React、Vue 的生命周期这样的 Feature 依旧可以使用。

      • 事件冒泡正常工作 —— 通过将事件传播到 React 树的祖先节点,事件冒泡将按预期工作,而与 DOM 中的 Portal 节点位置无关。

      • React、Vue 可以控制 Portal 节点及其生命周期 —— 通过 Portal 渲染子元素时,React、Vue 仍然可以控制其生命周期。

      • Portal 仅影响 DOM 结构 —— Portal 仅影响 HTML DOM 结构且不影响 React、Vue 组件树。

      • 预定义 HTML 挂载点 —— 使用 Portal 时,需要定义一个 HTML DOM 元素作为 Portal 组件的挂载点。

      当我们需要在正常 DOM 层次结构之外呈现子组件而又不通过 React 组件树层次结构破坏事件传播等的默认行为时,React、Vue Portal 就会显得非常有用:

      • 模态对话框

      • 工具提示

      • 悬浮卡片

      • 加载提示组件

      • Shawdow DOM 内挂载 React、Vue 组件

      Vue 3.0 新增了 Teleport 的概念,在 Vue 2 中是不支持这个特性的。

      const app = Vue.createApp({});

      app.component(‘modal-button’, {

      template: `

      <button @click=“modalOpen = true”>

      Open full screen modal! (With teleport!)

      I’m a teleported modal!

      (My parent is “body”)

      <button @click=“modalOpen = false”>

      Close

      `,

      data() {

      return {

      modalOpen: false

      }

      }

      })

      app.mount(‘#app’)

      Vue2 没有传送门的概念,是不是就不支持了呢?我们可以使用这个 3K Star 的开源项目 portal-vue

      <button @click=“disabled = !disabled”>Toggle “Disable”

      The content below this paragraph is

      rendered in the right/bottom (red) container by PortalVue

      if the portal is enabled. Otherwise, it’s shown here in place.

      This is content from the left/top container (green).

      总结

      • 之前:我们是向宿主平台某个页面提供多个业务组件,按照多入口打包方式打包成多个 chunk 给宿主使用。

      • 问题:多入口的方式对于数据共享非常不友好,能解决但是不优雅,也就是文中的方案一。

      • 解决:所以我们想要用相对正规的数据共享方式解决,Redux、Mobx、unstate、React Context 等。但是正规的方式都是在一个 React App 工作的,由于多入口打包打成了多个 React 应用,所以我们先针对单页面改用单入口打包,保证多个业务组件都在同一个 React App 上。与此同时,针对各个业务组件要挂载在不同 DOM 的需求,我们再用 Portal 对业务组件包裹一层,保证他们都在同一颗 React Tree。

      ??? 今天的文章分享就到这里啦,如果喜欢这篇文章的话,觉得这篇文章有用的话,请帮我点赞、在看、转发、以及关注我吧 ???

      参考

      • https://zhuanlan.zhihu.com/p/29880992

      • https://v3.vuejs.org/guide/teleport.html#using-with-vue-components

      • https://reactjs.org/docs/react-dom.html#createportal

      - END -

      往期精彩

      自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

      深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

      因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

      img

      既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

      由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

      如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

      核心竞争力,怎么才能提高呢?

      成年人想要改变生活,逆转状态?那就开始学习吧~

      万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

      为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

      前端面试题汇总

      JavaScript

      性能

      linux

      前端资料汇总

      完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可免费领取了

      前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

      g.cn/img_convert/42728594459506983a38ca2b86545fc6.png)

      JavaScript

      性能

      linux

      前端资料汇总

      完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可免费领取了

      前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值