背景:
- 假如我希望在不改变原组件内部逻辑的基础上,进行元素属性上的调整,你会怎么做
- 早期我在做圈选埋点第三方组件适配的时候,发现边界情况太多,很难适配,在学习装饰器的时候,突然发现,我是不是可以通过装饰器,将属性修改等操作,通过装饰器去解决,而不是让开发者自己去修改
案例
- 假如我现在希望,在给组件打上一个自定义属性 如 data-id=ffaawefvc 后,我希望将这个属性移动到内部input元素上.思路其实就是选择获取到目标元素,然后将当前组件中的错误标记元素上的 自定义属性,进行移除,然后给目标元素添加属性.那么一个通用的思路该如何去做呢?
js
import React from "react";
import { findDOMNode } from "react-dom";
import omit from 'omit.js';
export const DATA_CONSIDER_ID = "data-consider-id";
export const DATA_CONSIDER_TYPE = "data-consider-controltype";
export const DATA_CONSIDER_INDEX = "data-consider-index";
const getAttributesFromTarget = (element, markToItemIndexAttribute) => {
let attribute = element?.attributes[markToItemIndexAttribute]?.value;
if (!attribute) {
attribute = element.firstElementChild?.attributes[markToItemIndexAttribute]?.value;
}
return attribute;
}
interface MarkToItem {
markToItemClass: string;
//If markToItems contains more than one item,suggest add markToItemIdSuffix for each item
markToItemIdSuffix?: string;
//If markToItem has many,suggest add markToItemIndexAttribute for index diff
markToItemIndexAttribute?: string;
//If markToItem is dynamic changed at runtime,use this flag to add a MutationObserver
dynamicMark?: boolean;
// If has custom markLogic, pass it
customMarkLogic?: (element: HTMLElement, dataConsiderId: string, dataConsiderControlType: string, markToItemIndexAttribute?: string) => void;
}
const markDecorator = (WrappedPage, markToItems: MarkToItem[], includeConsiderAttributes?: boolean): any => {
class markComponent extends React.PureComponent {
dynamicMarkObserver: MutationObserver;
constructor (props) {
super(props);
}
componentDidMount() {
const dataConsiderId = this.props[DATA_CONSIDER_ID];
const dataConsiderControlType = this.props[DATA_CONSIDER_TYPE];
const element = findDOMNode(this) as HTMLElement;
if (element) {
try {
// Remove all exists mark
let fromElements = element.querySelectorAll(`[data-consider-id="${dataConsiderId}"]`);
fromElements?.forEach(fromElement => {
fromElement?.removeAttribute(DATA_CONSIDER_ID);
fromElement?.removeAttribute(DATA_CONSIDER_TYPE);
fromElement?.removeAttribute(DATA_CONSIDER_INDEX);
});
fromElements = null;
// Mark all needed items
markToItems.forEach(markToItem => {
const { markToItemClass, markToItemIdSuffix, markToItemIndexAttribute, dynamicMark, customMarkLogic } = markToItem;
// Add suffix to controlId if provided by component
let finalDataConsiderId = markToItemIdSuffix ? `${dataConsiderId}-${markToItemIdSuffix}` : dataConsiderId;
// Use custom mark logic if provided by component
const markLogic = customMarkLogic ?? function (toElement, dataConsiderId, dataConsiderControlType, markToItemIndexAttribute) {
toElement?.setAttribute(DATA_CONSIDER_ID, dataConsiderId);
toElement?.setAttribute(DATA_CONSIDER_TYPE, dataConsiderControlType);
markToItemIndexAttribute && toElement?.setAttribute(DATA_CONSIDER_INDEX, getAttributesFromTarget(toElement, markToItemIndexAttribute));
};
// Mark elements at first render
// @ts-ignore
const toElements = [element, ...element.querySelectorAll(markToItemClass)].filter(x => x.matches(markToItemClass));
toElements?.forEach((toElement: HTMLElement) => markLogic(toElement, finalDataConsiderId, dataConsiderControlType, markToItemIndexAttribute));
// If dynamicMark,means childNodes will change, will mark them at later render
if (dynamicMark && typeof MutationObserver !== "undefined") {
this.dynamicMarkObserver = new MutationObserver(() => {
// @ts-ignore
const newToElements = [element, ...element.querySelectorAll(markToItemClass)].filter(x => x.matches(markToItemClass));
newToElements?.forEach((toElement: HTMLElement) => markLogic(toElement, finalDataConsiderId, dataConsiderControlType, markToItemIndexAttribute));
});
this.dynamicMarkObserver.observe(element, { childList: true, subtree: true });
}
});
} catch (error) {
console.log('Mark fail:' + error);
}
}
}
componentWillUnmount() {
this.dynamicMarkObserver?.disconnect();
}
render() {
let omitAttributes = includeConsiderAttributes ? [] : [DATA_CONSIDER_ID, DATA_CONSIDER_TYPE];
var props = omit(this.props, omitAttributes);
return <WrappedPage {...props} />;
}
}
return markComponent;
}
export default markDecorator;