文档章节

【React教学】通用型DataTable组件——400行内

曾建凯
 曾建凯
发布于 2016/05/20 13:55
字数 4047
阅读 1812
收藏 3

其实严格意义来说,应该将Pagination(分页处理)和数据加载(AjaxLoad)作为一个独立的组件来处理,不过为了方便展示,就一股脑都做在这个Table里面了。

目前只实现到整个Table的数据加载,不包含单独更新某行某个单元格数据的状态处理。

这一次用到的类库也比较多,这里先汇总一下:

npm install webpack webpack-dev-server react react-dom jquery lodash babel-loader babel-preset-es2015 babel-preset-react babel-plugin-transform-class-properties babel-plugin-transform-es2015-block-scoping babel
-plugin-transform-es2015-computed-properties --save-dev

对,没错,好多,好啰嗦。

先上张预览图:

因为涉及到客户的资料,所以数据打了模糊滤镜了。

这个Table组件包含的特性:

  1. Ajax翻页
  2. 指定checkbox的字段
  3. checkbox翻页会保存(就是多页的checkbox的记录都维持着),这点对于大规模数据校对的时候很必要。
  4. 点击行选中checkbox
  5. 选中行(checkbox)更改行样式
  6. 支持数据排序,实际上数据排序用的是jquery-tablesort,没有嵌入到组件中,一个只有126行的table排序,非常实用。不过其实如果允许单元格数据更新的话,排序就要嵌入在renderTableBodyRows的方法中了。
  7. 有几个基本的事件,onMount,onInit,onRow,onFoot。
  8. 整个Table的操作(翻页)都是无刷新的。

其实要做一个满足各方面使用的Table组件,事情并不简单。除了Table组件外,我还定义了两个公共的方法,并放在了整个项目的入口文件中(webpack的entry),详情如下:

var _ = require('lodash');
var jQuery = require('jquery');
var React = require('react');
var ReactDOM = require('react-dom');

window.jQuery = jQuery;
window._ = _;
window.php = require('phpjs');

window.filterContent = function filterContent(value, column) {
	if (_.isObject(value)) {
		if (_.isArray(value)) {
			return value;
		}
		else if (React.isValidElement(value)) {
			return value;
		}
		else {
			if (value instanceof Date) {
				return php.date(column.format || this.props.defaultDateFormat || 'Y-m-d H:i:s', value);
			}
			else {
				return value.toString();
			}
		}
	}
	if (column) {
		if (column.options && column.options[value])
			return column.options[value];
		else if (column.datetime && value) {
			var timestamp = php.strtotime(value);
			if (timestamp > 0)
				return php.date(column.format || 'Y-m-d H:i', timestamp);
			return '';
		}
	}
	return value;
};

window.tag = function tag(tag, content, props) {
	return React.createElement(tag, props || {}, filterContent(content));
};


var Table = require('./components/Table.jsx');

React开发要点精讲

filterContent函数

filterContent方法,用于过滤指定的内容,这里的过滤指将内容过滤为符合React.isValidElement的内容,并可以嵌入在react的html标签中的内容。

这里其实是React很重要需要掌握的一个技巧。JS中有几种变量的类型:字符、数值、NULL、Boolean、Array、Object。除了Object以外,其他的类型都可以直接插入到react的html中作为内容使用。这里所谓直接插入,包括以下两种形式:

1. html方式(JSX)

<div>{filterContent(content)}</div>

2. 使用React的JS API方法

React.createElement('div', {}, filterContent(content));

尤其注意,所有Object类型,除了React.isValidElement()判断为有效的对象以外,直接用上述两种方法作为标签内容使用,都会抛出React的异常。包括正则表达式和Date对象。

filterContent方法允许传入第二个参数,就是对过滤内容的一些配置参数。这个函数其实是我正式版本的一个缩略版本,但其实已可以用于实用了。

