您的位置:首页 > Web前端 > React

ReactJS学习笔记——组件复合及表单的处理

2016-03-26 22:40 1611 查看
#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(
<div className="radio">
<label htmlFor={this.props.id}>
<input type="radio"
name={this.props.name}
value={this.props.value}
checked={this.props.checked}
onChange={this.handleChanged} />
{this.props.value}
</label>
</div>
);
}
});

module.exports = RadioInput;

代码总共分为5个步骤,这是绘制一个组件的基本流程:

定制单元模块所具有的属性,父元素能够通过动态属性传递数据到子组件,必需由父元素声明的属性加入
isReauired
,如果父元素没有对
isReauired
的属性进行声明,运行时会产生警告。RadioInput中必需的属性包含:name、value、onChanged(函数),具体类型声明参考:https://facebook.github.io/react/docs/reusable-components.html

对于非必要属性在
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(
<div className="form-group-choice">
{this.renderChoices()}
</div>
);
}

});

module.exports = MutileChoice;

代码中需要留意两个地方:(1)map使用;(2)注释代码中存在的问题。

Array.prototype.map()
,map是对array的每一个元素进行遍历,
arr.map(callback[, thisArg])
其中callback参数有三个(可选):currentValue:当前值,index当前元素索引,array当前数组;thisArg参数定义为:填入值为this,默认为window对象。

map参考
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/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(
<div className="root">
<label htmlFor="firstQuestion">你最喜欢的欢乐喜剧人?</label>
<AnswerMutileChoice choices={choices} onComplete={handleComplete}/>
<button>提交</button>
</div>
, 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 (
<form onSubmit={this.submitHandler}>
<input ref="helloTo" type="text" defaultValue="hello world!" />
<br />
<button type="submit">Speak</button>
</form>
);
}
});

ReactDOM.render(<div className="root"><MyForm /></div>, document.body);

无约束组件可以用在基本的无需任何验证或者输入控制的表单中,当期望用户在输入的时候检测输入的变化的需要使用约束组件。

###2.2 约束组件
约束组件的模式与React其他类型的组件的模式一致。表单组件的状态交由React组件的控制,状态值被存储在React组件的state中。在约束组件中,输入框的值是由父组件设置的。我们对2.1中的代码进行改造,改成约束组件:

var MyForm = React.createClass({
// 1.定义默认值
getInitialState:function() {
return {
helloTo:"hello world!!!"
};
},

// 2.处理输入变化
handleChange:fun
3ff0
ction(event) {
this.setState({
helloTo:event.target.value
});
},

submitHandler:function(event) {
event.preventDefault();
alert(this.state.helloTo);
},

// 3.渲染时value值使用state保存
render:function() {
return (
<form onSubmit={this.submitHandler}>
<input type="text" value={this.state.helloTo} onChange={this.handleChange}/>
<br />
<button type="submit">Speak</button>
</form>
);
}
});

ReactDOM.render(<div className="root"><MyForm /></div>, document.body);

显著的变化就是
</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 = (
<label>
<input
type = "radio"
name={this.props.name}
value={child.props.value}
checked={child.props.value == value}
onChange={this.handleChange} />
{child.props.children}
<br/>
</label>
);
children[i] = label;
}.bind(this));

return(
<div>
{
children.map(function (name) {
return <div>{name}</div>
})
}
</div>
);
}
});

通过上面的模块,就可以实现任意几个类型为radio的input组件自定义,在父组件中调用代码为:

render:function() {
return (
<form onSubmit={this.submitHandler}>
<Radio name="my_radio" value={this.state.my_radio} onChange={this.handleChange} >
<option value="A">First option</option>
<option value="B">Second option</option>
<option value="C">Thrid option</option>
</Radio>
<button type="submit">Speak</button>
</form>
);
}


在自定义模块render方法的return中,这里处理的不是很好,增加了两个div标签,暂时没想到好的办法,若您有好的办法,可以给我留言。

###2.6 Focus
控制表单组件的focus可以很好地引导用户按照表单逻辑逐步填写,而且还可以减少用户的操作,增强可用性,增强可用性,
因为React的表单并不总是在浏览器加载时被渲染,所以表单的输入域的自动聚焦操作起来有点不一样。React实现了autoFocus属性,因此在组建第一次挂载时,如果没有其他的表单域聚焦时,React就会把焦点放在这个组件对应的表单域中。如下代码:

<input type="text" name="given_name" autoFocus="true"/>

还有一种方法就是调用DOMNode的focus()方法,手动设置表单域聚焦。

##3 可用性
React虽然可以提供开发者的生产力,但是也有不尽如人意的地方。主要注意以下几点:

把需求传达清楚,无论对于应用程序的哪部分来说,好的沟通都是非常重要的,对表单来说尤其如此。

不断地反馈,尽可能快地为用户提供反馈也很重要。

迅速响应,React拥有非常强大的渲染引擎。他可以非常显著的提升应用的速度。

符合用户的预期,用户对事物如何工作有自己的预期。

可访问,可访问性也是开发者和设计师在创建用户界面时容易忽略的一点。

减少用户的输入,减少用户输入可以大幅提高应用的可用性。

##4 参考

《React 引领未来的用户界面开发框架》

https://facebook.github.io/react/docs/transferring-props.html

https://facebook.github.io/react/docs/reusable-components.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ReactJS 组件 表单