装饰者模式

JavaScript 设计模式 张容铭
第十二章 房子装修--装饰者模式 (102页)

装饰者模式(Decorator)

在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法)使原有对象可以满足用户的更复杂需求。


//装饰者
var decorator = function(input, fn) {
    // 获取事件源
    var input = document.getElementById(input);
   // 若事件源已经绑定事件
    if(typeof input.onclick === 'function') {
   // 缓存事件源原有回调函数
    var oldClickFn = input.onclick;
   // 为事件源定义新事件
  input.onclick = function() {
 oldClickFn();
 fn();
  }
} else {
    // 事件源未绑定事件,直接为事件源添加新增回调函数
    input.onclick = fn();
}
}

装饰器模式

// 电话输入框功能装饰
decorator('tel_input', function(){
      document.getElementById('tel_demo_text').style.display = 'none';
});
// 姓名输入框功能装饰
decorator('name_input', function(){
      document.getElementById('name_demo_text').style.display = 'none';
});
// 地址输入框功能装饰
decorator('name_input', function(){
      document.getElementById('address_demo_text').style.display = 'none';
});

ES7中怎么用

装饰器(Decorator)是ES7中的一个新语法,使用可参考阮一峰的文章。正如其字面意思而言,它可以对类、方法、属性进行修饰,从而进行一些相关功能定制。它的写法与Java的注解(Annotation)非常相似,但是功能还是有很大区别。
看一个简单的例子

const log = (target, name, descriptor) => {
    console.log('target');
    console.log(target);
    console.log('name');
    console.log(name);
    console.log('descriptor');
    console.log(descriptor);
    var oldValue = descriptor.value;
    descriptor.value = function() {
      console.log(`Calling ${name} with`, arguments);
      return oldValue.apply(this, arguments);
    };
    return descriptor;
  }
  
  export default class Math {
    @log  // Decorator
    plus(a, b) {
      return a + b;
    }
  }

// 调用页面

// 引用
import Math from '@common/decoratorTest';
// 在类中引用
 const math = new Math();
        console.log(math.plus(3,4));

看看antd pro中使用

@connect(({ list, loading }) => ({
  list,
  loading: loading.models.list,
}))
@Form.create({
  onValuesChange({ dispatch }, changedValues, allValues) {
    // 表单项变化时请求数据
    // eslint-disable-next-line
    console.log(changedValues, allValues);
    // 模拟查询表单生效
    dispatch({
      type: 'list/fetch',
      payload: {
        count: 8,
      },
    });
  },
})

ES7的语法糖怎么做才能引用到项目中

1、安装
npm install babel-plugin-transform-decorators-legacy --save-dev

2、.babelrc
"plugins": [
"transform-runtime",
"transform-decorators-legacy",

]

它的原理是什么

JS中的装饰器本质也是一个函数,利用的是JS中object的descriptor,这个函数会接收三个参数:

/**
 * 装饰器函数
 * @param {Object} target 被装饰器的类的原型
 * @param {string} name 被装饰的类、属性、方法的名字
 * @param {Object} descriptor 被装饰的类、属性、方法的descriptor
 */
function Decorator(target, name, descriptor) {
  // 以此可以获取实例化的时候此属性的默认值
  let v = descriptor.initializer && descriptor.initializer.call(this);

  // 返回一个新的描述对象作为被修饰对象的descriptor,或者直接修改 descriptor 也可以
  return {
    enumerable: true,
    configurable: true,
    get() {
      return v;
    },
    set(c) {
      v = c;
    },
  };
}

 // USE
 class Fudao{
    @Decorator
    title = "企鹅辅导“
 }
当然装饰器也可以接受参数:
// decorator 外部可以包装一个函数,函数可以带参数
function Decorator(type) {
  /**
* 装饰器函数
* @param {Object} target 被装饰器的类的原型
* @param {string} name 被装饰的类、属性、方法的名字
* @param {Object} descriptor 被装饰的类、属性、方法的descriptor
*/
  return (target, name, descriptor) => {
    // 以此可以获取实例化的时候此属性的默认值
    let v = descriptor.initializer && descriptor.initializer.call(this);

    // 返回一个新的描述对象作为被修饰对象的descriptor,或者直接修改 descriptor 也可以
    return {
      enumerable: true,
      configurable: true,
      get() {
        return v + type;
      },
      set(c) {
        v = c;
      },
    };
  }
}

// USE
 class Fudao{
    @Decorator('string') 
    title = "企鹅辅导“
 }

在React中使用

import React, { Component } from 'react';
import { Chart, Geom, Coord, Shape } from 'bizcharts';
import DataSet from '@antv/data-set';
import Debounce from 'lodash-decorators/debounce';
import Bind from 'lodash-decorators/bind';
import classNames from 'classnames';
import autoHeight from '../autoHeight';
import styles from './index.less';