补充说一下,判断为数组的时候,最好打扁遍历这个数组,然后依次将数组元素放入filterContent中执行,最后返回过滤完毕的数组即可,React会进行后续的处理。

tag函数

这个函数其实就是对外讲React.createElement的方法精简化输出实现的一个方法,因为直接生成有状态的DOMElement实在太实用了,让人忍不住想在任意地方去使用。实际上这个方法,就是上面说的创建React HTML标签的方法二。

第一个参数,其实是可以直接传入你自己自定义的React.Component的。

第二个参数就是要插入的内容

第三个参数是这个标签的属性,也就是React组件的props。

获取组件的DOM对象

这里需要粗略的说一下ReactComponent(ReactClass)从实例化->DOM实例化的状态切换。

我们在使用React.createClass或者extends React.Component时,定义大多数的方法和属性,面向的是DOM实例化状态下的对象的方法和属性。

在我们执行React.createElement(或者你直接new ReactComponent)的时候,实际上是创建了这个Component(ReactClass)的普通实例化对象,这个对象并不具备完整的属性(props)、状态(state),以及你所定义的方法。

在React中,并不推荐直接去操作这个普通的实例化对象,也没有提供太多的接口给你去操作。React认为只有渲染到DOM节点树上的对象才是有效的控制对象。如下:

var el = ReactDOM.render(<HelloWorld />, document.getElementById('test'));

ReactDOM.render返回的,才是一个DOM实例化的对象。

而<HelloWorld />则是普通实例化对象。

那么在已经生成了实例化对象的时候,我们该如何获得这个实例化对象所关联的DOM节点呢?

// 接着上一段代码
var domEl = ReactDOM.findDOMNode(el);

但是要注意,React有一套很严密的状态机处理的方法,有效的获取到这个DOM实例化对象的DOM节点,必须确保在componentDidMountcomponentDidUpdate之后,否则也会报异常。

好,今天要讲的内容基本上就到这,下面是Table组件的代码(Table.jsx):

var React = require('react');
var ReactDOM = require('react-dom');
var _ = require('lodash');
var php = require('phpjs');
var $ = require('jquery');

class Table extends React.Component {

	static defaultProps = {
		columns: {},
		mergeColumns: {},
		data: [],
		pageData: {},
		pageLinksCount: 10,
		pageOffset: 1,
		url: '',
		ajaxLoad: false,
		ajaxGetColumns: true,
		onInit: null,
		onRow: null,
		onFoot: null,
		onMount: null,
		checkbox: null,
		checked: [],
		thEmpty: '未指定表字段',
		tdEmpty: '未指定表数据',
		defaultDateFormat: 'Y-m-d H:i:s'
	};

	id = 0;

	updateMount = false;

	constructor(props) {
		super(props);
		this.id = _.uniqueId('table_');
		this.state = {
			error: null,
			ajaxLoading: false,
			ajaxGetColumns: true,
			columns: this.props.columns,
			mergeColumns: this.props.mergeColumns,
			data: this.props.data,
			pageData: this.props.data,
			goPage: 0,
			checked: this.props.checked
		};
	}

	makeKey() {
		return this.id + '_' + _.flattenDeep(arguments).join('_');
	}

	getCheckboxField() {
		if (this.props.checkbox && this.state.columns[this.props.checkbox])
			return 'checkbox_' + this.props.checkbox;
		return false;
	}

	getFields() {
		var fields = Object.keys(this.state.columns), checkboxField = this.getCheckboxField();
		if (checkboxField !== false)
			fields = [checkboxField].concat(fields);
		return fields;
	}

