【React教学】输入框自动完成提示——250行实现

原创
2016/05/18 14:11
阅读数 4.8K

为什么说使用React就是提高生产力呢?

我也不想多做解释了,大概类如以下这样的界面,用React实现,连HTML、JS、交互,250行不到,额外使用了jQuery和lodash。

完成效果大致如下:

以下是源代码:

var React = require('react');
var ReactDOM = require('react-dom');

const PropTypes = React.PropTypes;

var $ = jQuery;

class GoodsSearchInput extends React.Component {

	static defaultProps = {
		min: 2,
		max: 20,
		name: '',
		url: location.href,
		delay: 1000,
		debug: false,
		selectId: 0,
		selectName: '',
		tip: '请输入关键字'
	};

	static propTypes = {
		min: PropTypes.number,
		max: PropTypes.number,
		url: PropTypes.string,
		delay: PropTypes.number
	};

	constructor(props) {
		super(props);
		this.state = {
			value: this.props.value,
			category: '',
			results: {},
			searching: {},
			isHide: false,
			isSearch: true,
			selected: {
				id: this.props.selectId,
				name: this.props.selectName
			}
		};
	}

	tick = null;

	clearTick() {
		if (this.tick != null)
			clearTimeout(this.tick);
		return this;
	}

	resetTick() {
		this.clearTick();
		let delay = this.props.delay < 300 ? 300 : this.props.delay;
		this.tick = setTimeout(() => {
			this.search().then((result) => {
			}).catch((result) => {
			});
		}, this.props.delay);
		return this;
	}

	changeInput(value) {
		if (this.state.isSearch) {
			if (value.length >= this.props.min) {
				this.resetTick();
			}
			else {
				this.clearTick();
			}
		}
		this.setState({
			value: value,
			isHide: false,
			isSearch: true
		});
	}

	getValue() {
		return this.state.value;
	}

	search() {
		let me = this
			, state = this.state
			, value = this.getValue()
			, results = state.results
			, searching = state.searching;
		return new Promise((resolve, reject) => {
			if (typeof results[value] !== 'undefined') {
				if (results[value] === false)
					resolve(results[value]);
				else
					reject(results[value]);
			}
			else if (!searching[value]) {
				searching[value] = 1;
				me.setState({searching: searching});
				$.ajax({
					url: this.props.url,
					data: {
						search: value
						// category: value
					},
					dataType: 'json'
				}).done(function (data) {
					if (!data.count || data.count <= 0) {
						results[value] = false;
						searching[value] = 3;
						me.setState({
							results: results,
							searching: searching,
							isHide: false
						});
						reject(false);
					}
					else {
						results[value] = data;
						searching[value] = 2;
						me.setState({
							results: results,
							searching: searching
						});
						resolve(results[value]);
					}
				}).fail(function () {
					results[value] = false;
					searching[value] = 3;
					me.setState({
						results: results,
						searching: searching
					});
					reject(false);
				});
			}
		});
	}

	hideResult() {
		this.setState({isHide: true});
	}

	showResult() {
		this.setState({isHide: false});
	}

	selectItem(row) {
		this.setState({
			value: row.name,
			selected: row,
			isHide: true,
			isSearch: false
		});
	}

	isSelected() {
		return this.state.selected.id > 0;
	}

	cleanSelected() {
		this.setState({
			value: '',
			selected: {
				id: 0,
				name: ''
			}
		});
	}

	renderState() {
		let value = this.getValue(), searching = this.state.searching;
		switch (searching[value]) {
			case 1 :
				return <span className="searching">{`搜索“${this.state.value}”中`}</span>;
				break;
			case 2 :
				let result = this.state.results[value];
				if (this.state.isHide)
					return <span className="success">
						{`搜索“${this.state.value}”共${result.count}条结果`}
						<a onClick={() => this.showResult()} className="show">显示结果</a>
					</span>;
				else
					return <span className="success">
						{`搜索“${this.state.value}”共${result.count}条结果`}
					</span>;
				break;
			case 3 :
				return <span className="failure">{`搜索“${this.state.value}”无结果`}</span>;
				break;
			default :
				return this.props.tip;
		}
	}

	renderResult() {
		let result = this.state.results[this.getValue()];
		if (!result || result.count <= 0) {
			return '';
		}
		else if (!this.state.isHide) {
			return <div className="search-result abs">
				<div>
					{result.data.map((row) => {
						return <div key={'search_result_' + row.id}
						            className="search-item"
						            onClick={() => this.selectItem(row)}>
							<div className="search-icon"><img src={row.img}/></div>
							<div className="search-info">
								<div><strong>{row.name}</strong></div>
								<span className="goods-price">{row.price}</span>
							</div>
						</div>;
					})}
				</div>
				<div className="search-hide" onClick={() => this.hideResult()}>隐藏</div>
			</div>;
		}
	}

