предисловие
Жизнь — это процесс накопления, ты всегда будешь падать, даже если упадешь, ты должен уметь ухватиться за горсть песка в руке. —— Дин Лэй
Каждое требование, которое было закодировано, каждая яма, на которую наступили, все, что было отремонтировано.bug, каждое полученное знание и каждая прочитанная статья не будут бесполезными, все они будут способствовать их собственному технологическому замку. Сегодня мы достигнем другогоReact、Vue AppРазделение состояний между потребностями начать, учитьсяReact,VueТе, которые мы редко используем, но как только мы удовлетворяем эти особые потребности, это должно быть их характеристикой 🤹🏻
Требования и вопросы
Статус запроса
В моей ежедневной разработке Byte мне нужно монтировать различные бизнес-компоненты на странице платформы, которая не является частью нашего поглощения, потому что каждый бизнес-компонент имеет свое собственное место и время установки, и все они могут быть просмотрены в отдельномReactприложение, поэтому мы используемWebpackОсуществлять многократную упаковку и набирать несколькоReactприложение, а затем на этой странице, представивsdkспособ крепления бизнес-компонентов.
вопрос
Практика упаковки с несколькими входами приведет к совместному использованию внутреннего состояния бизнес-компонентов, но состояние различных бизнес-компонентов не может быть хорошо распределено. И каждому компоненту могут потребоваться одни и те же данные, поэтому один и тот же сетевой запрос будет отправляться несколько раз на одной и той же странице.
Итак, у нас есть проблемы, и конечная цель состоит в том, чтобы решить несколькоReactОбмен состояниями между приложениями:
- Определенное состояние должно быть разным при нескольких монтированиях на странице.
DOMСовместно между бизнес-компонентами узла (доступ + обновление) - Взаимодействия внутри компонента должны запускать обновления состояния для других компонентов.
решение
1. Смонтировать состояние в глобальном объекте окна,EventEmitterзапустить обновление
Использовать наследование классовEventEmitterДанные хранятся и совместно используются путем объявления общедоступных переменных в классе, а совместное использование и обновление данных достигается с помощью подписки на события и их отправки. Используйте шаблон singleton для синхронизацииwindow, чтобы несколько компонентов использовали один и тот же экземпляр публикации-подписки для синхронизации и совместного использования данных.EventEmitterмы используем напрямуюeventemitter3предоставлена библиотекаonпрослушивание событий иemitтриггерное событие. Ниже приведеныTS Demoкод
import EventEmitter from 'eventemitter3'
// 定义触发的事件常量
export const ACTION = {
ADD_COUNT: 'add-count',
} as const
// 申明 Store 接口
export interface IStore {
count: {
value:number,
addCount:() => void
}
}
// 通过继承 EventEmitter 的 class 中声明 store 来存储数据
export class MyEmitter extends EventEmitter {
public store: IStore = {
count:{
value:1,
addCount:()=>{this.count.value++}
}
}
}
// 将类实例挂载在 Window 中,并保证不同组件中使用的是同一个实例
export const getMyEmitter: () => MyEmitter = () => {
if (window.myEmitter) {
return window.myEmitter
}
window.myEmitter = new Emitter()
const currentEmitter = window.myEmitter
const store = currentEmitter.store
ee.on(ACTION.ADD_COUNT, store.count.addCount, store.count)
return window.myEmitter
}
Такой очень примитивный метод разделения состояния завершен, теперь давайте взглянем наReactКак это используется в
import React,{ useState, useEffect} from 'react'
import {getMyEmitter, ACTION} from './getMyEmitter'
// 使用
const emitter = getMyEmitter()
const CountDemo = ()=>{
return <div>{emitter.store.count.value}</div>
}
// 触发事件
const ButtonDemo = ()=>{
return <button onClick={()=>{emitter.emit(ACTION.ADD_COUNT)}}>add count</button>
}
преимущество
Такое решение относительно примитивно, но решает проблему, с которой мы столкнулись:
- Решите проблему, заключающуюся в том, что пакетные приложения с несколькими входами не могут использовать унифицированный источник данных, а также поддерживать и управлять состоянием данных нескольких приложений унифицированным образом.
- единственный источник правды
недостаток
Но недостатки тоже весьма очевидны:
- Данные доступны глобально
windowобъект, неэлегантный, небезопасный - Использование метода, запускаемого событием, для синхронизации данных, по-видимому, нецелесообразно.
ReactРекомендуемая практика - Когда потребуется зарегистрировать больше событий, будет сложно управлять событиями и состояниями.
2. Одновходовая упаковка + портал
Реагируйте на рекомендуемые практики
В решении 1 мы сказали, что использование событийных методов для синхронизации данных недопустимо.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 => (
<li>
<Button {...props} />
</li>
);
const App = props => {
const [state] = useContext(ThemeContext);
const bg = state.theme === "light" ? "#ffffff" : "#000000";
return (
<div
className="App"
style={{
background: bg
}}
>
<ul>
<ListItem value="light" />
<ListItem value="dark" />
</ul>
</div>
);
};
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}
</button>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(
<ThemeProvider>
<App />
</ThemeProvider>,
rootElement
);
реальная проблема, которую нужно решить
при использованииReactРекомендуемая практика для достижения совместного использования данных, тогда нам нужно убедиться, что каждый бизнес-компонент по-прежнему может быть смонтирован на разных страницах.DOMНа основе узлов поместите все бизнес-компоненты в один и тот жеReact Treeвниз, потому что только все бизнес-компоненты находятся в одномReact Treeв следующий раз, чтобы позволитьReactвсплывающие сообщения о событиях, совместное использование состояний,ReactЖизненный цикл работает так, как ожидалось. Итак, сначала нам нужно изменить способ упаковки с несколькими входами на упаковку с одним входом, по крайней мере, для одной страницы. Изменить способ многократной упаковки на одноразовую очень просто.webpackКонфигурация в порядке. Затем приступайте к решению, как добиться того, чтобы в том же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, я нашел их всех, нашел их! Конечно, после его обнаружения традиционный API React в какой-то момент заканчивается. 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<IWrapPortalProps> = (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'}>
<button>Modal</button>
</WrapPortal>
портал
Далее мы рассмотримReact、VueсерединаPortal(Портал) знания и сценарии использования
Порталы могут размещать компоненты вHTMLлюбой изDOMв, поPortalповедение компонента и нормальноеReact、VueДочерний узел ведет себя так же, как и все еще вReact、Vue Treeв и сDOM Treeне зависит от позиции, то есть что-то вродеcontext, всплывающие окна событий иReact、Vueжизненный цикл такойFeatureЕще можно использовать.
-
Всплывание событий работает нормально- путем распространения события на
Reactузлы-предки дерева, всплывающее окно событий будет работать, как и ожидалось, в то время как сDOMсерединаPortalРасположение узлов значения не имеет. - React, Vue может управлять узлом портала и его жизненным циклом— При рендеринге дочерних элементов через Portal, React, Vue по-прежнему может контролировать их жизненный цикл.
-
Портал влияет только на структуру 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!)
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
`,
data() {
return {
modalOpen: false
}
}
})
app.mount('#app')
Vue2Без концепции порталов она не поддерживается? мы можем использовать это3K Starпроект с открытым исходным кодомportal-vue
<template>
<div>
<button @click="disabled = !disabled">Toggle "Disable"</button>
<Design-Container>
<Design-Panel color="green" text="Source">
<p>
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.
</p>
<Portal to="right-disable" :disabled="disabled">
<p class="red">This is content from the left/top container (green).</p>
</Portal>
</Design-Panel>
<Design-Panel color="red" text="Target" left>
<PortalTarget name="right-disable"></PortalTarget>
</Design-Panel>
</Design-Container>
</div>
</template>
<script>
export default {
data: () => ({
disabled: false,
}),
}
</script>
Суммировать
-
Раньше: мы предоставили несколько бизнес-компонентов для определенной страницы хост-платформы и упаковали их в несколько фрагментов для использования хостом в соответствии с методом упаковки с несколькими входами.
-
Проблема: Метод множественной записи очень недружелюбен к обмену данными, его можно решить, но не элегантно, что является первым решением в тексте.
-
Решение: Итак, мы хотим использовать относительно формальные формальные решения для совместного использования данных, Redux, MOBX, Unscate, React Context и т. Д. Тем не менее, нормальный способ - работать в одном приложении RACT. Поскольку множество приложений RACT упакованы с несколькими записями, мы впервые переключаемся на единую входную упаковку для одной страницы, чтобы убедиться, что несколько бизнес-компонентов находятся в одном и том же приложении для реагирования. В то же время для каждого бизнес-компонента будет установлен в разных доменах, мы используем портал для обертывания слоя бизнес-компонентов для обеспечения того, чтобы они все были в том же дереве реагирования.
🌈 Сегодняшняя публикация статьи здесь. Если вам понравилась эта статья, пожалуйста, поставьте лайк,Star,Сфокусируйся ная 🎯