	getColumn(field) {
		let column = this.state.columns[field] || {}, checkboxField = this.getCheckboxField();
		if (field === checkboxField) {
			column.label = <input type="checkbox" value="check_all"
			                      onChange={(e) => this.checkAll(e.target.checked)} checked={this.isCheckedAll()}/>
		}
		else {
			if (_.isString(column))
				column = {label: column};
			else if (!_.isObject(column))
				column = {label: field};
			if (!column.label)
				column.label = field;
			if (this.state.mergeColumns[field])
				column = _.merge(column, this.state.mergeColumns[field]);
			if (_.isString(column))
				column = {label: column};
			else if (!_.isObject(column))
				column = {label: field};
			if (!column.label)
				column.label = field;
			if (this.state.mergeColumns[field])
				column = _.merge(column, this.state.mergeColumns[field]);
		}
		return column;
	}

	loadData(page) {
		this.setState({ajaxLoading: true});
		$.ajax({
			url: this.props.url,
			data: {
				columns: this.state.ajaxGetColumns ? 1 : 0,
				page: page || 1
			},
			dataType: 'json'
		}).success((data) => {
			data.ajaxGetColumns = false;
			data.ajaxLoading = false;
			data.goPage = data.pageData.pageNumber || 1;
			this.setState(data);
		}).fail(() => {
			this.setState({error: '网络错误,请重新尝试!'});
		})
	}

	getData() {
		return this.state.data || [];
	}

	isChecked(item) {
		return this.getCheckboxField() !== false && this.state.checked.length > 0 && _.indexOf(this.state.checked, item + '') > -1;
	}

	isCheckedAll() {
		var isChecked = false, checkboxField = this.getCheckboxField(), field = this.props.checkbox,
			data = this.getData(), length = data.length, counter = 0;
		if (checkboxField === false || this.state.checked.length <= 0)
			return false;
		_.each(data, (row) => {
			if (row[field] && this.isChecked(row[field]))
				counter += 1;
		});
		return counter >= length;
	}

	checkAll(isCheck) {
		var items = [], checkboxField = this.getCheckboxField(), field = this.props.checkbox;
		if (checkboxField !== false)
			_.each(this.getData(), function (row) {
				if (row[field])
					items.push(row[field]);
			});
		return this.checkItem(items, isCheck);
	}

	checkItem(item, isCheck) {
		isCheck = !!isCheck;
		let checked = this.state.checked;
		if (!_.isArray(item))
			item = [item];
		_.each(item, function (it) {
			it = it + '';
			if (isCheck) {
				if (_.indexOf(checked, it) < 0)
					checked.push(it);
			}
			else {
				var index = _.indexOf(checked, it);
				if (index > -1)
					checked.splice(index, 1);
			}
		});
		this.setState({checked: checked});
		return this;
	}

	checkRow(event, value) {
		var target = event.target, tag = target.tagName.toLowerCase();
		if (tag !== 'input' && tag !== 'a' && tag !== 'button') {
			this.checkItem(value, !this.isChecked(value));
		}
	}

	dom() {
		return ReactDOM.findDOMNode(this);
	}

	changeGoPage(value) {
		var pageData = this.state.pageData;
		if (isNaN(value))
			value = pageData.pageNumber || 1;
		this.setState({goPage: value});
	}

	componentDidMount() {
		var data = this.getData();
		if (this.props.ajaxLoad && this.props.url && data.length <= 0)
			this.loadData(this.props.pageOffset);

	}

	componentDidUpdate() {
		if (this.getData().length > 0 && _.isFunction(this.props.onMount))
			this.props.onMount.call(this, ReactDOM.findDOMNode(this));
	}

	renderTableHead() {
		return <thead>
		<tr>
			{this.renderTableHeadCells()}
		</tr>
		</thead>;
	}

	renderTableHeadCells() {
		let fields = this.getFields(), length = fields.length;
		if (length <= 0) {
			if (this.state.ajaxLoading) {
				return <th>正在获取数据,请稍候……</th>;
			}
			return <th>
				<div className="at-table-empty">{filterContent(this.props.tdEmpty)}</div>
			</th>;
		}
		return this.getFields().map((field) => {
			let column = this.getColumn(field), isSort = typeof column.sort === 'undefined' || !!column.sort,
				className = !isSort || field === this.getCheckboxField() ? 'no-sort' : 'sort-head';
			return <th key={this.makeKey('head', field)} data-field={field} className={className}>
				{this.getColumn(field).label}
			</th>;
		});
	}

