赵走x博客
网站访问量:151513
首页
书籍
软件
工具
古诗词
搜索
登录
49、Flux:理念、回顾Whinepad
48、lint、Flow、测试与复验:测试
47、lint、Flow、测试与复验:Flow
46、lint、Flow、测试与复验:ESLint
45、lint、Flow、测试与复验:package.json
44、构建实例应用:<Whinepad>
43、构建实例应用:应用配置
43、构建实例应用:<Excel>:改进的新版本
42、构建实例应用:组件:对话框
41、构建实例应用:组件:Actions
39、构建实例应用:表单:Form
38、构建实例应用:表单:<FormInput>“工厂组件”
37、构建实例应用:表单:Rating组件
36、构建实例应用:表单:Suggest
35、构建实例应用:Button组件
34、构建实例应用:组件
33、构建实例应用:Whinepad v.0.0.1
32、发布
31、开始构建
30、安装必备工具
29、为应用开发做准备:一个模板应用
28、JSX 和表单
27、JSX 和HTML 的区别
26、在JSX 中返回多个节点
25、展开属性
24、HTML 实体
23、JSX入门
22、Excel:一个出色的表格组件:下载表格数据
21、Excel:一个出色的表格组件:即时回放
20、Excel:一个出色的表格组件:搜索
19、Excel:一个出色的表格组件:编辑数据
18、Excel:一个出色的表格组件:排序
17、Excel:一个出色的表格组件
16、 PureRenderMixin
15、 性能优化:避免组件更新
14、 生命周期示例:使用子组件
13、组件生命周期示例:使用mixin
12、组件:生命周期方法
11、中途改变属性
10、从外部访问组件
9、在初始化state 时使用props:一种反模式
8、 props 与state
7、关于DOM 事件的说明
6、组件:带状态的文本框组件
5、组件的state
4、组件的propTypes
3、组件的属性
2、组件的基础
1、Hello World
50、Flux:Store
43、构建实例应用:<Excel>:改进的新版本
资源编号:76097
书籍
React快速上手开发
热度:91
我们在第3 章中创建的Excel 组件承载了太多功能。因此我们需要创建一个改进的新版本,以提高组件的可重用性。我们决定删减搜索功能(把功能移到顶层的<Whinepad> 中)与下载功能(如果你喜欢的话也可以保留下来)。
我们在第3 章中创建的Excel 组件承载了太多功能。因此我们需要创建一个改进的新版本,以提高组件的可重用性。我们决定删减搜索功能(把功能移到顶层的`
` 中)与下载功能(如果你喜欢的话也可以保留下来)。这个组件的全部功能应该与CRUD 中的RUD部分相关(如图6-10 所示)。由于这是一个可编辑的表格,我们还需要新增onDataChange属性,以便在表格中的数据内容发生改变时通知父组件Whinepad。  图6-10:Excel 组件 Whinepad 组件则负责搜索功能,CRUD 中的C 部分(创建新条目)以及使用localStorage进行数据持久化存储。(在实际应用开发中,你也可能需要把数据存储在服务器上。) 这两个组件都需要使用schema 对象进行数据类型配置。 以下是Excel 组件的完整实现(和第3 章的版本类似,其中某些功能稍有不同): ``` import Actions from './Actions'; import Dialog from './Dialog'; import Form from './Form'; import FormInput from './FormInput'; import Rating from './Rating'; import React, {Component} from 'react'; // 从 React v15.5 开始 ,React.PropTypes 助手函数已被弃用,我们建议使用 prop-types 库 来定义contextTypes。 import PropTypes from 'prop-types' import classNames from 'classnames'; class Excel extends Component { constructor(props) { super(props); this.state = { data: this.props.initialData, sortby: null, // schema.id descending: false, edit: null, // [row index, schema.id], dialog: null, // {type, idx} }; } componentWillReceiveProps(nextProps) { this.setState({data: nextProps.initialData}); } _fireDataChange(data) { this.props.onDataChange(data); } _sort(key) { let data = Array.from(this.state.data); const descending = this.state.sortby === key && !this.state.descending; data.sort(function(a, b) { return descending ? (a[column] < b[column] ? 1 : -1) : (a[column] > b[column] ? 1 : -1); }); this.setState({ data: data, sortby: key, descending: descending, }); this._fireDataChange(data); } _showEditor(e) { this.setState({edit: { row: parseInt(e.target.dataset.row, 10), key: e.target.dataset.key, }}); } _save(e) { e.preventDefault(); const value = this.refs.input.getValue(); let data = Array.from(this.state.data); data[this.state.edit.row][this.state.edit.key] = value; this.setState({ edit: null, data: data, }); this._fireDataChange(data); } _actionClick(rowidx, action) { this.setState({dialog: {type: action, idx: rowidx}}); } _deleteConfirmationClick(action) { if (action === 'dismiss') { this._closeDialog(); return; } let data = Array.from(this.state.data); data.splice(this.state.dialog.idx, 1); this.setState({ dialog: null, data: data, }); this._fireDataChange(data); } _closeDialog() { this.setState({dialog: null}); } _saveDataDialog(action) { if (action === 'dismiss') { this._closeDialog(); return; } let data = Array.from(this.state.data); data[this.state.dialog.idx] = this.refs.form.getData(); this.setState({ dialog: null, data: data, }); this._fireDataChange(data); } render() { return (
{this._renderTable()} {this._renderDialog()}
); } _renderDialog() { if (!this.state.dialog) { return null; } switch (this.state.dialog.type) { case 'delete': return this._renderDeleteDialog(); case 'info': return this._renderFormDialog(true); case 'edit': return this._renderFormDialog(); default: throw Error(`Unexpected dialog type ${this.state.dialog.type}`); } } _renderDeleteDialog() { const first = this.state.data[this.state.dialog.idx]; const nameguess = first[Object.keys(first)[0]]; return (
{`Are you sure you want to delete "${nameguess}"?`}
); } _renderFormDialog(readonly) { return (
); } _renderTable() { return (
{ this.props.schema.map(item => { if (!item.show) { return null; } let title = item.label; if (this.state.sortby === item.id) { title += this.state.descending ? ' \u2191' : ' \u2193'; } return (
{title}
); }, this) }
Actions
{this.state.data.map((row, rowidx) => { return (
{ Object.keys(row).map((cell, idx) => { const schema = this.props.schema[idx]; if (!schema || !schema.show) { return null; } const isRating = schema.type === 'rating'; const edit = this.state.edit; let content = row[cell]; if (!isRating && edit && edit.row === rowidx && edit.key === schema.id) { content = (
); } else if (isRating) { content =
; } return (
{content}
); }, this)}
); }, this)}
); } } Excel.propTypes = { schema: PropTypes.arrayOf( PropTypes.object ), initialData: PropTypes.arrayOf( PropTypes.object ), onDataChange: PropTypes.func, }; export default Excel ``` 有一些细节需要详细讨论: ``` render() { return (
{this._renderTable()} {this._renderDialog()}
); } ``` 这个组件会渲染一个表格与一个对话框(是否渲染对话框视情况而定)。对话框的情况多种多样,包括弹出确认信息(sure you want to delete?)、编辑表单、在表单只读时显示条目信息。对话框在默认情况下不会显示,可以通过设置this.state 的dialog 属性在需要时渲染对话框,状态变化将会导致组件重新渲染。 当用户点击`
`组件中的某个按钮时,你需要设置dialog 属性: ``` _actionClick(rowidx, action) { this.setState({dialog: {type: action, idx: rowidx}}); } ``` 当表格中的数据发生变化时(即使用`this.setState({data: /**/}`) 的时候),你需要触发一个监听改变的事件,通知父组件更新持久化存储中的内容: ``` _fireDataChange(data) { this.props.onDataChange(data); } ``` 反向通信(从父组件Whinepad 到子组件Excel 之间的通信)会在父组件改变initialData属性时发生。Excel 组件可以通过以下方法响应数据变化: ``` componentWillReceiveProps(nextProps) { this.setState({data: nextProps.initialData}); } ``` 如何创建数据条目(如图6-11 所示)或一个数据视图(如图6-12 所示)?你需要打开一个包含Form 组件的Dialog 对话框。表单中的数据配置来源于schema,而数据条目的内容则来源于this.state.data: ``` _renderFormDialog(readonly) { return (
); } ```  图6-11:编辑数据的对话框(CRUD 中的U)  图6-12:数据视图对话框(CRUD 中的R) 当用户完成编辑后,你需要更新状态,并把变化告知订阅者: ``` _saveDataDialog(action) { if (action === 'dismiss') { this._closeDialog(); return; } let data = Array.from(this.state.data); data[this.state.dialog.idx] = this.refs.form.getData(); this.setState({ dialog: null, data: data, }); this._fireDataChange(data); } ``` 至于ES 的新语法,这里除了模板字符串的广泛使用,并没有涉及太多: ``` // 旧语法 "Are you sure you want to delete " + nameguess + "?" // 新语法 {`Are you sure you want to delete "${nameguess}"?`} ``` 此外还要注意类名中也使用了模板字符串,因为这个应用允许你在schema 中通过添加ID的方式自定义数据表格: ``` // 旧语法
// 新语法
``` 模板字符串最令人不可思议的用法,是可以通过中括号[] 用作对象中的属性名。虽然这 与React 本身没有关系,但当你看到如下用法时,可能还是会感到有些奇怪: ``` { [`schema-${schema.id}`]: true, 'ExcelEditable': !isRating, 'ExcelDataLeft': schema.align === 'left', 'ExcelDataRight': schema.align === 'right', 'ExcelDataCenter': schema.align !== 'left' && schema.align !== 'right', } ```