ReactJS學習筆記——組件複合及表單的處理

#ReactJS學習筆記——組件複合及表單的處理html

React是一個JavaScript庫文件,使用它的目的在於可以解決構建大的應用和數據的實時變動。該設計使用JSX容許你在構建標籤結構時充分利用JavaScript的強大能力,而沒必要在笨拙的模板語言上浪費時間。node

系統環境:window x86_64react

命令行工具:git-bashgit

React版本:React v0.14.7github

##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)的特性。bash

###1.2 組裝HTML單選框RadioInput 依照從下往上的設計規則,咱們首先須要組裝一個RadioInput組件,這個組件使用了通用的input,,將其精縮成與單選按鈕行爲一致的組件。 ####1.2.1 添加動態屬性 咱們知道input尚未內容是動態的,咱們須要定義一些可以有父元素傳遞給單選框的一些屬性。框架

  • 這個單選框表明什麼值,也是它的顯示內容?(必填)
  • 這個單選框的name是什麼?(必填)
  • 重載它的默認值,也是選擇狀態

####1.2.2 代碼分析 RadioInput.jsdom

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

相關文章
相關標籤/搜索