Ant Design源码分析(三):Wave组件

栏目: 编程工具 · 发布时间: 6年前

内容简介:在看完UI效果之后我们大概已经知道是什么了,再看代码部分,由于代码书写顺序与阅读顺序并不一致,为了方便理解,我们在分析源码的过程中,会调整代码解释的顺序

Wave组件效果预览

上一篇文章Button组件的源码分析中 遇到了一个 Wave 组件, Wave 组件在 Ant design 中提供了通用的表单控件点击效果,在自己阅读源码之前,也并没有过更多留心过在这些表单控件的动画效果是如何实现的,甚至可能有时都没注意到这些动画效果。下面先一起来看以下具体的效果:

Ant Design源码分析(三):Wave组件

看完UI效果之后我们大概已经知道是什么了,再看代码部分,由于代码书写顺序与阅读顺序并不一致,为了方便理解,我们在分析源码的过程中,会调整代码解释的顺序

源码分析

// 一个新的依赖,暂时不知道是什么,依据名字推测与动画效果有关
import TransitionEvents from 'css-animation/lib/Event';

export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
  
  //... some type code
  
  // 我们发现Wave组件只提供组件逻辑,不参与UI展示,这种容器组件,往往在DidMount或WillMount声明周期中开始
  // 构建组件逻辑,往下看
  render() {
    return this.props.children;
  }
  
  // 只有一行代码, 先看下方bindAnimationEvent方法
  componentDidMount() {
    this.instance = this.bindAnimationEvent(findDOMNode(this) as HTMLElement);
  }
    
  // 在组件卸载时,清除掉事件监听与定时器,避免内存泄漏
  componentWillUnmount() {
    if (this.instance) {
      this.instance.cancel();
    }
    if (this.clickWaveTimeoutId) {
      clearTimeout(this.clickWaveTimeoutId);
    }
  }  

  // 根据名字推测: 为DOM节点绑定动画效果,进入函数内部查看
  bindAnimationEvent = (node: HTMLElement) => {
   //... some code 

    const onClick = (e: MouseEvent) => {
      //... some code

      // 无论是否正在执行动画,先清除掉动画效果(至于怎么清除,先不关注)
      this.resetEffect(node);
      
      // 从target取得颜色值
      const waveColor =
        getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
        getComputedStyle(node).getPropertyValue('border-color') ||
        getComputedStyle(node).getPropertyValue('background-color');

      // 在这里可以看到之前定义的私有变量clickWaveTimeoutId,被用作储存一个定时器
      this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0);
    };
  
    // 监听node(this.props.children)的onClick事件
    node.addEventListener('click', onClick, true);

    // 将移除监听事件的回调函数封装在一个对象中并作为返回值,看着这里应该想起之前定义的私有变量instance,
    // 回顾DidMount生命周期函数,你会发现这个返回的对象将会被储存在instance中
    return {
      cancel: () => {
        node.removeEventListener('click', onClick, true);
      },
    };
  }

  //未完待续
我们通过观察上方 bindAnimationEvent 方法,其主要做了三件事,调用了两个外部函数 this.resetEffectthis.onClick

1、过滤不执行的条件(代码省略掉了,非主干逻辑)

2、声明 onClick 函数,并作为 node 的点击事件触发时要执行的函数

3、返回一个储存了取消监听 click 事件方法的对象

个人认为 bindAnimationEvent 方法,做了太多的事情,而 ComponentDidMount 做的事情太少(单一原则)

往下方,继续查看 this.resetEffectthis.onClick 分别是做什么的,以及如何实现的

