React系列---React(一)初識React
React系列---React(二)組件的prop和state
React系列---React(三)組件的生命週期react
組件是React的基石,全部的React應用程序都是基於組件的。基於組件的應用開發是普遍使用的軟件開發模式,用分而治之的方法,把一個大的應用分解成若干小的組件,每一個組件只關注某個特定功能,可是把組件組合起來,就能構成一個功能龐大的應用。npm
React組件的數據分爲兩種,prop和state,不管prop或者state的改變,均可能引起組件的從新渲染。prop是組件對外接口,state是組件內部狀態。segmentfault
用create-react-app工具,初始化一個React項目:數組
npm create-react-app react-component-demo
建立一個能夠計算點擊數的組件:
/src/ClickCounter.js:瀏覽器
import React from 'react'; class ClickCounter extends React.Component { constructor(props) { super(props); this.onClickButton = this.onClickButton.bind(this); this.state = {count: 0}; } onClickButton() { this.setState({count: this.state.count + 1}); } render() { return ( <div> <button onClick={this.onClickButton}>Click Me</button> <div> Click Count: {this.state.count} </div> </div> ); }; } export default ClickCounter;
修改/src/index.js:babel
import React from 'react'; import ReactDOM from 'react-dom'; import ClickCounter from './ClickCounter'; ReactDOM.render(<ClickCounter />, document.getElementById('root'));
運行React項目app
npm run start
點擊按鈕,數字會隨之增長。恭喜你,已經構建了一個有交互的組件!less
咱們還能夠在React組件中定義樣式。修改ClickCounter組件的render函數:dom
render() { const counterStyle = { margin: '16px' }; return ( <div style={counterStyle}> <button onClick={this.onClickButton}>Click Me</button> <div> Click Count: <span id="clickCount">{this.state.count}</span> </div> </div> ); };
React組件經過定義本身可以接受的prop就定義了本身對外公共接口。外部世界經過prop和組件對話。函數
從外部世界看prop的使用:
<SampleButton id="sample" borderWidth={2} onClick={onButtonClick} style={{color: "red"}} />
上面的例子使用了名爲SampleButton的組件實例。React組件的prop所能支持的類型除了字符串,還能夠是任何一種JavaScript語言支持的數據類型。當prop的類型不是字符串時,再JSX中必須用花括號{}把值包裹,因此style的值有兩層花括號,外層表明是JSX的語法,內層表明這是個對象常量。
React組件要反饋數據給外部世界,也是用prop,由於prop類型也能夠是函數,函數類型的prop等於讓父組件交給子組件一個回調函數,子組件在恰當的時機調用函數的prop,就能夠把信息傳遞給外部世界。
爲了演示,咱們構造一個應用包含兩種組件,ControlPanel父組件,而後若干個Counter子組件。對於Counter組件,父組件ControlPanel就是外部世界:
class ControlPanel extends React.Component { render() { return ( <div> <Counter caption="First" initValue={0} /> <Counter caption="Second" initValue={10} /> <Counter caption="Third" initValue={20} /> </div> ); } }
React要求render只能返回一個元素,因此咱們用div包裹了3個子組件。
每一個Counter組件使用了caption和initValue兩個prop。ControlPanel經過caption的prop傳遞給Counter組件實例說明文字,經過initValue的prop傳遞給Count組件一個初始的計數值。
看下Counter組件內部是如何接收prop的:
class Counter extends React.Component { constructor(props) { super(props); this.onClickIncrementButton = this.onClickIncrementButton.bind(this); this.onClickDecrementButton = this.onClickDecrementButton.bind(this); this.state = { count: props.initValue || 0 }; } }
若是組件須要定義本身的構造函數,構造函數第一行必定要經過super調用父類React.Component的構造函數。給this.props賦值也是React.Component構造函數的工做之一。
在Counter的構造函數中,還給兩個成員函數綁定了當前this的執行環境,由於ES6方式建立的組件並不自動給咱們綁定this到當前實例對象。
在其餘函數中則能夠經過this.props訪問傳入的值,看一下render函數:
render() { const {caption} = this.props; // ES6的解構賦值 return ( <div> <button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button> <button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button> <span>{caption} count: {this.state.count}</span> </div> ); };
在ES6方法定義的組件中,能夠經過增長類的propTypes屬性來定義prop規格。在運行和靜態代碼檢查時,均可以根據propTypes判斷外部世界是否正確地使用了組件的屬性。
增長Counter組件的propTypes定義:
Counter.propTypes = { caption: PropTypes.string.isRequired, initValue: PropTypes.number };
開發過程當中,定義propTypes代碼能夠避免犯錯,可是在發佈產品時,能夠用babel-react-optimize工具自動去除propTypes,這樣部署到產品環境的代碼就會更優。
驅動組件渲染的除了prop,還有state,state表明組件內部狀態。因爲React組件禁止修改傳入的prop,因此當組件須要記錄自身的數據變化時,就要使用state。
在Counter組件中,初始計數能夠經過initValue這個prop指定。當用戶點擊「+」和「-」改變計數時,就要Counter組件本身經過state來存儲了。
一般在構造函數的結尾處初始化state,就如上面的Counter:
constructor(props) { ... this.state = { count: props.initValue || 0 }; }
因爲在PropType聲明中沒有用isRequired,咱們須要在代碼中判斷給定的prop值是否存在,不存在則給一個默認值。咱們能夠利用React的defaultProps功能,避免判斷邏輯這種充斥在構造函數之中,讓代碼更優。
給Counter組件添加defaultProps代碼:
Counter.defaultProps = { initValue: 0 };
構造函數就能夠簡化了:
constructor(props) { ... this.state = { count: props.initValue }; }
經過給button的onClick屬性掛載點擊事件處理函數,咱們能夠改變組件的state,以點擊「+」按鈕的響應函數爲例:
onClickIncrementButton() { this.setState({count: this.state.count + 1}); }
經過this.state能夠讀取到組件的當前state。注意的是,改變state必須使用this.setState函數,而不能直接修改this.state。若是你違反這個操做,瀏覽器Console會告警。
直接修改this.state的值,只是野蠻的修改了state,卻沒有驅動組件從新渲染,新的值固然也不會反應在界面上。而this.setState()函數所作的事情,就是改變this.state的值後再驅動組件從新渲染。
沒有內部state,不須要組件生命週期函數。能夠用純函數的形式來表達。它作的事情只是根據輸入來展現組件,沒有其餘反作用。能夠把這種組件稱爲無狀態函數式組件(stateless functional component)。
import React from 'react'; // 用一個純函數表示 const Hobby = (props) => <li>{props.hobby}</li>; export default Hobby;
建立儘可能多的無狀態組件,這些組件惟一關心的就是渲染數據。而在最外層,應該有一個包含state的父級別組件,用於處理各類事件、交流邏輯、修改state。對應的子組件要關心的只是傳入的屬性而已。
state應該包含組件的事件回調函數可能引起UI更新的這類數據。在實際的項目中,應該是輕量化的JSON數據,儘可能把數據的表現設計到最小,更多的數據能夠在render中經過各類計算獲得。
大多數狀況下,不須要操做DOM去更新UI,應使用setState。可是有些狀況確實須要訪問一些DOM(如表單的值),那麼可採用refs方式來得到DOM節點。只須要加個ref屬性,而後經過this.refs.name來得到對應的DOM結構。
示例Profile組件:
render() { return ( <div> ... <input type="text" ref="hobby" /> <button onClick={this.addHobbyCallback}>添加愛好</button> </div> ) }
在button上添加事件,取得input的值,添加到state的值裏面:
addHobbyCallback() { // 用this.refs.name來取得DOM節點 let hobbyInput = this.refs.hobby; let val = hobbyInput.value; if (val) { let hobbies = this.state.hobbies; // 添加值到數組 hobbies = [...hobbies, val]; // 更新state, 刷新UI this.setState({ hobbies }, () => { hobbyInput.value = ''; }); } }
React系列---React(一)初識React
React系列---React(二)組件的prop和state
React系列---React(三)組件的生命週期