	renderTableBody() {
		return <tbody>{this.renderTableBodyRows()}</tbody>
	}

	renderTableBodyRows() {
		let fields = this.getFields(), data = this.getData(), length = data.length,
			checkboxField = this.getCheckboxField(), checkbox = this.props.checkbox;
		if (length <= 0) {
			if (this.state.ajaxLoading) {
				return <tr>
					<td>
						<div className="at-ajax-loading">正在加载表数据,请稍候……</div>
					</td>
				</tr>;
			}
			return <tr>
				<td>
					<div className="at-table-empty">{filterContent(this.props.tdEmpty)}</div>
				</td>
			</tr>;
		}
		if (_.isFunction(this.props.onInit))
			this.props.onInit.call(this, data);
		return this.getData().map((row, i) => {
			var clone = _.clone(row);
			if (_.isFunction(this.props.onRow)) {
				this.props.onRow.call(this, row, clone);
			}
			return <tr key={this.makeKey('tr', i)}
			           className={clone[checkbox] && this.isChecked(clone[checkbox]) ? 'at-row-checked' : ''}
			           onClick={(e) => this.checkRow(e, clone[checkbox])}>
				{
					fields.map((field) => {
						let value = clone[field] || null;
						let data = {
							value: value,
							text: value,
							field: field,
							index: i
						};
						if (this.props.onRow[field] && _.isFunction(this.props.onRow[field])) {
							this.props.onRow[field].call(this, data, row);
						}
						if (field === checkboxField) {
							return <td key={this.makeKey('td', i, field)}
							           data-field={field}>
								<input type="checkbox" value={clone[checkbox]} key={this.makeKey('checkbox_', i, field)}
								       checked={this.isChecked(clone[checkbox])}
								       onChange={(e) => this.checkItem(clone[checkbox], e.target.checked)}/>
							</td>;
						}
						else {
							return <td key={this.makeKey('td', i, field)} data-field={field} data-sort-value={data.value}>
								{filterContent(data.text, this.getColumn(field))}
							</td>;
						}
					})
				}
			</tr>;
		});
	}

	renderTableFoot() {
		var foot = {
			data: {},
			show: false
		}, fields = this.getFields();
		if (_.isFunction(this.props.onFoot))
			this.props.onFoot.call(this, foot);
		if (foot.show) {
			return <tfoot>
			<tr className="at-sum-row">
				{
					fields.map((field) => {
						return <td key={this.makeKey('tfoot', field)}>{foot.data[field]}</td>
					})
				}
			</tr>
			</tfoot>;
		}
	}

