美文网首页
react动画开源库代码学习之rc-animate

react动画开源库代码学习之rc-animate

作者: 伊各的诗与代码 | 来源:发表于2018-03-03 18:00 被阅读0次

rc-animate

rc-animate是这个库在npm上的名字,他在github搜索的话得用animate去搜索。(下面就都简称为animate了)
animate是阿里的库,ant-design也是使用的他完成动画的一部分内容。
官网是这样介绍的:

对单个元素根据状态进行动画显示隐藏,需结合 css 或其它第三方动画类一起使用

代码结构

code.PNG

rc-animate的源代码src文件夹下只有Animate,AnimateChild,ChildrenUtils,Utils这四个js文件,后两个utils是工具类js,用来检测组件的props是否含有某个值这样的工作等,不过多介绍。
Animate.js是我们库暴露给我们使用的组件,他会在内部做一些检测,回调等等操作。然后调用AnimateChild组件,AnimateChild是实际实现动画的地方。AnimateChild中使用了css-animate这个库,css-animate才是最核心的操作,他将给dom元素添加指定的css类名。另一个animateUtils是上一层的utils.js这个文件,我写错了,这里说明一下。
先从rc-animate的最外层,我们最先接触到的Animate.js讲起。

Animate.js

简书上写大段的代码太费劲了,我就一小段一小段的解释吧。

constructor

这个没什么难点,就是普通的初始化。currentlyAnimatingKeys和childrenRefs在后续会比较重要,使用次数比较多

constructor(props) {
  super(props);
  // 这个数组用来储存正在进行动画的dom节点的key值
  this.currentlyAnimatingKeys = {};
  // 这两个数组就顾名思义啦
  this.keysToEnter = [];
  this.keysToLeave = [];
  this.state = {
    // 这里的toArrayChildren和getChildrenFromProps都是上面讲的两个工具类里的工具方法
    children: toArrayChildren(getChildrenFromProps(props)),
  };
  // 储存ref
  this.childrenRefs = {};
}

componentDidMount

componentDidMount() {
  const showProp = this.props.showProp;
  let children = this.state.children;
  // showProp是Animate组件提供的一个API,原文解释为:'子级动画的类型,显示或隐藏'
  // 完全不理解他的意思,刚好官网的代码示例也没有用带这个参数,我们就暂且跳过他,给他一个null值吧
  if (showProp) {
    children = children.filter((child) => {
      return !!child.props[showProp];
    });
  }
  // 遍历子节点,并调用performAppear方法,这个方法名翻译一下就是:‘执行显示’,所以这就是我们的css效果的第一个触发点
  // 完成组件挂载后,让组件执行css动画,也就是常见的淡入效果之类的。
  children.forEach((child) => {
    if (child) {
      this.performAppear(child.key);
    }
  });
}

performAppear

performAppear这个方法是Animate组件自己的实例方法

performAppear = (key) => {
  if (this.childrenRefs[key]) {
    // 用key值找到目标组件,然后对currentlyAnimatingKeys数组做一个标识,告诉大家,这个组件正在进行动画
    this.currentlyAnimatingKeys[key] = true;
    // 调用实例上的方法,这个componentWillAppear方法,是Ref对应的实例自己提供的一个实例方法(这里实例对应的Class是AnimateChild)
   // 可以将componentWillAppear理解为AnimateChild的一个生命周期函数,Animate就是执行这个生命周期函数的框架
  // 传递给可以将componentWillAppear的方法是一个回调函数
    this.childrenRefs[key].componentWillAppear(
      this.handleDoneAdding.bind(this, key, 'appear')
    );
  }
}

我想按照调用组件时代码的执行顺序来梳理整个库的代码,所以接下来不一一说明Animate类里的方法了,而是讲他的render函数与AnimateChild.js的内容

render

    render() {
      const props = this.props;
      this.nextProps = props;
      const stateChildren = this.state.children;
      let children = null;
      // 先map拿到被AnimateChild包装好的组件数组
      if (stateChildren) {
        children = stateChildren.map((child) => {
          if (child === null || child === undefined) {
            return child;
          }
          if (!child.key) {
            throw new Error('must set key for <rc-animate> children');
          }
          // 返回的AnimateChild组件,childrenRefs数组里储存的就是AnimateChild组件的实例
          return (
            <AnimateChild
              key={child.key}
              ref={node => this.childrenRefs[child.key] = node}
              animation={props.animation}
              transitionName={props.transitionName}
              transitionEnter={props.transitionEnter}
              transitionAppear={props.transitionAppear}
              transitionLeave={props.transitionLeave}
            >
              {child}
            </AnimateChild>
          );
        });
      }
      const Component = props.component;
      // 这个component是暴露出来的api,用来指定需要替换的标签
      // 这里可以看到,如果没有component的话,会只渲染组件数组的第一项
      // 有component则会全部渲染,因该是为了保证有一个父级节点去包裹,以便进行动画
      if (Component) {
        let passedProps = props;
        if (typeof Component === 'string') {
          passedProps = {
            className: props.className,
            style: props.style,
            ...props.componentProps,
          };
        }
        return <Component {...passedProps}>{children}</Component>;
      }
      return children[0] || null;
    }

AnimateChild

