首页推荐React.js

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

2018-08-30  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读