	renderPagination() {
		let pageData = this.state.pageData, links = [], tail = [];
		if (pageData.pageNumber > 0 && pageData.pageSize > 0) {
			var current = parseInt(pageData.pageNumber), linksCount = parseInt(this.props.pageLinksCount),
				middle = parseInt(linksCount / 2),
				total = pageData.pageCount, start = 1, end = linksCount;
			if (total > linksCount) {
				if (current >= middle) {
					start = current - (middle - 1);
					end = linksCount + start - 1;
					if (start > 1) {
						links.push(<li className="pagination-item" key={this.makeKey('page_item_', 1)}>
							<a href={'#page/' + (1)} onClick={() => this.loadData(1)}>{1}</a></li>);
						end -= 1;
					}
					if (start > 2)
						links.push(<li className="pagination-item pagination-item pagination-omission"
						               key={this.makeKey('page_omission_', 'start')}><span>...</span></li>);
				}
				if (end >= total) {
					start -= end - total;
					end = total;
				}
				else {
					if (end < total - 1)
						tail.push(<li className="pagination-item pagination-item pagination-omission"
						              key={this.makeKey('page_omission_', 'end')}><span>...</span></li>);
					if (end !== total)
						tail.push(<li className="pagination-item" key={this.makeKey('page_item_', total)}>
							<a href={'#page/' + (total)} onClick={() => this.loadData(total)}>{total}</a></li>);
				}
			}
			for (let i = start; i <= end; i++) {
				let className = 'pagination-item';
				if (i == pageData.pageNumber)
					className += ' pagination-active';
				let link = <li className={className}
				               key={this.makeKey('page_item_', i)}
				               key={this.makeKey('page_item_', i)}><a href={'#page/' + (i)}
				                                                      onClick={() => this.loadData(i)}>{i}</a></li>;
				links.push(link);
			}
			return <div className="pagination-box">
				<ul className="pagination-list">
					<li className="pagination-item">
						{this.getCheckboxField() ? '选中' + this.state.checked.length + '行,' : ''}
						{pageData.recordCount && pageData.recordCount > 0 ? '共' + pageData.recordCount + '条记录' : ''}
					</li>
					{links.concat(tail)}
					<li className="pagination-item">
						<input type="text"
						       value={this.state.goPage}
						       onChange={(e) => this.changeGoPage(e.target.value)}/>
						<a href="#" onClick={() => this.loadData(this.state.goPage)}>跳转</a>
					</li>
				</ul>
			</div>;
		}
	}

	render() {
		return <div className={this.state.ajaxLoading ? 'at-table-loading' : ''}>
			<table className="at-table">
				{this.renderTableHead()}
				{this.renderTableBody()}
				{this.renderTableFoot()}
			</table>
			{this.renderPagination()}
		</div>;
	}
}

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

module.exports = Table;

这个Table组件,其实是从我的正式项目中抽离出来,并且针对第一阶段使用ReactJS碰到的一些问题重新做了调整和优化。要说的话,可能距离正经的开源还有距离,但自己日常用用还是没啥问题的。

如何调用呢?

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
	<link rel="stylesheet" type="text/css" href="normalize.css"/>
	<link rel="stylesheet" type="text/css" href="font-awesome.css"/>
	<link rel="stylesheet" type="text/css" href="main.css"/>
</head>
<body>
<div id="table_header"></div>
<div id="table_container"></div>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript" src="jquery.tablesort.js"></script>