AnimateChild组件并没有react原生的生命周期函数包括constructor,render函数也只是简单的返回了this.props.children。由此可见,AnimateChild其实是一个类,提供一些接口。下面,主要了解一下他的接口都是做什么的。

先从我们上边讲的performAppear函数调用的componentWillAppear函数开始讲

componentWillAppear

AnimateChild组件的componentWillAppear,componentWillEnter,componentWillLeave全都一样,判断一下是否做动画,做就调用transition方法,不然就调用done。
然后说transition,这是AnimateChild的核心方法

    componentWillAppear(done) {
      if (animUtil.isAppearSupported(this.props)) {
        this.transition('appear', done);
      } else {
        done();
      }
    }

transition

    transition(animationType, finishCallback) {
      const node = ReactDOM.findDOMNode(this);
      const props = this.props;
      const transitionName = props.transitionName;
      const nameIsObj = typeof transitionName === 'object';
      this.stop();
      const end = () => {
        this.stopper = null;
        finishCallback();
      };
      // props.animation是如果使用第三方动画类库需要传入的对象,props[transitionMap[animationType]]是一个布尔值,对应着transitionAppear等三个api
    // 这个判断翻译过来就是,支持css动画或者不使用css类库并且有自己的css类名并且的确要做这一步的动画
      if ((isCssAnimationSupported || !props.animation[animationType]) &&
        transitionName && props[transitionMap[animationType]]) {
        // 这一大串,都是在拼接字符串,拼接出要添加的类名
        const name = nameIsObj ? transitionName[animationType] : `${transitionName}-${animationType}`;
        let activeName = `${name}-active`;
        if (nameIsObj && transitionName[`${animationType}Active`]) {
          activeName = transitionName[`${animationType}Active`];
        }
        // 核心中的核心,调用cssAnimate方法并传入回调
        this.stopper = cssAnimate(node, {
          name,
          active: activeName,
        }, end);
      } else {
        this.stopper = props.animation[animationType](node, end);
      }
    }

cssAnimate

cssAnimate库包含两个js文件,一个index.js是提供给我们使用的,用来触发动画或者过渡。另一个event.js是支撑index.js的运行的,event.js在内部封装了函数,用来检测浏览器是否执行完了css动画。(这是一个新的知识点,之前从未了解过。上边讲的cssAnimate方法是index.js提供的。下面我们先说他。

index

index.js里的工具方法与细枝末节就不说了,直接说cssAnimate这个函数

    const cssAnimation = (node, transitionName, endCallback) => {
      // 先是一大串字符串拼接,得出要添加的class类名
      const nameIsObj = typeof transitionName === 'object';
      const className = nameIsObj ? transitionName.name : transitionName;
      const activeClassName = nameIsObj ? transitionName.active : `${transitionName}-active`;
      let end = endCallback;
      let start;
      let active;
      // 这个classes是引用的component-classes这个库的api,用来跨浏览器操作dom元素的类名,理解为jq吧,但是只有类名的增删改查
      const nodeClasses = classes(node);
      // 如果是对象,就解构一下,拿到真正的回调函数
      if (endCallback && Object.prototype.toString.call(endCallback) === '[object Object]') {
        end = endCallback.end;
        start = endCallback.start;
        active = endCallback.active;
      }
      // 这是cssAnimate自己定义的一个方法,动画结束时的回调
      if (node.rcEndListener) {
        node.rcEndListener();
      }

      node.rcEndListener = (e) => {
        if (e && e.target !== node) {
          return;
        }
  
        if (node.rcAnimTimeout) {
          clearTimeout(node.rcAnimTimeout);
          node.rcAnimTimeout = null;
        }

        clearBrowserBugTimeout(node);

        nodeClasses.remove(className);
        nodeClasses.remove(activeClassName);

        Event.removeEndEventListener(node, node.rcEndListener);
        node.rcEndListener = null;

        // Usually this optional end is used for informing an owner of
        // a leave animation and telling it to remove the child.
        if (end) {
          end();
        }
      };
      // 绑定动画结束的监听事件
      Event.addEndEventListener(node, node.rcEndListener);
      // 真正开始了
      if (start) {
        start();
      }
      nodeClasses.add(className);
      // 这里延时加载了实现动画效果的activeClassName,还有生命周期函数active
      node.rcAnimTimeout = setTimeout(() => {
        node.rcAnimTimeout = null;
        nodeClasses.add(activeClassName);
        if (active) {
          setTimeout(active, 0);
        }
        fixBrowserByTimeout(node);
        // 30ms for firefox
      }, 30);
      // node.rcEndListener在自己被调用之后会将自己置为空,我认为这里的返回是为了提供一个手动停止动画的方法
      return {
        stop() {
          if (node.rcEndListener) {
            node.rcEndListener();
          }
        },
      };
    };

这里就结束了,梳理之后对于react实现动画就有了理解了,用起来不会那么迷糊。其实感觉实现的代码不是特别难,主要是逻辑分层非常清晰。学习到了一个好的代码应该是什么样的。解耦功能,起好变量名,完善而清晰的逻辑,这些是学到的最重要的东西

相关文章

网友评论

      本文标题:react动画开源库代码学习之rc-animate

      本文链接:https://www.haomeiwen.com/subject/ypmtfftx.html