	renderHidden() {
		if (this.props.name) {
			return <input type="hidden"
			              name={this.props.name}
			              value={this.state.selected.id}/>
		}
	}

	render() {
		return <div className="">
			<div className="search-input-box">
				<div className="uk-form-icon input">
					<i className="uk-icon-search"/>
					<input type="text"
					       value={this.state.value}
					       min={this.props.min}
					       maxLength={this.props.max}
					       placeholder={`输入最少${this.props.min}个字符`}
					       onChange={(event) => this.changeInput(event.target.value)}
					       className="af-input"
					/>
				</div>
				<button type="button"
				        className="uk-button uk-button-primary"
				        title="清除选中结果"
				        disabled={!this.isSelected()}
				        onClick={() => this.cleanSelected()}>
					<i className="uk-icon-remove"/>
				</button>
			</div>
			{this.renderHidden()}
			<div className="rel">
				{this.renderResult()}
			</div>
			<div className="af-tip">
				{this.renderState()}
			</div>
		</div>;
	}
}

$.fn.goodsSearchInput = function (props) {
	if (!this.get(0))
		throw new ReferenceError('Invalid DOM Element!');
	else if (!this.prop('goods-search-input')) {
		props = props || {};
		props = _.merge(props, this.data());
		let input = ReactDOM.render(<GoodsSearchInput {...props}/>, this.get(0));
		this.prop('goods-search-input', input);
	}
	return this.prop('goods-search-input');
};

module.exports = GoodsSearchInput;

其实严格来说,还可以再优化一下,做成一个通用版本,代码也可以再少些,不过因为做的时候,是赶着项目的需求做的,所以暂时就不折腾了。

做的时候是命名为GoodsSearchInput,其实事后基本上所有自动检索部分的输入框都用了他。

忘记收了,如何调用呢?巨简单,这是js方法:

<div id="test"></div>
<script type="text/javascript">
	(function($) {
		$('#test').goodsSearchInput({
			name: 'test',
			url: 'http://localhost/test/search_goods.php',
			value: '商业包装设计',
		});
	})(jQuery)
</script>

下面是标签方法:

<?php

$goods = Goods::getCacheOne($value);
?>
<div id="<?php echo $attr['id']; ?>"
     goods-search-input
     class="af-min-height"
     data-url="<?php echo linkUri('ajax/search_goods', ['supplier_id' => $this->supplier->id]); ?>"
     data-name="<?php echo $attr['name']; ?>"
     data-value="<?php echo $goods->isExists() ? $goods->name : '' ?>"
     data-select-id="<?php echo $goods->isExists() ? $goods->id : 0 ?>"
     data-select-name="<?php echo $goods->isExists() ? $goods->name : '' ?>"></div>

写完调试完上述代码,用了1个小时左右的时间,增加样式调试0.5小时。剩下来的时间,可以去b站补两集番。

用React就是这样简单,如果你还没用,你out了。

展开阅读全文
打赏
3
16 收藏
分享
加载中
test
2016/05/21 10:21
回复
举报
曾建凯博主

引用来自“Holt_Vong”的评论

3楼猪我想问下。React组件化后,在非React为主的页面混合着用怎么样啊
对了,额外的说,如果你是指如何将jsx和es6混合到原有的js中的话,我建议你可以考虑使用webpack,你可以看我这篇教程 http://my.oschina.net/janpoem/blog/677791 ,webpack能很好的整合你的前端js,包括将jsx转译为一般的js,并支持实时预览调试。
2016/05/19 14:52
回复
举报
曾建凯博主

引用来自“Holt_Vong”的评论

3楼猪我想问下。React组件化后,在非React为主的页面混合着用怎么样啊
你看,我用jsx写了这个组件,并且在组件的末尾部分,将输出组件的方法注入到jQuery的扩展函数中。你看:$.fn.goodsSearchInput这里。所以到页面调用直接$(el).goodsSearchInput()即可。进一步的,考虑到易用性的问题,我们可以给GoodsSearchInput定一个静态方法,其实主要就是将ReactDOM.render(React.createElement(GoodsSearchInput, props), domEl)这个复杂的调用给封装一下,让页面调用起来的时候更直观一些。在jsx中,你就可以直接:ReactDOM.render(, domEl),比起枯燥的调用React接口要好用得多。
2016/05/19 14:50
回复
举报
3楼猪我想问下。React组件化后,在非React为主的页面混合着用怎么样啊
2016/05/19 13:54
回复
举报
更多评论
打赏
4 评论
16 收藏
3
分享
返回顶部
顶部