看接下来的内容之前, 需要明白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>;
|