装饰器工程运用-埋点

背景:

  1. 假如我希望在不改变原组件内部逻辑的基础上,进行元素属性上的调整,你会怎么做
  2. 早期我在做圈选埋点第三方组件适配的时候,发现边界情况太多,很难适配,在学习装饰器的时候,突然发现,我是不是可以通过装饰器,将属性修改等操作,通过装饰器去解决,而不是让开发者自己去修改

案例

  1. 假如我现在希望,在给组件打上一个自定义属性 如 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;
相关推荐
YYYing.25 分钟前
【设计模式系列 (五) 】原型模式
开发语言·后端·设计模式·原型模式·c/c++
长按助力退休16 小时前
设计模式实战,这 5 个最常用
设计模式
Carl_奕然18 小时前
【智能体】Agent的四种设计模式之:Multi-Agent Collaboration
设计模式
Lost of 程序猿18 小时前
建造者模式实战:告别“十参数构造函数“的数据导出任务
后端·设计模式·c#·asp.net
SL_staff18 小时前
合规性折旧:当知识资产因主权缺位在审计中‘功能性清零’
java·设计模式·开源
Zane19941 天前
工厂方法一定比简单工厂高级?三种工厂模式到底该怎么选
设计模式
Hespethorn1 天前
设计模式是语言缺陷的补丁 —— 从工厂、单例、策略三个模式说起
设计模式
Lost of 程序猿1 天前
命令模式实战:把“下单后的动作“打包成可执行的任务
后端·设计模式·c#·asp.net·命令模式
一只旭宝1 天前
【C++复习】四种类型转换 + 常见设计模式 + Redis/MySQL(后端面试复习完整版)
c++·redis·笔记·mysql·设计模式
YYYing.1 天前
【设计模式系列 (四) 】建造者模式
c++·后端·设计模式·建造者模式·c/c++