/* eslint no-underscore-dangle: 0 */
/* eslint no-param-reassign: 0 */

const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png';

@autoHeight()
class TagCloud extends Component {
  state = {
    dv: null,
  };

  componentDidMount() {
    requestAnimationFrame(() => {
      this.initTagCloud();
      this.renderChart();
    });
    window.addEventListener('resize', this.resize, { passive: true });
  }

  componentDidUpdate(preProps) {
    const { data } = this.props;
    if (JSON.stringify(preProps.data) !== JSON.stringify(data)) {
      this.renderChart(this.props);
    }
  }

  componentWillUnmount() {
    this.isUnmount = true;
    window.cancelAnimationFrame(this.requestRef);
    window.removeEventListener('resize', this.resize);
  }

  resize = () => {
    this.requestRef = requestAnimationFrame(() => {
      this.renderChart();
    });
  };

  saveRootRef = node => {
    this.root = node;
  };

  initTagCloud = () => {
    function getTextAttrs(cfg) {
      return Object.assign(
        {},
        {
          fillOpacity: cfg.opacity,
          fontSize: cfg.origin._origin.size,
          rotate: cfg.origin._origin.rotate,
          text: cfg.origin._origin.text,
          textAlign: 'center',
          fontFamily: cfg.origin._origin.font,
          fill: cfg.color,
          textBaseline: 'Alphabetic',
        },
        cfg.style
      );
    }

    // 给point注册一个词云的shape
    Shape.registerShape('point', 'cloud', {
      drawShape(cfg, container) {
        const attrs = getTextAttrs(cfg);
        return container.addShape('text', {
          attrs: Object.assign(attrs, {
            x: cfg.x,
            y: cfg.y,
          }),
        });
      },
    });
  };

  @Bind()
  @Debounce(500)
  renderChart(nextProps) {
    // const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C'];
    const { data, height } = nextProps || this.props;

    if (data.length < 1 || !this.root) {
      return;
    }

    const h = height * 4;
    const w = this.root.offsetWidth * 4;

    const onload = () => {
      const dv = new DataSet.View().source(data);
      const range = dv.range('value');
      const [min, max] = range;
      dv.transform({
        type: 'tag-cloud',
        fields: ['name', 'value'],
        imageMask: this.imageMask,
        font: 'Verdana',
        size: [w, h], // 宽高设置最好根据 imageMask 做调整
        padding: 5,
        timeInterval: 5000, // max execute time
        rotate() {
          return 0;
        },
        fontSize(d) {
          // eslint-disable-next-line
          return Math.pow((d.value - min) / (max - min), 2) * (70 - 20) + 20;
        },
      });

      if (this.isUnmount) {
        return;
      }

      this.setState({
        dv,
        w,
        h,
      });
    };

    if (!this.imageMask) {
      this.imageMask = new Image();
      this.imageMask.crossOrigin = '';
      this.imageMask.src = imgUrl;

      this.imageMask.onload = onload;
    } else {
      onload();
    }
  }

  render() {
    const { className, height } = this.props;
    const { dv, w, h } = this.state;

    return (
      <div
        className={classNames(styles.tagCloud, className)}
        style={{ width: '100%', height }}
        ref={this.saveRootRef}
      >
        {dv && (
          <Chart
            width={w}
            height={h}
            data={dv}
            padding={0}
            scale={{
              x: { nice: false },
              y: { nice: false },
            }}
          >
            <Coord reflect="y" />
            <Geom type="point" position="x*y" color="text" shape="cloud" />
          </Chart>
        )}
      </div>
    );
  }
}

export default TagCloud;

autoHeight 装饰器是怎么设计的

/* eslint eqeqeq: 0 */
import React from 'react';

function computeHeight(node) {
  const totalHeight = parseInt(getComputedStyle(node).height, 10);
  const padding =
    parseInt(getComputedStyle(node).paddingTop, 10) +
    parseInt(getComputedStyle(node).paddingBottom, 10);
  return totalHeight - padding;
}

function getAutoHeight(n) {
  if (!n) {
    return 0;
  }

  let node = n;

  let height = computeHeight(node);

  while (!height) {
    node = node.parentNode;
    if (node) {
      height = computeHeight(node);
    } else {
      break;
    }
  }

  return height;
}

const autoHeight = () => WrappedComponent =>
  class extends React.Component {
    state = {
      computedHeight: 0,
    };

    componentDidMount() {
      const { height } = this.props;
      if (!height) {
        const h = getAutoHeight(this.root);
        // eslint-disable-next-line
        this.setState({ computedHeight: h });
      }
    }

    handleRoot = node => {
      this.root = node;
    };

    render() {
      const { height } = this.props;
      const { computedHeight } = this.state;
      const h = height || computedHeight;
      return (
        <div ref={this.handleRoot}>{h > 0 && <WrappedComponent {...this.props} height={h} />}</div>
      );
    }
  };

export default autoHeight;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容