看接下来的内容之前, 需要明白react以及github上已经提供了足够多的控件来供平常使用, 需要控件的时候可以查看. 如antd, react-components等. 这篇文章只是提供一个改造方案的解决思路, 如果有某些特殊需求的控件需要实现才手动改造.
引入jquery
比较推荐全局引入jquery包, 减少打包的依赖而且有cdn可用, 引入其他jquery插件时也比较方便. 坏处是会暴露全局变量$, 不过并不是特别大影响. 如果不想全局引入, 可以在webpack里设置alias和ProvidePlugin结合expose-loader, 也可以达到类似全局效果(通过expose-loader和ProvidePlugin自动解析$变量), 引入其他插件.
引入插件包
jquery全局可用以后, 插件包在什么地方加载都没区别, 可以在页面上引入, 也可以使用import. 这次拿来改造举例的是datetime-picker这个插件.
改造插件
首先创建自己的组件, 由于是结合jquery, 那么肯定有自己的生命周期函数. 顺便带上componentLogger做生命周期日志.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | import React from 'react'; import { PropTypes as P } from 'prop-types'; import _ from 'lodash'; import classNames from 'classnames';
  import { componentLogger } from './../../common/decorators';
  @componentLogger class DateTimePicker extends React.PureComponent {   static propTypes = {};   constructor(props) {     super(props);     this.state = {};   }   componentDidMount() {}   componentWillReceiveProps(nextProps) {}   componentDidUpdate() {}   render() {     return null;   }   componentWillUnmount() {} }
  export default DateTimePicker;
   | 
 
先在render中写好结构和样式, 给上ref, 为使用jquery做准备. 这里为了方便自定义样式, 外层div和内层input的className都可以通过props传入.
1 2 3 4 5 6
   | render() {     return (<div className={classNames("date-time-picker input-group date", this.props.className)} ref="pickerDiv">         <input ref="pickerInput" type="text" readOnly className={classNames("form-control", this.props.inputClassName)} {...this.state.inputProps} />         <span className="input-group-addon"><span className="glyphicon glyphicon-calendar"></span></span>     </div>) }
  | 
 
然后给它在componentDidMount中加上初始化函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | componentDidMount() {     this.resetDateTimePicker(this.state.options);     this.refs.pickerInput.value = this.props.value; } resetDateTimePicker(options) {     $(this.refs.pickerDiv).datetimepicker(options)         .on('changeDate', (event) => {             if (this.refs.pickerInput.value != this.props.value) {                 this.props.onChange({                     target: this.refs.pickerInput,                     stopPropagation: event.stopPropagation,                     preventDefault: event.preventDefault,                 });             }         }); }
  | 
 
这里的options便是插件文档中给定可以设置的options了, 需要让它有默认值而且也可以从外部传入, 那么实现的地方应该在constructor和componentWillReceiveProps两个地方了.
1 2 3 4 5 6 7 8 9 10 11 12
   | constructor(props) {     super(props);     this.state = {         ...splitDateTimePickerOptionsAndInputProps(props),     } } componentWillReceiveProps(nextProps) {     this.setState(...splitDateTimePickerOptionsAndInputProps(nextProps));     if (nextProps.value != this.props.value) {         this.refs.pickerInput.value = nextProps.value;     } }
  | 
 
splitDateTimePickerOptionsAndInputProps这个函数即从props里拿到设置值和混合默认值的函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | const defaultDateTimePickerOptions = {     language: 'zh-CN',     format: "yyyy-mm-dd",     autoclose: true,     todayHighlight: true,     forceParse: true,     minView: 2,     maxView: 5,     todayBtn: true, };
  const needRemoveInputProps = ['onChange', 'value', 'className', 'inputClassName'];
  const splitDateTimePickerOptionsAndInputProps = (props) => {     const propsKeyStartsWith = 'dateTime';     let options = {         ...defaultDateTimePickerOptions,     };     let inputProps = {};     for (let key in props) {         if (key.startsWith(propsKeyStartsWith)) {             options[key.substring(propsKeyStartsWith.length)] = props[key];         } else if (needRemoveInputProps.indexOf(key) == -1) {             inputProps[key] = props[key];         }     }     return { options, inputProps }; }
  | 
 