// 这个函数是实现动画效果的核心,其主要有三个行为:1、创建内联style标签, 2、插入css字符串 3、并将其插入到document中
  // 我们知道css也是可以控制DOM变化的,比如伪类元素:after :before  这里正是通过:after来实现效果的
  onClick = (node: HTMLElement, waveColor: string) => {
    //... some code 1
    const { insertExtraNode } = this.props;

    /* 创建了一个div元素extraNode,装饰该div,在inserExtracNode= true时,将extraNode作为node的子元素 */
    /* 创建一个div元素,并缓存中私有变量extraNode中 */
    this.extraNode = document.createElement('div');
    
    /* 这里用let 更合适 */
    const extraNode = this.extraNode;
    extraNode.className = 'ant-click-animating-node';
    // 可能有人好奇这个extraNode是干嘛的?
    // 往之后的代码中看的时候会发现,在insertExtraNode为false时,Wave通过插入伪类元素:after 来作为承载动画效果的DOM元素
    // 在insertExtraNode = true时,会生成一个div替代:after伪类元素,猜测是某些this.props.children可能自带:after,所以
    // 使用div元素来替代:after避免冲突,在这里我们只需要知道它是作为承载动画css的DOM元素即可
   
    // 获取指定的string insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node';
    const attributeName = this.getAttributeName();  
    
    // Element.removeAttribute('someString');  从element中删除值为someString的属性
    // Element.setAttribute(name, value); 为element元素值为value的name属性
    node.removeAttribute(attributeName);
    node.setAttribute(attributeName, 'true');
    
    // 行为1:这里创建了一个内联style标签
    styleForPesudo = styleForPesudo || document.createElement('style');
    if (waveColor &&
        waveColor !== '#ffffff' &&
        waveColor !== 'rgb(255, 255, 255)' &&
        this.isNotGrey(waveColor) &&
        /* 透明度不为0的任意颜色 */
        !/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) &&  // any transparent rgba color
        waveColor !== 'transparent') {
          /* 给子元素加上borderColor */
        extraNode.style.borderColor = waveColor;
        
        /* 行为2:在内联style标签中插入样式字符串, 利用伪元素:after作为承载效果的DOM */
        styleForPesudo.innerHTML =
            `[ant-click-animating-without-extra-node]:after { border-color: ${waveColor}; }`;
            
        /* 行为3:将style标签插入到document中 */
      if (!document.body.contains(styleForPesudo)) {
        document.body.appendChild(styleForPesudo);
      }
    }    

     /* 在inserExtarNode为true时,将extraNode插入到node子元素中 */
    if (insertExtraNode) {
      node.appendChild(extraNode);
    }
    
    /* 为元素增加动画效果 */
    TransitionEvents.addEndEventListener(node, this.onTransitionEnd);
  }

  /**
   * 重置效果
   * 顾名思义:这个函数通过三个行为,致力于一件事情,取消动画效果
   * 1、删除node的attribute属性 2、node的子元素 3、删除对应的内联style标签
   */
  resetEffect(node: HTMLElement) {
    // come code ...
    
    const { insertExtraNode } = this.props;
    
    const attributeName = this.getAttributeName();
    /* 行为1:删除node的attribute属性 */
    node.removeAttribute(attributeName);

    /* 行为3: 清空了为伪类元素内置的style标签 styleForPesudo */
    this.removeExtraStyleNode();

    if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) {
      // Node.removeChild() 方法从DOM中删除一个子节点。返回删除的节点。
      node.removeChild(this.extraNode);
    }
    TransitionEvents.removeEndEventListener(node, this.onTransitionEnd);
  }

  // 删除内联style标签
  removeExtraStyleNode() {
    if (styleForPesudo) {
      styleForPesudo.innerHTML = '';
    }
  }
      
  // 在每次动画执行结束后,清除掉状态,完成一个生命周期
  onTransitionEnd = (e: AnimationEvent) => {
    // todo
    if (!e || e.animationName !== 'fadeEffect') {
      return;
    }

    this.resetEffect(e.target as HTMLElement);
  }

}

组件逻辑:

我们回过头来看第一部分的代码,组件逻辑体现在 componentDidMountcomponentWillUnMount

1、在 componentDidMount 中为 this.props.childrenclick 事件绑定动画执行函数 this.onClick

2、在 componentWillUnMount 中清除与动画相关的状态避免内存泄漏。

组件运行逻辑:

此时, Wave 组件的代码与逻辑已经全部分析完了,整个Wave组件的运行逻辑可以通过下方这张图来概括

Ant Design源码分析(三):Wave组件

本篇完~


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Math Adventures with Python

Math Adventures with Python

Peter Farrell / No Starch Press / 2018-11-13 / GBP 24.99

Learn math by getting creative with code! Use the Python programming language to transform learning high school-level math topics like algebra, geometry, trigonometry, and calculus! In Math Adventu......一起来看看 《Math Adventures with Python》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具