本文采用 es6 語法,徹底參考 https://reactjs.org/docs/
本文徹底參考 React 官方 Quick Start 部分,除了最後的 thinking-in-react 小節
首先你須要點擊安裝 nodejs(npm)。而後執行:javascript
npm install -g create-react-app
若是上述命令執行失敗能夠運行如下命令:html
npm install -g create-react-app --registry=https://registry.npm.taobao.org
而後創建一個 react 並運行:java
create-react-app myApp cd myApp npm start
這樣你就簡單的完成了一個 react app 創建,其目錄結構以下( 圖中不包括 node_modules 目錄,下同 ):node
咱們刪除一些沒必要要的東西,而後修改目錄結構以下(不能刪 node_modules 目錄,若是刪了就在項目目錄下運行 npm i
就行了):react
其中 components 是個目錄。程序員
修改 index.js 以下:es6
import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render( <h1> hello world! </h1>, document.getElementById('root') );
而後命令行運行:npm
npm start
你就能夠看到熟悉的 'hello world' 了數組
JSX 是 react 中容許 js 和 html 混寫的語法格式,須要依賴 babel 編譯。這裏我就只研究它的語法:瀏覽器
const element = <h1>Hello, world!</h1>;
能夠經過花括號在其中插入表達式:
function formatName(user){ return user.firstName + ' ' + user.lastName; } const user = { firstName: 'Harper', lastName: 'Perez' }; const element = ( <h1> Hello, {formatName(user)}! </h1> ); ReactDOM.render( element, document.getElementById('root') );
能夠將 HTML 語句寫爲多行以增長可讀性,用小括號括起來能夠防止自動插入分號致使的錯誤。
JSX 也是個表達式,因此能夠用在 for 和 if 中:
function getGreeting(user){ if (user){ return <h1>Hello, {formatName(user)}!</h1>; } return <h1>Hello, Stranger.</h1>; }
咱們能夠正常使用引號給 HTML 標籤添加屬性,也可使用 js 表達式
const element = <div tabIndex="0"></div>; const element = <img src={user.avatarUrl} />; //注意空標籤以 /> 結尾,像 XML 同樣
注意 html 屬性名請使用小駝峯(camelCase)寫法
React 會在渲染以前 escape 全部在 JSX 嵌入的值,能夠有效的防止 XSS 攻擊。
babel 會編譯 JSX 成 React.createElement() 的參數調用:
const element = ( <h1 className="greeting"> Hello, world! </h1> ); // 編譯爲如下形式 const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' );
而 React.createElement() 會生成這樣一個對象(React 元素):
const element = { type: 'h1', props: { className: 'greeting', children: 'Hello, world' } };
在 ./public/index.html
中有一個 id 爲 root 的 div。咱們將這個 div 做爲 react 渲染的容器。
回看 hello world 程序,經過 ReactDOM.render() 方法很輕鬆的把內容渲染到了目標容器上:
ReactDOM.render( <h1> hello world! </h1>, document.getElementById('root') );
固然也能夠這樣寫:
let content = <h1> hello world! </h1>; ReactDOM.render( content, document.getElementById('root') );
下面咱們寫一個複雜的,這是個實時更新的時鐘,經過 setInerval 每隔 1s 調用 ReactDOM.render:
function Tick(){ const element = ( <div> <h1>Hello, world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element, document.getElementById('root') ); } setInterval(Tick, 1000);
重寫上面時鐘組件的代碼以下,使其組件化程度更高:
function Clock(props){ return ( <div> <h1>Hello, world!</h1> <h2>It is {props.date.toLocaleTimeString()}.</h2> </div> ); } function Tick(){ ReactDOM.render( //這個地方不得不傳入一個參數, 但理論上獲取一個時鐘直接獲取就能夠了,這個問題咱們後面再解決 <Clock date={new Date()} />, document.getElementById('root') ); } setInterval(Tick, 1000);
React 給咱們提供了更好的管理個人代碼——組件。這裏咱們仍是首先咱們先了解一下自定義標籤:
const element = <Welcome name="Sara" />;
對這個標籤的理解也不難,它實際上調用了 Welcome 函數,而且將全部的屬性(這裏只有name)打包爲一個對象傳給 Welcome 函數。因此下面這個代碼輸出 」Hello Sara"
function Welcome(props){ return <h1>Hello, {props.name}</h1>; } const element = <Welcome name="Sara" />; ReactDOM.render( element, document.getElementById('root') );
組件幫助我事先一些重複的工做,好比這樣:
function Welcome(props){ return <h1>Hello, {props.name}</h1>; } function App(){ return ( <div> <Welcome name="Sara" /> <Welcome name="Cahal" /> <Welcome name="Edite" /> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
咱們能夠經過傳遞參數獲得同一個組件構建的不一樣模塊。
這裏咱們須要補充一個重要的概念:__純函數!!!__
若是一個函數執行過程當中不改變其參數,也不改變其外部做用於參數,當相同的輸入總能獲得相同的值時,咱們稱之這樣的函數爲純函數。__React 要求全部組件函數都必須是純函數。__
其實以前的一段代碼中 Tick, Welcome 函數就能夠看作是一個組件,同時 React 建議組件名的首字母大寫。可是更多狀況下咱們會用到 es6 的語法構建組件。以以前時鐘代碼爲例,轉換過程分爲五個步:
結果以下:
class Clock extends React.Component { render(){ return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
但這樣計時的功能就不能用了,咱們繼續往下看……
解決上面這個問題,就須要用到 State 和 Lifecycle 的知識了
咱們給 Clock 類添加一個構造函數,而且刪除 Clock 標籤中的參數:
class Clock extends React.Component { constructor(props){ super(props); this.state = {date: new Date()}; //state 用來記錄狀態 } render(){ return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, //刪除參數 document.getElementById('root') );
爲了控制計時的生命週期,咱們須要引入 2 個方法 componentDidMount() 和 componentWillUnmount(),前者在渲染(render方法)完成時當即執行,後者在該 render 的內容即將被移除前執行。
很明顯,前者適合註冊計時器,後者能夠用來清除計時器(防止內存泄露)
componentDidMount(){ this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount(){ clearInterval(this.timerID); }
下一步咱們重寫 tick 函數,此時的 tick 函數只須要修改 this.state 就好了。注意 React 要求不能直接修改該屬性,而是使用 setState() 方法,因此 tick 函數以下:
tick(){ this.setState({ date: new Date() }); }
這裏須要注意的是,當 state 中有不少屬性的時候,好比:
this.state = {name:"Lily", age: 12};執行 setState 方法修改其中的內容時並不會影響未修改的屬性:
this.setState({name: "Bob"}); //此後 this.state 爲 {name:"Bob", age: 12};此外 setState 多是異步的,因此不要在更新狀態時依賴前值:
// 這是個反例 this.setState({ counter: this.state.counter + this.props.increment, });爲例解決這個問題,你能夠傳入函數參數:
// Correct
this.setState((prevState, props) => ({ //這裏 prevState 更新前的 state 對象,props 爲新值構成的對象
counter: prevState.counter + props.increment
}));
此時,完整的代碼爲:
class Clock extends React.Component { constructor(props){ super(props); this.state = {date: new Date()}; } componentDidMount(){ this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount(){ clearInterval(this.timerID); } tick(){ this.setState({ date: new Date() }); } render(){ return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
React 事件註冊和原生 DOM 事件相似的,這裏須要理解一些不一樣點便可:
<button onClick={activateLasers}> Click Here </button>
return false
的方式阻止默認事件,必須顯式的調用 preventDefault(),而且在使用時不用糾結瀏覽器兼容問題,React 已經幫你處理好了render(){ return ( <button onClick={this.handleClick}> Click Here... </button> ); }
class Button extends React.Component { constructor(){ super(); this.name = "Bob"; this.click3 = this.click2.bind(this); this.click1 = () => { console.log(`hello ${this.name}`); } } click2(){ console.log(`hello ${this.name}`); } render(){ return ( <raw> <button onClick={this.click1}>Click1</button> <button onClick={this.click2}>Click2</button> <button onClick={this.click3}>Click3</button> <button onClick={(e) => this.click2(e)}>Click3</button> </raw> ); } }
class Button extends React.Component { constructor(){ super(); this.name = "Bob"; this.click = this.click.bind(this); } click(){ console.log(`hello ${this.name}`); } render(){ return ( <raw> <button onClick={this.click}>Click me</button> </raw> ); } }
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id, e)}>Delete Row</button>
根據不一樣的條件(一般指state)渲染不一樣的內容, 好比下面段代碼能夠根據 isLoggenIn
渲染不一樣的問候語:
function UserGreeting(props) { return <h1>Welcome back!</h1>; } function GuestGreeting(props) { return <h1>Please sign up.</h1>; } function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { // 根據 isLoggenIn 渲染不一樣的問候語 return <UserGreeting />; } return <GuestGreeting />; } ReactDOM.render( // 你能夠嘗試設置 isLoggedIn={true}: <Greeting isLoggedIn={false} />, document.getElementById('root') );
下面用 class 實現一個複雜一點的,帶有登陸/註銷按鈕的:
function LoginButton(props) { return ( <button onClick={props.onClick}> 登陸 </button> ); } function LogoutButton(props) { return ( <button onClick={props.onClick}> 註銷 </button> ); } class LoginControl extends React.Component { constructor(props) { super(props); this.state = { isLoggedIn: false }; // 修正 this 綁定 this.handleLoginClick = this.handleLoginClick.bind(this); this.handleLogoutClick = this.handleLogoutClick.bind(this); } handleLoginClick() { this.setState({isLoggedIn: true}); } handleLogoutClick() { this.setState({isLoggedIn: false}); } render() { const { isLoggedIn } = this.state; let button = null; if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; } return ( <div> {/* Greeting 取自上一個示例 (注意這裏的註釋寫法)*/} <Greeting isLoggedIn={isLoggedIn} /> {button} </div> ); } } ReactDOM.render( <LoginControl />, document.getElementById('root') );
固然,對於這樣一個簡單的示例,使用 if 可能你會覺的太複雜了,咱們也可使用 &&
?:
這些運算符來代替 if 語句,就像寫 javascript 代碼同樣。咱們極力的化簡一下上面的代碼:
class LoginControl extends React.Component { constructor(props) { super(props); this.state = { isLoggedIn: false }; } render() { const { isLoggedIn } = this.state; const button = isLoggedIn ? <button onClick={() => { this.setState({isLoggedIn: false}); }}>註銷</button> : <button onClick={() => { this.setState({isLoggedIn: true}); }}>登陸</button>; return ( <div> <h1> { isLoggedIn ? 'Welcome back!' : 'Please sign up.' } </h1> {button} </div> ); } } ReactDOM.render( <LoginControl />, document.getElementById('root') );
固然,若是你須要在某個條件下不進行渲染,那麼直接輸出 null 便可,好比下面這個組件,在 props.warn
爲 false
時不渲染任何內容:
function WarningBanner(props) { if (!props.warn) { return null; } return ( <div className="warning"> Warning! </div> ); }
須要注意的是,即使你輸出了 null, react 也會再渲染一次。同理,componentWillUpdate
和 componentDidUpdate
也會被調用。
在 React 中咱們可使用 map() 方法渲染列表,好比以下這個例子,將一組數據映射(map)爲一組 dom:
const data = [1, 2, 3, 4, 5]; const listItems = data.map((item) => <li key={number.toString()}>{item}</li> ); ReactDOM.render( <ul>{listItems}</ul>, document.getElementById('root') );
咱們注意到這裏咱們給 li (即列表的每一個元素)標籤加了一個 key 屬性,這個 key 用來幫助 React 判斷哪一個元素髮生了改變、添加或移除。關於這個 key 咱們須要明白如下幾點:
固然,上面代碼咱們也能夠寫成 inline 的形式:
const data = [1, 2, 3, 4, 5]; ReactDOM.render( <ul> { data.map((item) => <li key={number.toString()}>{item}</li> ); } </ul>, document.getElementById('root') );
表單的處理會和原生的 html 有一些區別,由於 React 能夠很好的幫助你使用 js 控制你的表單,這裏咱們須要引入一個新的概念:受控組件。
受控組件說白了就是其值受 react 控制的組件。其中,表單的元素一般都會具備其本身的 state,該值會隨着用戶的輸入改變。好比下面這個例子,會在用戶提交時輸出用戶的名字:
class NameForm extends React.Component { constructor(props) { super(props); this.state = {value: ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('A name was submitted: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
不難發現,這裏使用了,onchange 事件不斷的將用戶的輸入綁定到 this.state.value 上,而後經過和用戶輸入同步的重繪實現數據的顯示。這樣能夠很好的控制用戶輸入,好比同步的將用戶輸入轉化爲大寫:
handleChange(event) { this.setState({value: event.target.value.toUpperCase()}); }
理解了上面的內容咱們能夠知道,單純給一個 input 賦值一個值用戶是不能修改的,好比下面這行代碼:
ReactDOM.render(<input value="hi" />, mountNode);
但若是你不當心他的值設爲 null 或 undefined(等同於沒有 value 屬性),這個 input 就能夠被更改了:
ReactDOM.render(<input value="hi" />, mountNode); setTimeout(function() { ReactDOM.render(<input value={null} />, mountNode); }, 1000);
在 React 中 textarea 也是經過 value 屬性實現其內容變化的,而非其子節點:
class EssayForm extends React.Component { constructor(props) { super(props); this.state = { value: 'Please write an essay about your favorite DOM element.' }; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('An essay was submitted: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Essay: <textarea value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="Submit" /> </form> ); } }
在 React 中,對於 select 也會顯得很方便,你不須要在 option 中經過 selected 改變其值了,而是在 select 標籤上經過 value 屬性實現:
class FlavorForm extends React.Component { constructor(props) { super(props); this.state = {value: 'coconut'}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('Your favorite flavor is: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Pick your favorite La Croix flavor: <select value={this.state.value} onChange={this.handleChange}> <option value="grapefruit">Grapefruit</option> <option value="lime">Lime</option> <option value="coconut">Coconut</option> <option value="mango">Mango</option> </select> </label> <input type="submit" value="Submit" /> </form> ); } }
上面代碼默認選中 Coconut。 這裏值得注意的是,對於多選框,你能夠傳入一個數組做爲值:
<select multiple={true} value={['B', 'C']}>
當你控制不少個表單組件的時候要是爲每一個組件寫一個 handler 方法做爲 onChange 事件那就太麻煩了。因此 React 能夠經過表單元素的 name 配合 event.target.name 來控制表單:
class Reservation extends React.Component { constructor(props) { super(props); this.state = { isGoing: true, numberOfGuests: 2 }; this.handleInputChange = this.handleInputChange.bind(this); } handleInputChange(event) { const { target } = event; const value = target.type === 'checkbox' ? target.checked : target.value; const { name } = target; this.setState({ [name]: value }); } render() { return ( <form> <label> Is going: <input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} /> </label> <br /> <label> Number of guests: <input name="numberOfGuests" type="number"s value={this.state.numberOfGuests} onChange={this.handleInputChange} /> </label> </form> ); } }
這個部分,想說的不是個語法問題,而是代碼結構問題。咱們重點理解一個例子:計算溫度的功能。
咱們實現2個輸入框(攝氏溫度和華氏溫度)的同步數據顯示,和對數據的簡單操做(判斷是否達到標況下水的沸點100攝氏度)
咱們先作點準備工做,好比溫度轉換函數:
function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32; }
別忘了,一個好的程序員要可以很好的控制數據輸入,因此咱們再寫一個函數用來處理溫度,參數是溫度和溫度轉換函數:
function tryConvert(temperature, convert) { const input = parseFloat(temperature); if (Number.isNaN(input) || typeof convert !== 'function') { return ''; } const output = convert(input); const rounded = Math.round(output * 1000) / 1000; return String(rounded); }
咱們先簡單實現這個功能:
function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>; }
而後咱們寫一個組件用來讓用戶輸入溫度
class Calculator extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {temperature: ''}; } handleChange(e) { this.setState({temperature: e.target.value}); } render() { const { temperature } = this.state; return ( <fieldset> <legend>Enter temperature in Celsius:</legend> <input value={temperature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset> ); } }
此時咱們能夠輸入攝氏溫度了,再添加一個數據華氏溫度的地方。這裏咱們從上面的 Calculator 中提出來輸入組件:
const scaleNames = { c: 'Celsius', f: 'Fahrenheit' }; class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {temperature: ''}; } handleChange(e) { this.setState({temperature: e.target.value}); } render() { const { temperature } = this.state; const { scale } = this.props; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } }
這樣 Calculator 就簡單了:
class Calculator extends React.Component { render() { return ( <div> <TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div> ); } }
這樣2個輸入框就有了,可是它們還不能同步變化。並且 Calculator 組件不知道水溫是多少了,無法判斷溫度了。這是咱們應該吧溫度狀態放在他們最近的公共祖先元素上,這裏就是 Calculator 組件啦。
很明顯,首先要改的就是 TemperatureInput, 它不須要 state 了,咱們應該從參數獲取溫度了:
class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.props.onTemperatureChange(e.target.value); } render() { const { temperature, scale } = this.props; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={temperature} onChange={this.handleChange} /> </fieldset> ); } }
以後咱們修改 Calculator 的 state, 將 temperature 和 scale 放入其中, 並添加狀態轉換函數:
class Calculator extends React.Component { constructor(props) { super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); this.state = {temperature: '', scale: 'c'}; } handleCelsiusChange(temperature) { this.setState({scale: 'c', temperature}); } handleFahrenheitChange(temperature) { this.setState({scale: 'f', temperature}); } render() { const { temperature, scale } = this.state; const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature; const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature; return ( <div> <TemperatureInput scale="c" temperature={celsius} onTemperatureChange={this.handleCelsiusChange} /> <TemperatureInput scale="f" temperature={fahrenheit} onTemperatureChange={this.handleFahrenheitChange} /> <BoilingVerdict celsius={parseFloat(celsius)} /> </div> ); } }
到此全部的工做就完成了。咱們總結一下,輸入數據時都發生了什麼
咱們看看官方給出的效果:
React 建議用組件組合的方式代替組件繼承。因此咱們須要學習如何用組合代替繼承。
不少組件在事先是不知道本身的孩子(內部的元素)的。好比對話框這樣的盒子型元素。咱們須要使用 children 屬性來解決這個問題
function FancyBorder(props) { return ( <div className={'FancyBorder FancyBorder-' + props.color}> {props.children} </div> ); }
props.children 表示經過其餘組件調用 FancyBorder 時的所有孩子元素,對應下面例子,children 就是 h1 和 p 的 react 對象數組
function WelcomeDialog() { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> Welcome </h1> <p className="Dialog-message"> Thank you for visiting our spacecraft! </p> </FancyBorder> ); }
可是當組件缺少共同點的時候,咱們須要在組件中開不少孔,就像下面這個例子,這些孔能夠很好的幫咱們組合使用不少組件,並且 react 並不限制我咱們傳遞參數的類型
function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }
有時候咱們想對組件作具體化分類的時候,邏輯上會很像繼承,好比 WelcomeDialog 是 Dialog 的一個具體化分類。但在 React 中咱們依然用組合的方式實現這個功能:
function Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {props.title} </h1> <p className="Dialog-message"> {props.message} </p> </FancyBorder> ); } function WelcomeDialog() { return ( <Dialog title="Welcome" message="Thank you for visiting our spacecraft!" /> ); }
固然咱們也能夠用 class 的定義方式:
function Dialog(props) { return ( <FancyBorder color="blue"> <h1 className="Dialog-title"> {props.title} </h1> <p className="Dialog-message"> {props.message} </p> {props.children} </FancyBorder> ); } class SignUpDialog extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.handleSignUp = this.handleSignUp.bind(this); this.state = {login: ''}; } render() { return ( <Dialog title="Mars Exploration Program" message="How should we refer to you?"> <input value={this.state.login} onChange={this.handleChange} /> <button onClick={this.handleSignUp}> Sign Me Up! </button> </Dialog> ); } handleChange(e) { this.setState({login: e.target.value}); } handleSignUp() { alert(`Welcome aboard, ${this.state.login}!`); } }