需要注意的是, defaultDateTimePickerOptions, needRemoveInputProps, splitDateTimePickerOptionsAndInputProps这种可能会改变或变动的代码, 最好写在组件外面. defaultDateTimePickerOptions即如果没有传入相关设置值的清空下使用的默认值, needRemoveInputProps表示这些值是特殊的, 不会传递到input组件上, 其他的值提取出来后都可以传递到input组件上. 在splitDateTimePickerOptionsAndInputProps函数中, 把以dateTime开头的props都拿出来, 放到options对象中, 其他的放到inputProps对象中, 最后将它们返回.
不过现在还有个问题, 如果外面的props改变了, 控件需要重新加载.
1 2 3 4 5 6 7 8 9 10 11
   | componentWillReceiveProps(nextProps) {     this.setState({...splitDateTimePickerOptionsAndInputProps(nextProps)});     if (nextProps.value != this.props.value) {         this.refs.pickerInput.value = nextProps.value;     } } componentDidUpdate(prevProps, prevState) {     if (!_.isEqual(prevState.options, this.state.options)) {         this.resetDateTimePicker(this.state.options);     } }
  | 
 
为了优化性能, 这里使用了lodash来深度比较两个对象, 如果有不同的options, 才重置DateTimePicker.
再在componentWillUnmount中加上析构函数
1 2 3
   | componentWillUnmount() {     $(this.refs.pickerDiv).datetimepicker('remove'); }
  | 
 
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
   | import React from 'react'; import { PropTypes as P } from 'prop-types'; import _ from 'lodash'; import classNames from 'classnames';
  import { componentLogger } from './../../common/decorators';
  const defaultDateTimePickerOptions = {   language: 'zh-CN',   format: 'yyyy-mm-dd',   autoclose: true,   todayHighlight: true,   forceParse: true,   minView: 2,   maxView: 5,   todayBtn: true, };
  const needRemoveInputProps = ['onChange', 'value', 'className', 'inputClassName'];
  const splitDateTimePickerOptionsAndInputProps = (props) => {   const propsKeyStartsWith = 'dateTime';   let options = {     ...defaultDateTimePickerOptions,   };   let inputProps = {};   for (let key in props) {     if (key.startsWith(propsKeyStartsWith)) {       options[key.substring(propsKeyStartsWith.length)] = props[key];     } else if (needRemoveInputProps.indexOf(key) == -1) {       inputProps[key] = props[key];     }   }   return { options, inputProps }; };
  @componentLogger class DateTimePicker extends React.PureComponent {   static propTypes = {};   constructor(props) {     super(props);     this.state = {       ...splitDateTimePickerOptionsAndInputProps(props),     };   }   resetDateTimePicker(options) {     $(this.refs.pickerDiv)       .datetimepicker(options)       .on('changeDate', (event) => {         if (this.refs.pickerInput.value != this.props.value) {           this.props.onChange({             target: this.refs.pickerInput,             stopPropagation: event.stopPropagation,             preventDefault: event.preventDefault,           });         }       });   }   componentDidMount() {     this.resetDateTimePicker(this.state.options);     this.refs.pickerInput.value = this.props.value;   }   componentWillReceiveProps(nextProps) {     this.setState({ ...splitDateTimePickerOptionsAndInputProps(nextProps) });     if (nextProps.value != this.props.value) {       this.refs.pickerInput.value = nextProps.value;     }   }   componentDidUpdate(prevProps, prevState) {     if (!_.isEqual(prevState.options, this.state.options)) {       this.resetDateTimePicker(this.state.options);     }   }   render() {     return (       <div className={classNames('date-time-picker input-group date', this.props.className)} ref='pickerDiv'>         <input ref='pickerInput' type='text' readOnly className={classNames('form-control', this.props.inputClassName)} {...this.state.inputProps} />         <span className='input-group-addon'>           <span className='glyphicon glyphicon-calendar'></span>         </span>       </div>     );   }   componentWillUnmount() {     $(this.refs.pickerDiv).datetimepicker('remove');   } }
  export default DateTimePicker;
 
  <span className='toolbar-search '>   <span className='toolbar-search-label'>选择时间:</span>   <DateTimePicker     type='text'     placeholder='请选择时间'     {...dataValueBindHandler(this, 'searchParam', 'CompleteTime')}     inputClassName='toolbar-search-input'   /> </span>;
   |