前端react框架
介绍 (Introduction)
At Lab Digital we provide eCommerce solutions for a variety of clients. Some of these clients already have websites they wish to keep, just with added eCommerce functionality. For these clients we can provide a tailor-made set of front-end components reflecting their corporate identity which they can easily implement on their website in order to achieve full webshop status. These components include an add-to-cart button, a price indicator, a cart and a checkout flow. The client merely has to pass the relevant SKUs (product identifiers) to the add-to-cart and price components and we take care of the rest. The front-end components communicate with our commercetools implementation in order to provide all the required functionality.
在Lab Digital,我们为各种客户提供电子商务解决方案。 其中一些客户已经拥有他们希望保留的网站,只是增加了电子商务功能。 对于这些客户,我们可以提供一组量身定制的前端组件,以反映其企业标识,可以轻松地在其网站上实施这些组件,以实现完整的网上商店状态。 这些组件包括一个购物车按钮,一个价格指示器,一个购物车和一个结帐流程。 客户只需要将相关的SKU(产品标识符)传递给购物车和价格组件,其余的我们会处理。 前端组件与我们的commercetools实现通信,以提供所有必需的功能。
In order to get these components to render on the client’s page, some technical hoops need to be jumped through. First, the elements you want to replace need to be detected. Then correct modules containing the React components need to be imported, the React elements need to be created and rendered inside the detected DOM elements. In order to have a truly effective application, you would also want to be able to pass some props to the React components as well as automatically detect new DOM elements which have been asynchronously added. In order to achieve all this, we at Lab Digital used to implement React Habitat. However, React Habitat has not been updated for quite a while and shows quite a few errors when using the dynamic DOM node detection, so we decided to write our own package. Some of the design choices made in React Habitat were clearly influenced by a Java style of code writing, which we believe not play well with Typescript. Because of these reasons we decided to write our own package, which you can check out as React Abode on npmjs.
为了使这些组件能够在客户端的页面上呈现,需要跳过一些技术难题。 首先,需要检测要替换的元素。 然后,需要导入包含React组件的正确模块,需要在检测到的DOM元素内部创建和渲染React元素。 为了拥有真正有效的应用程序,您还希望能够将一些支持传递给React组件,并自动检测已异步添加的新DOM元素。 为了实现所有这些目的,我们在Lab Digital用来实现React Habitat 。 但是,React Habitat并没有更新很长时间,并且在使用动态DOM节点检测时会显示很多错误,因此我们决定编写自己的包。 React Habitat做出的一些设计选择显然受到Java风格的代码编写的影响,我们认为TypeScript不能很好地发挥这种风格。 由于这些原因,我们决定编写自己的软件包,您可以在npmjs上以React Abode的身份签出。
检测DOM节点并渲染React组件 (Detecting DOM nodes and rendering React components)
Everything starts off with detecting which DOM elements we need to replace. The easiest way to do this is with a custom attribute which we can query for. For example, if you and I were making an eCommerce website and we wanted to render a shopping cart, we could do something like the code below. In this code we add an empty div with our component selector and we load our bundle which contains our react components and implements the micro front-end package.
一切始于检测我们需要替换哪些DOM元素。 最简单的方法是使用我们可以查询的自定义属性。 例如,如果您和我正在建立一个电子商务网站,而我们想绘制一个购物车,则可以执行以下代码。 在此代码中,我们使用组件选择器添加了一个空的div,并加载了包含我们的react组件的bundle并实现了微型前端程序包。
<html>
<body>
<div data-component="Cart">
This text will be replaced by your React component
</div>
<script src="your/react/bundle"></script>
</body>
</html>
By executing javascript shown below in our micro front-end package, we can find all elements with the data-component
attribute. Then all we really need to do is load the proper React component and render them in these elements. Easy peasy!
通过在我们的微型前端程序包中执行以下所示的javascript,我们可以找到具有data-component
属性的所有元素。 然后,我们真正需要做的就是加载适当的React组件并将其呈现在这些元素中。 十分简单!
import { render } from 'react-dom';
import { createElement } from 'react';
const renderComponent = async (el: Element) => {
render(createElement(module.default, {}), el);
};
const refs = Array.from(document.querySelectorAll(`[data-component]`));
refs.forEach(renderComponent);
The eagle eyed reader might have noticed that a module with a React component as a default export appeared out of nowhere. Before we render any components we need to make sure we have loaded our modules. We can export a register
function from our package which does exactly that.
老鹰眼的读者可能已经注意到,带有React组件作为默认导出功能的模块似乎无处不在。 在渲染任何组件之前,我们需要确保已加载模块。 我们可以从我们的包中导出一个register
函数来做到这一点。
let components: { [key: string]: Promise<NodeModule> } = {};
export const register = (name: string, fn: () => Promise<NodeModule>) => {
components[name] = fn();
};
const renderComponent = async (el: Element) => {
const componentName = Array.from(el.attributes).find(
(at) => at.name === 'data-component'
)?.value;
const module = await components[componentName];
render(createElement(module.default, {}), el);
};
In the bundle where the React components are defined we could then register our React components.
在定义React组件的包中,我们可以注册React组件。
register('Cart', () => import('./modules/Cart/Cart'));
And that’s it! That’s really all we need to do in order to load multiple react components dynamically on a HTML page. Of course, this is very basic. Let’s see if we can add some improvements in both speed and functionality.
就是这样! 实际上,我们要做的就是在HTML页面上动态加载多个React组件。 当然,这是非常基本的。 让我们看看是否可以在速度和功能上进行一些改进。
检测新元素 (Detecting new elements)
Since it is 2020, being able to dynamically load more HTML and detect any elements we need to populate is pretty much a basic requirement. This is done easily enough by making use of the DOMNodeInserted
event on the document body. Every time a new DOM node is added to the DOM our function which detects and populates targeted elements will be run.
从2020年开始,能够动态加载更多HTML并检测我们需要填充的任何元素几乎是一个基本要求。 通过使用文档主体上的DOMNodeInserted
事件,可以很容易地完成此操作。 每次将新的DOM节点添加到DOM时,都会运行检测并填充目标元素的函数。
const update = async () => {
const refs = Array.from(document.querySelectorAll(`[data-component]`));
refs.forEach((el) => {
renderComponent(el);
});
};
export const populate = async () => {
await update();
document.body.addEventListener('DOMNodeInserted', update);
};
The downside to this is that rendering React components is an asynchronous process during which new DOM nodes will be added. This means that every time a React element is rendered, our update function will be triggered quite a few times and the elements with our selector data-component
will be populated over and over. We can prevent this by tagging all our elements before starting to render them and then filtering elements which have already been tagged.
缺点是渲染React组件是一个异步过程,在此过程中将添加新的DOM节点。 这意味着每次渲染一个React元素时,我们的更新函数都会被触发多次,并且带有选择器data-component
将被反复填充。 我们可以通过在开始渲染它们之前标记所有元素然后过滤已经被标记的元素来防止这种情况。
const update = async () => {
const refs = Array.from(document.querySelectorAll(`[data-component]`)).filter(
(el) => !el.getAttribute('react-abode-populated')
);
refs.forEach((el) => el.setAttribute('react-abode-populated', 'true'));
refs.forEach((el) => {
renderComponent(el);
});
};
传递道具 (Passing props)
Another piece of functionality we will definitely want is the ability to pass props to our React components. In this way, a user of our React bundle could do the following in their HTML to pass the SKU (product ID) to a price indicator component.
我们肯定想要的另一项功能是将prop传递到React组件的能力。 这样,React包的用户可以在其HTML中执行以下操作,以将SKU(产品ID)传递给价格指标组件。
<div data-component="Price" data-prop-sku="123456"></div>
We can then grab these props in our javascript, clean them up a bit and pass them down to the React components! The React components can then use these props just as if they had come from any other parent component.
然后,我们可以在JavaScript中抓取这些道具,将其清理一点,然后传递给React组件! 然后,React组件可以使用这些道具,就像它们来自任何其他父组件一样。
const getProps = (el: Element | HTMLScriptElement): Props => {
const props: { [key: string]: string } = {};
const rawProps = Array.from(el.attributes).filter((attribute) =>
attribute.name.startsWith('data-prop-')
);
rawProps.forEach((prop) => (props[getCleanPropName(prop.name)] = prop.value));
return props;
};
const renderComponent = async (el: Element) => {
const props = getProps(el);
// ...
render(createElement(module.default, props), el);
};
真正的互动性 (True interactiveness)
The method of passing props described above works very well if the components are rendered on a static HTML page. If the HTML is dynamic, for example because you are nesting your components in an existing React application, it would be nice to be able to detect prop changes and rerender, right? We can do exactly that by using the MutationObserver. The mutation observer is available on most browsers, even IE11! We can tell the mutation observer to observe specific HTML elements for changes. We can even tell it to specifically track changes in the attributes. When we set things up as shown below, each change in attributes for an observed element will cause a rerender with the newest props.
如果组件在静态HTML页面上呈现,则上述传递道具的方法效果很好。 如果HTML是动态的(例如,因为您要将组件嵌套在现有的React应用程序中),那么能够检测到道具更改并重新渲染会很好,对吧? 我们可以通过使用MutationObserver完全做到这一点。 突变观察器可用于大多数浏览器,甚至IE11! 我们可以告诉变异观察者观察特定HTML元素进行更改。 我们甚至可以告诉它专门跟踪属性的更改。 当我们按如下所示进行设置时,所观察元素的每个属性更改都会导致使用最新道具重新渲染。
export const trackPropChanges = (el: Element) => {
const observer = new MutationObserver(() => {
renderComponent(el);
});
observer.observe(el, { attributes: true });
};
export const update = async () => {
// ...
refs.forEach((el) => {
renderComponent(el);
trackPropChanges(el);
});
};
性能 (Performance)
When using this method of rendering React, all components are rendered sequentially. This means that there will be a performance impact when rendering a lot of components on the same page. When measuring this effect with Lighthouse, we can see a performance score of 99 when loading 50 simple react components. When increasing the number of components to 1000, this performance score drops to 74. For most websites there would be no reason to use this many separate micro front-ends. If you were to really need that many components you could use an async renderer or wait for React concurrent.
当使用这种渲染React的方法时,所有组件都是顺序渲染的。 这意味着在同一页面上呈现大量组件时,会对性能产生影响。 使用Lighthouse测量此效果时,加载50个简单的React组件时,我们的性能得分为99。 当组件数量增加到1000时,该性能得分将降至74。对于大多数网站,没有理由使用这么多单独的微型前端。 如果确实需要那么多组件,则可以使用异步渲染器或等待React并发 。
结论 (Conclusion)
As you’ve seen above, getting started with your own React micro front-end setup isn’t all that hard with some knowledge of vanilla JavaScript and HTML. If you enjoy this kind of thing, feel free to check out the code of React Abode or our current vacancies at Lab Digital.
正如您在上面看到的,有了一些原始JavaScript和HTML知识,开始自己的React micro前端设置并不难。 如果您喜欢这种事情,请随时查看React Abode的代码或Lab Digital当前的空缺。
Bram is a full-stack developer at Lab Digital. He loves music your neighbours would hate, nerding out about code and nature.
Bram是Lab Digital的全栈开发人员。 他热爱邻居们讨厌的音乐,对代码和自然情有独钟。
翻译自: https://blog.labdigital.nl/creating-your-own-micro-front-end-framework-for-react-d5671b228f80
前端react框架