在方案一中我们说了,使用事件触发的方式同步数据不是 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、Vue
中Portal
(传送门)的知识以及使用场景传送门可以将组件放在
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前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
核心竞争力,怎么才能提高呢?
成年人想要改变生活,逆转状态?那就开始学习吧~
万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。
为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。
前端面试题汇总
JavaScript
性能
linux
前端资料汇总
完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可免费领取了。
前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。
g.cn/img_convert/42728594459506983a38ca2b86545fc6.png)
JavaScript
性能
linux
前端资料汇总
完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可免费领取了。
前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。
-