<script type="text/javascript">
	(function() {
		var $ = jQuery;
		$(document).ready(function () {
			var total = 0;
			function confirmData() {
				alert('123');
			}
			$('#table_container').table({
				// ajax的数据url
				url: 'http://localhost/ajax/purchase.json',
				// 是否使用ajax加载
				ajaxLoad: true,
				// 数据内容,你可以不填写data,而让ajax来加载
				// data: [],
				// 默认页面的当前页,这个也会影响ajax第一次优先加载第几页
				pageOffset: 1,
				// 分页的连接显示多少个,实际上无论如何都会按照双数-1,即19 => 19,20 => 19
				pageLinksCount: 20,
				// checkbox对应的字段
				checkbox: 'OrderID',
				// 已经选中的行
				checked: ['120014', '120009'],
				// 表字段的设置,如果ajaxLoad,建议这里留空,附加的字段可以用mergeColumns来设定
				// columns: {},
				// 额外附加的字段说明,他会和columns相关的字段的设定内容合并
				mergeColumns: {
					DeliveryDate: { datetime: true, format: 'Y-m-d' },
					OrderDate: { datetime: true, format: 'Y-m-d' },
					Checked: { options: { 0: tag('strong', '否', { className: 'red' }), 1: tag('strong', '是') } },
					Valid: { sort: false }
				},
				// 初始化接口,这里实际上是渲染到table head的时候,所以这里请不要做任何关于DOM节点的操作
				onInit: function(data) {
					total = 0;
				},
				// 这里实际上是应该叫做onDataMount,也即,当加载了有效的表数据的时候,才会执行这个结果
				// 但因为他执行的时机实际上是比React渲染完成要略早的,所以这里执行的内容还是给一个延迟吧
				onMount: function(dom) {
					setTimeout(function() {
						// 这里我们对这个表绑定了一个tablesort的操作,翻页的时候这个tablesort会更新
						// 但这里就不处理翻页时默认的排序状态了。
						$($(dom).find('table')).tablesort({

						}).sort('th.sort-th');
					}, 500);
				},
				// 每一行数据的处理过滤方式,下面这里这个演示的是针对每一行的每一个字段的过滤方式
				onRow: {
					// data是一个object,结构为:{ value: value, text: value, field: field, index: rowIndex }
					// value为原值,text也是原值,但输出的时候会使用text来输出,而不使用value,field是字段名,index是行号
					// row则是当前行的数据,因为过滤某个单元格的数据时,还是需要使用到行数据的。
					OrderID: function(data, row) {
						data.text = tag('strong', data.value);
						total += parseInt(data.value) || 0;
					},
					Valid: function(data, row) {
						data.text = tag('button', '未核实', { onClick: confirmData });
					}
				},
				// 下面是行数据过滤的另一个版本,这个方式只能针对一行做过滤,两种模式只能任选一种
				// row是默认的行数据,clone是复制出来的行数据,经过这个接口后,输出的每一行的数据实际上使用的是clone的内容
				// 所以要通过这里修改输出的内容,请直接修改clone
//				onRow: function(row, clone) {
//
//				},
				// foot这里只有两个属性:show 是否显示,data 相关显示在tfoot行的数据,一般tfoot主要用来输出汇总的数据内容
				onFoot: function(foot) {
					foot.show = true;
					foot.data = {
						OrderID: total
					}
				}
			});
		});

	}) (jQuery);

</script>
</body>
</html>

使用说明已经在注释中了,具体就不做多解释了。

额外补充一些说明,ajax的数据格式:

{
    "columns": {
        "id": ["label" => "主键"]
    },
    "data": [
        {
            "id": 1,
            "name": "hello"
        }
    ],
    "pageData": {
        "pageCount": 426,
        "pageNumber": "1",
        "pageParam": "page",
        "pageSize": 20,
        "recordCount": 8513
    }
}

后记

其实在过去的2年里,我一直在考虑如何简化后端程序员如何简化操作HTML复杂性的问题。所以在AgiMVC后续的升级版本已经kephp中,都实现了HTML部分的操作函数在内。设计的思想就是用函数名取代繁琐的HTML标签嵌套,并且允许用户实现自定的函数,以实现自定义的标签组合。

而实际上当看到React的时候,我发现自己的想法,和他出发点是很相似的。而React的虚拟化DOM操作,很像我06-07年在某个网站写的一套基于内存操作DOM节点的方法。当然总体而言,React走得更远,还包括了ReactNative。

所以我在对ReactJS有了一个整体性的了解以后,决定入他的坑。

现在前端MVC实在太多,已经进入了前端写模板的时代了。后端程序只要关心数据接口的准确性,前端可以包揽一切。

比起诸多的jade、handlebars、mustcache等js前端模板语言而言,ReactJS最大的优势是保持了HTML与JS混合编写,并实时调用JS变量的内容,没有再经过一层模板系统过滤。这种方式使得你写出来的HTML标签,最终实际上是以JS API的方式保存的,对于团队而言,无非就是有一个写JS的地方而已。而无需额外再去学习一套模板的引擎自己一时脑洞设计出来的模板语言。

保持了DOM节点的另外一个好处就是,能够与HTML规范与时俱进,比如 SVG,这里的好处实在太多。同时还能够因应浏览器的JS引擎升级而升级,完全不需要去改变什么。

当然转用了ReactJS以后,并不能够马上改善后端程序员写HTML的局面,这需要有一个量变到质变的累积。

