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

#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. 实现条件:(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个步骤,这是绘制一个组件的基本流程:

  1. 定制单元模块所具有的属性,父元素能够通过动态属性传递数据到子组件,必需由父元素声明的属性加入isReauired,如果父元素没有对isReauired的属性进行声明,运行时会产生警告。RadioInput中必需的属性包含:name、value、onChanged(函数),具体类型声明参考:https://facebook.github.io/react/docs/reusable-components.html
  2. 对于非必要属性在getDefaultProps进行初始化。
  3. 追踪状态,在getInitialState中声明组件内的变量,记录者组件的数据变更,可以通过setState方法修改其内容。
  4. 这里不得不提一下onChange的处理函数handleChanged:function(e)(函数名可以自定义),在handleChanged:function(e)可以看到对属性函数onChanged方法this.props.onChanged(this.props.value);的调用,这里可以将组件的事件传递至父组件,由父组件相应当前子组件的变化。桥接了子组件和父组件之间的关系。
  5. 绘制当前组件,input的type为radio。

###1.3 父组件对子组件的整合

####1.3.1 设计思考 父组件期望组合一个单选组合,这一层的主要作用是渲染一列选项让用户从中选择。这里还是按照之前设计RadioInput的设计逻辑:

  1. 确定动态属性,当前单选组合选择内容:value,单选组合每个单选卡的内容:choices和点击完成的事件:onComplete
  2. 对于非必要属性在getDefaultProps进行初始化,这里不需要。
  3. 追踪状态,在getInitialState中声明组件内的变量,这里包含一个id和一个value。具体使用参考代码。
  4. 响应事件,发生事件时,调用setState并把事件对外传递通过this.props.onComplete
  5. 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)注释代码中存在的问题。

  1. 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

  1. 留意代码中注释掉的部分,直接构建子组件实例,没注释掉的部分使用工厂创建一个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:function(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中。因为数据流有了清晰的定义。

  1. getInitialState设置defaultValue值。
  2. </input>,其值onChange时,change处理器被调用。
  3. change通过处理函数更新state的值。
  4. 在重新渲染时更新</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虽然可以提供开发者的生产力,但是也有不尽如人意的地方。主要注意以下几点:

  1. 把需求传达清楚,无论对于应用程序的哪部分来说,好的沟通都是非常重要的,对表单来说尤其如此。
  2. 不断地反馈,尽可能快地为用户提供反馈也很重要。
  3. 迅速响应,React拥有非常强大的渲染引擎。他可以非常显著的提升应用的速度。
  4. 符合用户的预期,用户对事物如何工作有自己的预期。
  5. 可访问,可访问性也是开发者和设计师在创建用户界面时容易忽略的一点。
  6. 减少用户的输入,减少用户输入可以大幅提高应用的可用性。

##4 参考

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

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

https://facebook.github.io/react/docs/reusable-components.html

转载于:https://my.oschina.net/feiyangxiaomi/blog/647985

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值