#ReactJS学习笔记——组件复合及表单的处理
React是一个JavaScript库文件,使用它的目的在于能够解决构建大的应用和数据的实时变更。该设计使用JSX允许你在构建标签结构时充分利用JavaScript的强大能力,而不必在笨拙的模板语言上浪费时间。
系统环境:window x86_64
命令行工具:git-bash
React版本:React v0.14.7
##1 组件的复合 在传统的HTML中,元素是构成页面的基础单元。但是在React中,构建页面的基础单元是React组件。你可以把React组件理解成为混入了JavaScript表达能力的HTML元素。实际上写React代码就是构建组件,就像编写HTML文档时使用元素一样。 本质上,一个组件就是一个JavaScript函数,它接受属性(props)和(state)作为参数,并输出渲染好的HTML。组件一般被用来呈现和表达应用的某部分数据,因此你可以把React组件理解为HTML元素的拓展。 React+JSX是强大而富有表现力的工具,允许我们使用类似HTML的语法创建自定义元素,比起单纯的HTML,他们还能够控制僧命周期中的行为。这些都在从React.createClass
方法开始的。相较于继承ES6已经开始支持,实现多个小巧、简单的组件和数据对象,构造成大而复杂的组件。
###1.1 组件复合的例子
- 需求:做一个渲染选择题的组件
- 实现条件:(1)接收一组选项作为输入;(2)把选项渲染给用户;(3)只允许用户选择一个选项;
HTML提供了一些基本的元素——单选类型是输入框和表单组(input group),可以在这里使用。组件的层级从上往下看是这样的: MultipleChoice - RadioInput - Input (type="radio")
从先往后的顺序。选择题组件MultipleChoice"有一个"单选框RadioInput,单选框RadioInput”有一个“输入框元素Input。这里组合模式(composition pattern)的特性。
###1.2 组装HTML单选框RadioInput 依照从下往上的设计规则,我们首先需要组装一个RadioInput组件,这个组件使用了通用的input,,将其精缩成与单选按钮行为一致的组件。 ####1.2.1 添加动态属性 我们知道input还没有内容是动态的,我们需要定义一些能够有父元素传递给单选框的一些属性。
- 这个单选框代表什么值,也是它的显示内容?(必填)
- 这个单选框的name是什么?(必填)
- 重载它的默认值,也是选择状态
####1.2.2 代码分析 RadioInput.js
var React = require('react');var uniqueId = require('lodash-node/modern/utility/uniqueId');var RadioInput = React.createClass({ // 1.添加动态属性 propTypes: { name: React.PropTypes.string.isRequired, value:React.PropTypes.string.isRequired, checked:React.PropTypes.bool, onChanged:React.PropTypes.func.isRequired }, // 2.为非必要属性定义其默认值 getDefaultProps: function() { return { checked: false } }, // 3.追踪状态,组件需要记录随时间而变化的数据 getInitialState: function() { var name = this.props.name ? this.props.name:uniqueId('radio-'); return { checked: !!this.props.checked, name: name } }, // 4.追踪当前组件的状态变更,并通过this.props.onChanged通知给父组件 handleChanged:function(e) { var checked = e.target.checked; this.setState({checked:checked}); if(checked) { this.props.onChanged(this.props.value); } }, render:function() { return(); }});module.exports = RadioInput;
代码总共分为5个步骤,这是绘制一个组件的基本流程:
- 定制单元模块所具有的属性,父元素能够通过动态属性传递数据到子组件,必需由父元素声明的属性加入
isReauired
,如果父元素没有对isReauired
的属性进行声明,运行时会产生警告。RadioInput中必需的属性包含:name、value、onChanged(函数),具体类型声明参考: - 对于非必要属性在
getDefaultProps
进行初始化。 - 追踪状态,在
getInitialState
中声明组件内的变量,记录者组件的数据变更,可以通过setState方法修改其内容。 - 这里不得不提一下onChange的处理函数
handleChanged:function(e)
(函数名可以自定义),在handleChanged:function(e)
可以看到对属性函数onChanged方法this.props.onChanged(this.props.value);
的调用,这里可以将组件的事件传递至父组件,由父组件相应当前子组件的变化。桥接了子组件和父组件之间的关系。 - 绘制当前组件,input的type为radio。
###1.3 父组件对子组件的整合
####1.3.1 设计思考 父组件期望组合一个单选组合,这一层的主要作用是渲染一列选项让用户从中选择。这里还是按照之前设计RadioInput的设计逻辑:
- 确定动态属性,当前单选组合选择内容:value,单选组合每个单选卡的内容:choices和点击完成的事件:onComplete
- 对于非必要属性在
getDefaultProps
进行初始化,这里不需要。 - 追踪状态,在
getInitialState
中声明组件内的变量,这里包含一个id和一个value。具体使用参考代码。 - 响应事件,发生事件时,调用
setState
并把事件对外传递通过this.props.onComplete
- render样式
####1.3.2 代码分析 MutileChoice.js
var React = require('react');var RadioInput = require('./RadioInput');var uniqueId = require('lodash-node/modern/utility/uniqueId');// 父组件var MutileChoice = React.createClass({ // 1.添加动态属性 propTypes: { value: React.PropTypes.string, choices: React.PropTypes.array.isRequired, onComplete: React.PropTypes.func.isRequired }, // 3.追踪状态,组件需要记录随时间而变化的数据 getInitialState: function() { return { id: uniqueId('multiple-choice-'), value: this.props.value } }, // 4.响应事件 handleChanged: function(value) { this.setState({value:value}); this.props.onComplete(value); }, renderChoices: function() { var SquareItemFactory = React.createFactory(RadioInput); return this.props.choices.map(function(choice, i) { // return AnswerRadioInput({ // id:"choice-"+i, // name:this.state.id, // label:choice, // value:choice, // checked:this.state.value === choice, // onChanged: this.handleChanged // }); return SquareItemFactory({ key:"choice-"+i, name:this.state.id, value:choice, checked:this.state.value === choice, onChanged: this.handleChanged }); }.bind(this)); }, render: function() { return({this.renderChoices()}); }});module.exports = MutileChoice;
代码中需要留意两个地方:(1)map使用;(2)注释代码中存在的问题。
Array.prototype.map()
,map是对array的每一个元素进行遍历,arr.map(callback[, thisArg])
其中callback参数有三个(可选):currentValue:当前值,index当前元素索引,array当前数组;thisArg参数定义为:填入值为this,默认为window对象。
map参考
- 留意代码中注释掉的部分,直接构建子组件实例,没注释掉的部分使用工厂创建一个RadioInput实例,然后填入RadioInput实例的内容。参考如下代码:
renderChoices: function() { var SquareItemFactory = React.createFactory(RadioInput); return this.props.choices.map(function(choice, i) { // return RadioInput({ // id:"choice-"+i, // name:this.state.id, // label:choice, // value:choice, // checked:this.state.value === choice, // onChanged: this.handleChanged // }); return SquareItemFactory({ key:"choice-"+i, name:this.state.id, value:choice, checked:this.state.value === choice, onChanged: this.handleChanged }); }.bind(this));},
如果代码中,直接构建实例,在React v0.14.7环境下,会报出如下: Uncaught TypeError: Cannot read property '__reactAutoBindMap' of undefined
###1.4 使用已经封装好的组件 子组件和父组件都已经封装完毕,如何使用父组件来实现单选功能?这里只需要在实现代码中使用MutileChoice标签,同时定义相应的标签属性,代码如下所示:
var React = require('react');var ReactDOM = require("react-dom");var AnswerMutileChoice = require('./MutileChoice');var choices = [ "辽宁民间艺术团队", "开心麻花","贾玲团队","曹云金团队"];function handleComplete(value) { console.log("handleComplete " + value);}ReactDOM.render(, document.body);
效果参考下图所示:
##2 表单使用 表单是应用必不可少的一部分,只要需要用户输入,哪怕是最简单的输入,都离不开表单。一直以来,单页应用的表单都很难处理好,因为表单充斥着用户变化莫测的状态,要管理好这些状态是很费神的,也很容易出现bug。React可以帮助你管理应用中的状态,自然也包括表单在内。 现在,你应该知道React组件的核心离你那就是可预知性和可测试性。给定同样的props和state,任何React组件都会渲染出一样的结果。表单也不例外。 在React中,表单有两种类型:约束组件和无约束组件。
###2.1 无约束组件 无约束表单的构造与React中大多数组件相比是反模式。在HTML中,表单组件与React组件行为并不一致。给定HTML的<input/>
一个值,这个<input/>
值仍是可以改变的。这正是无约束组件名称的由来,因为表单组件的值是不受React组件控制的。如果想访问它的值,需要给<input/>
添加一个ref属性,以访问DOM节点的值。 ref是一个不属于DOM属性的特殊属性,用来标记DOM节点,可以通过this上下文访问这个节点。为了便于访问,组件的所有的ref都添加到了this.refs上。 下面我们在表单中添加一个<input/>
,并在表单提交时访问它的值。
var React = require('react');var ReactDOM = require("react-dom");var MyForm = React.createClass({ submitHandler:function(event) { event.preventDefault(); // 通过ref访问输入框 var helloTo = this.refs.helloTo.getDOMNode().value; alert(helloTo); }, render:function() { return (); }});ReactDOM.render(
无约束组件可以用在基本的无需任何验证或者输入控制的表单中,当期望用户在输入的时候检测输入的变化的需要使用约束组件。
###2.2 约束组件 约束组件的模式与React其他类型的组件的模式一致。表单组件的状态交由React组件的控制,状态值被存储在React组件的state中。在约束组件中,输入框的值是由父组件设置的。我们对2.1中的代码进行改造,改成约束组件:
var MyForm = React.createClass({ // 1.定义默认值 getInitialState:function() { return { helloTo:"hello world!!!" }; }, // 2.处理输入变化 handleChange:function(event) { this.setState({ helloTo:event.target.value }); }, submitHandler:function(event) { event.preventDefault(); alert(this.state.helloTo); }, // 3.渲染时value值使用state保存 render:function() { return (); }});ReactDOM.render(
显著的变化就是</input>
的值存储在父组件的state中。因为数据流有了清晰的定义。
- getInitialState设置defaultValue值。
</input>
,其值onChange时,change处理器被调用。- change通过处理函数更新state的值。
- 在重新渲染时更新
</input>
的值。
相比于无约束组件相比,代码量增加了不少,但是现在可以控制数据流,在用户输入数据的时候更新state。譬如想在用户输入的时候将字符都转成大写。
handleChange:function(event) { this.setState({ helloTo:event.target.value.toUpperCase() });},
这样我们可以限制可输入的字符集,或者限制用户想邮件地址输入框输入不合法的字符。 你还可以在用户输入数据时,把他们用在其他的组件上。例如:
- 显示一个有长度限制的输入框还可以输入多少个字符。
- 显示输入的HEX值所代表的颜色。
- 显示可自动匹配下拉列表的可选项。
- 使用输入框的值更新其他UI元素。
###2.3 表单元素的name属性 在React中,name属性对于表单元素来说并没有那么重要,因为约束表单组件已经把值存储到了state中,并且表单提交事件也会被拦截。在获取表单值的时候,name属性并不是必需的。对于非约束组件的表单来说,也可以使用refs来直接访问表单元素。 即便如此,name仍然是表单组件中非常重要的一部分。
- name属性可以让第三方表单序列化类库在React中正常工作。
- 对于仍然使用传统提交方式的表单来说,name属性是必需的。
- 在用户的浏览器中,name被用在自定填写常用信息中,比如用户地址等。
- 对于非约束单选框组件来说,name是由必要的,他可以作为这些组件分组的依据,确保在同一时刻,同一个表单中用于同样name的单选框只有一个可以被选中。如果不使用name属性,这一行为可以使用约束的单选框实现。
###2.4 多个表单元素与change处理器 在使用约束的表单组件时,没有人愿意重复地为每一个组件编写change处理器,还好有几种方式可以在React中重用一个事件处理器。 示例一:通过.bind
传递其他参数。
onChange={this.handleChange.bind(this, 'given_name')}
示例二:使用DOMNode的name属性来判断需要更新哪个组件的状态 组件name="given_name" 提供state的given_name,然后通过如下代码匹配:
handleChange: function(event) { var newState = {}; newState[event.target.name] = event.target.value; this.setState(newState);},
示例三:React还在addon中提供了一个mixin,React.addons.LinkedStateMixin通过另一种方式解决同样的问题。
###2.5 自定义表单组件 自定义组件是一种极好方式,可以在项目中复用共有的功能。同时,也不失为一种将交互界面提升为更加复杂的表单组件(比如复选框组件或单选框组件)的好方法。 当编写自定义组件时,接口应当与其他表单组件保持一致。这可以帮助用户理解代码,明白如何使用自定义组件,且无须深入到组件的实现细节里。 我们来创建一个自定义的单选框组件,其接口与React的select组件保持一致。我们不打算实现多选功能,因为单选框组件本来就不支持多选。
var Radio = React.createClass({ // 初始化属性 propTypes:{ onChange: React.PropTypes.func }, // 初始化state getInitialState:function() { return { value:this.props.defaultValue }; }, // 事件处理 handleChange:function(event) { if(this.props.onChange) { this.props.onChange(event); } this.setState({ value:event.target.value }); }, render:function() { var children = []; var value = this.props.value || this.state.value; React.Children.forEach(this.props.children, function(child, i) { console.log("children " + child.props.value +" " +child.props.children); var label = ( ); children[i] = label; }.bind(this)); return({ children.map(function (name) { return); }});{name}}) }
通过上面的模块,就可以实现任意几个类型为radio的input组件自定义,在父组件中调用代码为:
render:function() { return ();}
在自定义模块render方法的return中,这里处理的不是很好,增加了两个div标签,暂时没想到好的办法,若您有好的办法,可以给我留言。
###2.6 Focus 控制表单组件的focus可以很好地引导用户按照表单逻辑逐步填写,而且还可以减少用户的操作,增强可用性,增强可用性, 因为React的表单并不总是在浏览器加载时被渲染,所以表单的输入域的自动聚焦操作起来有点不一样。React实现了autoFocus属性,因此在组建第一次挂载时,如果没有其他的表单域聚焦时,React就会把焦点放在这个组件对应的表单域中。如下代码:
还有一种方法就是调用DOMNode的focus()方法,手动设置表单域聚焦。
##3 可用性 React虽然可以提供开发者的生产力,但是也有不尽如人意的地方。主要注意以下几点:
- 把需求传达清楚,无论对于应用程序的哪部分来说,好的沟通都是非常重要的,对表单来说尤其如此。
- 不断地反馈,尽可能快地为用户提供反馈也很重要。
- 迅速响应,React拥有非常强大的渲染引擎。他可以非常显著的提升应用的速度。
- 符合用户的预期,用户对事物如何工作有自己的预期。
- 可访问,可访问性也是开发者和设计师在创建用户界面时容易忽略的一点。
- 减少用户的输入,减少用户输入可以大幅提高应用的可用性。
##4 参考
《React 引领未来的用户界面开发框架》