而经历过这么多年的前端改革洗礼,我已经决定,整个团队的前端的ReactJS组件,由自己的团队成员来写,杜绝使用任何外部插件,因为其实所有的插件,都只是因应一时一刻某一特定环境写成,比如jQuery系列的插件,进入到ReactJS时代,其实80%都可以作废扔掉了。而目前大多数的ReactJS插件,实际上也只是针对某个CSS框架,或者某套UI规范写的,如果哪天你觉得这个CSS看着烦了,要换,基本上全部代码作废。作为UI框架,应该考虑得更远,也应该考虑得更全面。这也包括整个团队的前端打包构建规范,一次性代码、多次性使用的问题。

好吧,随意东拉西扯的,扯远了!

忽然想用ReactJS来写一套替代phpmyadmin的东西,phpmyadmin现行版本实在太扯,各种bug,也敢release,要不要脸了。

© 著作权归作者所有

曾建凯

曾建凯

粉丝 336
博文 66
码字总数 104794
作品 0
广州
技术主管
私信 提问
加载中

评论(1)

哈哈123er
感谢
从 0 到 1 实现 React 系列 —— 4.优化 setState 和 ref 的实现

看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/...) 从 0 到 1 实现 Reac...

牧云云
2018/08/05
0
0
从 0 到 1 实现 React 系列 —— 4.setState优化和ref的实现

看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/...) 从 0 到 1 实现 Reac...

牧云云
2018/08/05
0
0
为什么选用 React 创建混合型移动应用?

【编者按】本文作者为 14islands 联合创始人、创新 Web 开发者 David Lindkvist,主要介绍有关混合型应用搭建的方方面面。文章系国内 [ITOM][1] 管理平台 [OneAPM][2] 编译呈现。 最近,我们...

OneAPM蓝海讯通
2016/05/10
27
0
ReactNative项目实践编码规范

说明 此为无线前端开发团队遵循和约定的开发规范,旨在保持项目代码的整洁、易读、和一致性,更容易被理解和维护。对待规范,要严格遵守;对待风格,要懂得尊重。 要求 在本开发规范中,使用...

芒言
2018/06/05
0
0
[译] React性能优化-虚拟Dom原理浅析

本文译自《Optimizing React: Virtual DOM explained》,作者是Alexey Ivanov和Andy Barnov,来自Evil Martians’ team团队。 译者说:通过一些实际场景和demo,给大家描述React的Virtual D...

YuyingWu
2018/06/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

CSS盒子模型

一、什么叫框模型 页面元素皆为框(盒子) 定义了元素框处理元素内容,内边距,外边距以及边框的计算方式 二、外边距 围绕在元素边框外的空白距离(元素与元素之间的距离) 语法:margin,定...

wytao1995
今天
4
0
Replugin借助“UI进程”来快速释放Dex

public static boolean preload(PluginInfo pi) { if (pi == null) { return false; } // 借助“UI进程”来快速释放Dex(见PluginFastInstallProviderProxy的说明) return PluginFastInsta......

Gemini-Lin
今天
4
0
Hibernate 5 的模块/包(modules/artifacts)

Hibernate 的功能被拆分成一系列的模块/包(modules/artifacts),其目的是为了对依赖进行独立(模块化)。 模块名称 说明 hibernate-core 这个是 Hibernate 的主要(main (core))模块。定义...

honeymoose
今天
4
0
精华帖

第一章 jQuery简介 jQuery是一个JavaScript库 jQuery具备简洁的语法和跨平台的兼容性 简化了JavaScript的操作。 在页面中引入jQuery jQuery是一个JavaScript脚本库,不需要特别的安装,只需要...

流川偑
今天
7
0
语音对话英语翻译在线翻译成中文哪个方法好用

想要进行将中文翻译成英文,或者将英文翻译成中文的操作,其实有一个非常简单的工具就能够帮助完成将语音进行翻译转换的软件。 在应用市场或者百度手机助手等各大应用渠道里面就能够找到一款...

401恶户
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部