react 组件结合 jquery 组件

看接下来的内容之前, 需要明白react以及github上已经提供了足够多的控件来供平常使用, 需要控件的时候可以查看. 如antd, react-components等. 这篇文章只是提供一个改造方案的解决思路, 如果有某些特殊需求的控件需要实现才手动改造.

引入jquery

比较推荐全局引入jquery包, 减少打包的依赖而且有cdn可用, 引入其他jquery插件时也比较方便. 坏处是会暴露全局变量$, 不过并不是特别大影响. 如果不想全局引入, 可以在webpack里设置aliasProvidePlugin结合expose-loader, 也可以达到类似全局效果(通过expose-loaderProvidePlugin自动解析$变量), 引入其他插件.

引入插件包

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和内层inputclassName都可以通过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了, 需要让它有默认值而且也可以从外部传入, 那么实现的地方应该在constructorcomponentWillReceiveProps两个地方了.

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>;
作者

Mosby

发布于

2017-08-21

许可协议

评论