美文网首页首页推荐React.js
React高阶组件之rc-notification源码解析

React高阶组件之rc-notification源码解析

作者: 打铁大师 | 来源:发表于2018-08-30 23:28 被阅读4次

    github地址

    demo地址

    React Notification UI 组件

    安装

    npm install rc-notification
    

    用法

     var Notification = require('rc-notification');
         Notification.newInstance({}, notification => {
         notification.notice({
          content: 'content'
      });
    });
    

    API

    Notification.newInstance(props, (notification) => void) => void

    props 详情:

    name type default description
    prefixCls String notification 容器css类名的前缀
    style Object {'top': 65, left: '50%'} notification容器额外的样式
    getContainer Function:HTMLElement 返回html节点,作为notification 挂载容器

    notification.notice(props)

    props 详情

    name type default description
    content React.Element notice的内容
    key String notice的id
    closable Boolean 实现显示关闭按钮
    onClose Function 当notice关闭时会被调用
    duration number 1.5 notice显示的时间(秒)
    style Object { right: '50%' } 单个notice的额外样式
    maxCount number notices显示的最大数量,当超出限制,会移除第一个notice
    closeIcon ReactNode 关闭icon

    notification.removeNotice(key:string)

    使用key移除单个notice

    notification.component

    notification组件自身的引用

    notification.destroy()

    销毁当前的notification组件

    源码包括Notice组件 和 Notification组件

    Notice组件对应单个notice,Notification对应多个notice

    Notice源码:

    import React, {Component} from 'react';
    import classNames from 'classnames';
    import PropTypes from 'prop-types';
    
    export default class Notice extends Component {
      //规定了每个属性的类型,any表示任意类型
      static propTypes = {
        duration: PropTypes.number, //组件挂载后多久关闭
        onClose: PropTypes.func, //当notice关闭后被调用的函数
        children: PropTypes.any, //子元素
        update: PropTypes.bool, //update属性为true时,组件更新会重启定时器
        closeIcon: PropTypes.node //指定的close icon
      };
    
      //定义了默认的属性
      static defaultProps = {
        onClose() {
        }, //Notice没有定义关闭的具体细节,由使用者发挥
        duration: 1.5,
        style: {
          right: '50%'
          }
      };
    
      componentDidMount() {
        //启动关闭组件的定时器
        this.startCloseTimer();
      }
    
      componentDidUpdate(prevProps) {
        if (this.props.duration !== prevProps.duration
          || this.props.update) {
          this.restartCloseTimer();
        }
      }
    
      componentWillUnmount() {
        //组件卸载了,清除定时器
        this.clearCloseTimer();
      }
    
      close = () => {
        this.clearCloseTimer();
        this.props.onClose();
      };
    
      startCloseTimer = () => {
        //duration 默认为1.5秒。if的含义是,duration不能设置为0和null,undefined
        if (this.props.duration) {
          this.closeTimer = setTimeout(() => {
            this.close();
          }, this.props.duration * 1000);
        }
      };
    
      clearCloseTimer = () => {
        if (this.closeTimer) {
          //清除定时器,并将this.closeTimer设置为null
          clearTimeout(this.closeTimer);
          this.closeTimer = null;
        }
      };
    
      restartCloseTimer() {
        //重启关闭组件的定时器。重启前,先清除定时器。
        this.clearCloseTimer();
        this.startCloseTimer();
      }
    
      render() {
        const props = this.props;
        //componentClass表示整个组件的样式前缀
        const componentClass = `${props.prefixCls}-notice`;
        //使用classNames得出组件实际的css类,因为classNames会过滤掉值为false的css类。
        const className = {
          [`${componentClass}`]: 1,
          [`${componentClass}-closable`]: props.closable,
          [props.className]: !!props.className,
        };
        return (
          //鼠标移到组件时,清除定时器。移出组件,开启定时器。
          //组件没有定义样式。使用者可以传入样式类的前缀prefixCls。
          <div className={classNames(className)} style={props.style} onMouseEnter={this.clearCloseTimer}
               onMouseLeave={this.startCloseTimer}
          >
            <div className={`${componentClass}-content`}>{props.children}</div>
            {props.closable ?
              <a tabIndex="0" onClick={this.close} className=    {`${componentClass}-close`}>
                {props.closeIcon || <span className={`${componentClass}-close-x`}/>}
              </a> : null
            }
          </div>
        );
      }
    }
    

    Notification组件源码:

    import React, {Component} from 'react';
    import PropTypes from 'prop-types';
    import ReactDOM from 'react-dom';
    import Animate from 'rc-animate';
    import createChainedFunction from 'rc-util/lib/createChainedFunction';
    import classnames from 'classnames';
    import Notice from './Notice';
    
    let seed = 0;
    const now = Date.now();
    
    function getUuid() {
    return `rcNotification_${now}_${seed++}`;
    }
    
    class Notification extends Component {
      //定义props对象的属性值类型
      static propTypes = {
        prefixCls: PropTypes.string,
        transitionName: PropTypes.string,
        animation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
        style: PropTypes.object,
        maxCount: PropTypes.number,
        closeIcon: PropTypes.node,
      };
    
      //设置props对象的属性值的默认值
      static defaultProps = {
        prefixCls: 'rc-notification',
        animation: 'fade',
        style: {
          top: 65,
          left: '50%'
        }
      };
    
      state = {
        notices: [],
      };
    
      getTransitionName() {
        const props = this.props;
        let transitionName = props.transitionName;
        if (!transitionName && props.animation) {
          transitionName = `${props.prefixCls}-${props.animation}`;
        }
        return transitionName;
      }
    
      //add方法保证了notice不会重复加入到notices队列中。
      add = (notice) => {
        //key表示一个notice的id
        const key = notice.key = notice.key || getUuid();
        const {maxCount} = this.props;
        this.setState(previousState => {
          const notices = previousState.notices;
          //要添加的notice是否已经存在
          const noticeIndex = notices.map(v => v.key).indexOf(key);
          //使用concat()来复制notice数组
          const updatedNotices = notices.concat();
          //如果要加的notice已经存在
          if (noticeIndex !== -1) {
            //删除已存在的notice,加入要添加的notice
            updatedNotices.splice(noticeIndex, 1, notice);
          } else {
            //如果设置了maxCount,且notices中的数量已达到maxCount的限制,那么移除第一个notice
            if (maxCount && notices.length >= maxCount) {
              //updateKey设置为最初移除的key,最要是为了利用已存在的组件
              notice.updateKey = updatedNotices[0].updateKey || updatedNotices[0].key;
              updatedNotices.shift();
            }
            //加入的要添加的notice
            updatedNotices.push(notice);
          }
          return {
            notices: updatedNotices,
          };
        });
      };
    
      remove = (key) => {
        this.setState(previousState => {
          return {
            notices: previousState.notices.filter(notice => notice.key !== key),
          };
        });
      };
    
      render() {
        const props = this.props;
        const {notices} = this.state;
        const noticeNodes = notices.map((notice, index) => {
          //如果notice是数组最后一个,且存在updateKey。
          // 说明,该notice添加进来之前,数组已经达到maxCount,并挤掉了数组的第一个noitce。
          // update 为true,是由于重用了之前被挤掉的notice的组件,需要更新重启Notice组件的定时器
          const update = Boolean(index === notices.length - 1 && notice.updateKey);
          //key相同,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新。
          const key = notice.updateKey ? notice.updateKey : notice.key;
          //createChainedFunction目的是,让this.remove函数,notice.onClose函数能够接收相同的参数,并一同调用。
          //即调用onClose时,会先调用this.remove,再调用notice.onClose
          const onClose = createChainedFunction(this.remove.bind(this, notice.key), notice.onClose);
          return (<Notice
            prefixCls={props.prefixCls}
            {...notice}
            key={key}
            update={update}
            onClose={onClose}
            closeIcon={props.closeIcon}
          >
            {notice.content}
          </Notice>);
        });
        const className = {
          [props.prefixCls]: 1,
          [props.className]: !!props.className,
        };
        return (
          <div className={classnames(className)} style={props.style}>
            <Animate transitionName={this.getTransitionName()}>{noticeNodes}</Animate>
          </div>
        );
      }
    }
    
    Notification.newInstance = function newNotificationInstance(properties, callback) {
      const {getContainer, ...props} = properties || {};
      const div = document.createElement('div');
      if (getContainer) {// getContainer是函数,返回HTMLElement。返回结果将作为notification挂载的容器
        const root = getContainer();
        root.appendChild(div);
      } else {
        document.body.appendChild(div);
      }
      //由上可知,其实notification最后都挂载在div中
      let called = false;
    
      //参数notification是指Notification组件
      function ref(notification) {
        //设置变量called的原因是
        //因为ref回调会在一些生命周期回调之前执行,这里为了保证ref回调在组件的所有生命周期中只执行完整的一次,所以才使用了一个布尔值。
        if (called) {
          return;
        }
        called = true;
        //传进来的callback函数,接收一个对象,该对象可以增加notice,移除notice,提供组件的引用,销毁组件
        callback({
          notice(noticeProps) {
            notification.add(noticeProps);
          },
          removeNotice(key) {
            notification.remove(key);
          },
          component: notification, //compoent可以向外暴露Notification实例
          destroy() {
            ReactDOM.unmountComponentAtNode(div); //从div中移除已挂载的Notification组件
            div.parentNode.removeChild(div);//移除挂载的容器
          },
        });
      }
    
      ReactDOM.render(<Notification {...props} ref={ref}/>, div);
    };
    
    export default Notification;
    

    收获:

    Notification源码总共才200多行,但使用了很多react高级特性。读完之后收获不少,补充了一些以前不知道的知识点。

    具体收获如下:

    1. key相同,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新。
      key值不同,则react先销毁该组件(有状态组件的componentWillUnmount会执行),然后重新创建该组件(有状态组件的constructor和componentWillUnmount都会执行)

    2. boolean unmountComponentAtNode(DOMElement container)
      从 DOM 中移除已经挂载的 React 组件,清除相应的事件处理器和 state。如果在 container 内没有组件挂载,这个函数将什么都不做。如果组件成功移除,则返回 true;如果没有组件被移除,则返回 false。

    3. 使用 PropTypes 进行类型检查

    4. Refs 与 DOM

    相关文章

      网友评论

        本文标题:React高阶组件之rc-notification源码解析

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