init
ReactDOM.render(
<h1>hello, world!</h1>
document.getElementById('root')
)
JSX
JSX
是Javascript
的語法擴展,建議在React
中配合使用JSX
,可以描述UI
交互,并且具有Javascript
的全部功能。JSX
中放置的大括號{}
會被當做有效的Javascript
表達式執行。Babel
會將JSX
轉譯成 React.createElement
函數調用。以下兩種示例代碼完全等效
cont element = (
<h1 className="greeting">
Hello. word
</h1>
)
const element = React.createElement(
'h1',
{className: 'greeting'},
'hello. world'
)
React.createElement
函數會預先執行一些檢查,以幫助編寫無錯代碼,實際上創建了一個被稱為 React
元素的對象,React
通過讀取這些對象,使用他們來構建DOM
并保持數據更新。
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'hello. world'
}
}
元素渲染
React
元素是不可變對象,被創建后無法更改它的子元素或屬性。更新UI
的方式是創建一個全新的元素,將其傳入ReactDOM.render()
。React DOM
會將元素和它的子元素間的狀態進行比較,只會進行必要的更新使得DOM
達到預期的狀態。
function tick() {
const element = (
<div>
<h1>hello. world</h1>
<h2>It is { new Date().toLocaleTimeString() }<h2>
</div>
)
ReactDOM.render(element, document.getElementBtId('root'))
}
setInterval(tick, 1000)
組件 & Props
組件允許將 UI 拆分為獨立可復用的代碼片段,接受任意的入參 props
,返回用于展示頁面元素內容的 React
元素。
函數組件接收 props
對象,返回 React
元素。
function Welcome(props) {
return <h1>hello {props.name}</h1>
}
也可以使用 ES6
的 class
定義組件
class Welcome extends React.Component {
render() {
return <h1>hello {this.props.name}</h1>
}
}
渲染組件
React
元素為用戶自定義組件時,會將 JSX
接收的屬性和子組件轉換為單個props
對象傳遞給組件。下面的部分會被渲染成 Hello Sara
。
function Welcome(props) {
return <h1>hello . {props.name}</h1>
}
const element = <Welcome name="Sara"/>
ReactDOM.render(
element,
document.getElementById('root')
)
組件名稱必須以大寫字母開頭,React
會將以小寫字母開頭的組件視為原生 DOM
標簽。這個例子中發生的流程
調用
ReactDOM.render()
函數,并傳入<Welcome name="Sara"/>
作為參數React
調用Welcome
組件,并將{name: 'Sara'}
作為props
傳入Welcome
組件將<h1>Hello, Sara</h1>
元素作為返回值React DOM
將DOM
高效更新為<h1>Hello, Sara<h1>
每個新的React
應用程序的頂層組件都是App
組件,如果將React
集成到現有的應用程序中,需要使用一些小的功能性組件,自下而上地將這類組件逐步應用到視圖中的每一處。
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
組件必須像純函數(不會更改函數入參)一樣保護 props
不被更改。state
允許React
組件隨用戶操作、網絡相應或者其他變化而動態更改輸出內容。
State 與生命周期
state
與props
類似,props
是私有的,并且完全受控于當前組件。將函數組件轉換成 class
組件:
class Clock extends React.Component {
constructor(props) {
super(props) //將props傳遞到父類的構造函數中, class組件應該始終使用props參數來調用父類的構造函數
this.state = {date: new Date()}
}
componentDidMount() {//componentDidMount方法會在組件已經被渲染到DOM中后運行
this.timerId = setInterval(() => this.tick(), 100)
}
componentWillUnmount() {
clearInterval(this.timerId)
}
tick() {
this.setState({ //使用 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')
)
每次組件更新時render
方法都會被調用,但只要在相同的DOM
節點中渲染<Clock/>
,就僅有一個Clock
組件的class
實例被創建使用,使得我們可以使用state
或生命周期方法的很多特性。
關于state
使用的注意事項
-
構造函數是唯一可以給
this.state
賦值的地方,其他情況下不要直接修改state
,要通過setState()
this.setState({comment: 'hello'})
-
state
的更新可能是異步的。出于性能考慮,React
可能會把多個setState()
調用合并成一個調用。this.props
和this.state
可能是異步更新的,不要依賴他們的值更新下一個狀態。解決方式是讓setState
接收一個函數而不是對象。this.setState((state, props) => ({ m: state.a + props.b }))
當調用
setState()
時,React
會把提供的對象合并到當前的state
中-
數據是向下流動的,除了擁有并設置
state
的組件,其他組件無法訪問。組件可以選擇把它的state
作為props
向下傳遞到它的子組件中。function FormattedDate(props) { return <h2>It is { props.date.toLocaleTimeString() }</h2> } <FormattedDate date={this.state.date}></FormattedDate>
事件處理
React
元素的事件處理和DOM
元素的很相似,但有幾點不同
React
的事件命名采用小駝峰式,不是純小寫-
使用
JSX
語法時需要傳入一個函數作為事件處理函數,不是字符串<button onClick={activateLasers}> Active Lasers</button>
-
不能通過返回
false
的形式阻止事件的默認行為,必須使用顯示的preventDefault
function ActionLink() { function handleClick(e) { e.preventDefault() // e是一個合成事件,react 根據 w3c 規范來定義這些合成事件,不需要擔心跨瀏覽器的兼容問題 console.log('The link was clicked') } return ( <a href="#" onClick={handleClick}>Click me</a> ) }
-
在
React
中,不需要使用addEventListener
為已創建的DOM
元素添加監聽器,只需要在元素初始渲染的時候添加監聽器即可// 渲染一個讓用戶切換開關狀態的按鈕 class Toggle extends React.Component { constructor(props) { super(props) this.state = { isToggleOn: true } this.handleClick = this.handleClick.bind(this) // 為了在回調中綁定 this,這個綁定是必不可少的 } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })) } render() { return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ) } }
-
JSX
函數中的this
需要正常使用的情況下,需要使用bind
進行綁定class Toggle extends React.Component { constructor(props) { super(props) this.state = { isToggleOn: true} this.handleClick = this.handleClick.bind(this) //為了在回調中使用this } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })) } // class Fields 語法 handleClick = () => { console.log('this is:', this) } render() { return ( <button onClick={this.handleClick}> <!--> 或者在回調中使用箭頭函數 <button onClick={() => this.handleClick()}> <--> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ) } } ReactDOM.render( <Toggle/>, document.getElementById('root') )
-
事件的參數傳遞
<button onClick={(e) => this.deleteRow(id, e)}>Detelet Row</button> <button onClick={this.deleteRow(this, id)}>Detelet Row</button>
條件渲染
-
if
function UserGreeting(props) { return <h1>Welcome back</h1> } function GuestGreeting(props) { return <h1>Please Sign up</h1> } // 創建 Greeting 組件,根據用戶是否登錄決定顯示哪一個組件 function Greeting(props) { const isLoggedIn = props.isLoggedIn if(isLoggedIn) { return <UserGreeting/> } return <GuestGreeting/> } ReactDOM.render( <Greeting isLoggedIn={false}/>, document.getElementById('root') )
-
與 &&
function Mailbox(props) { const unreadMessage = props.unreadMessage return ( <div> <h1>hello</h1> {unreadMessage.length > 0 && <h2>You hava {unreadMessage.length} unread message/</h2> } </div> ) } const message = ['a', 'b', 'c'] ReactDOM.render( <Mailbox unreadMessages={message}/>, document.getElementById('root') )
-
三目 ? :
render() { const isLoggedIn = this.state.isLoggedIn return ( <div> The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in. </div> ) }
-
阻止條件渲染
在組件已經被其他組件渲染的情況下,隱藏組件,設置在組件的
render
方法中返回null
function WarningBanner(props) { if(!props.warn) return null return ( <div classNmae='warning'>Warning</div> ) }
列表 & key
-
列表渲染
function NumberList(props) { const numbers = props.numbers const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> ) return ( <ul>{listItems}</ul> ) } const numbers = [1, 2, 3, 4, 5] ReactDOM.render( <NumberList number={number}/>, document.getElementById('root') )
key
幫助React
識別元素的添加和刪除,應該給數組中的每一個元賦予一個確定的標識。一個元素的key
最好是這個元素在列表中獨一無二的字符串,通常使用數據的id
作為元素的key
。如果選擇不顯示指定key
值,react
將默認使用索引作為列表項目的key
值。key
只需要在兄弟節點之間唯一,不需要全局唯一。在map
方法中的元素需要設置key
屬性。
function ListItem(props) {
return <li>{props.value}</li>
}
function NumberList(props) {
const numbers = props.numbers
const listItens = numbers.map((number) =>
<ListItem key={number.toString()} value={number}/>
)
return (
<ul>
{listItems}
</ul>
)
}
const numbers = [1, 2, 3, 4, 5]
ReactDOM.render(
<NumberList number={numbers}/>,
document.getElementById('root')
)
ket
會傳遞信息給React
,但不會傳遞給組件。如果組件中需要使用key
屬性的值,請用其他屬性名顯示傳遞這個值。
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
)
// Post組件可以讀出 props.id,不能讀出 props.key
表單
在html
中,表單元素 <input>
,<textarea>
和<select>
通常自己維護state
,并且根據用戶輸入進行更新。在React
中,可變狀態 mutable state
通常保存在組件的state
屬性中,只能通過setState
更新。React
的state
作為作為表單元素的唯一數據源,渲染表單的React
組件控制著用戶輸入過程中表單發生的操作,這種表單元素叫做受控組件。
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('提交的名字:' + this.state.value)
event.preventDefault()
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type='text' value={this.state.value} onChange={this.handleChange}></input>
</label>
<input type='submit' value='提交'/>
</form>
)
}
}
由于在表單元素上設置了value
屬性,因此顯示的值將始終為this.state.value
,使得React
的state
成為唯一數據源。由于handleChange
在每次更新時都會執行并更新React
的state
,顯示的值將隨著用戶輸入而更新。
一些需要注意的處理場景:
-
文件
input
標簽。在HTML
中,<input type='file'>
允許用戶從存儲設備中選擇一個或多個文件,將其上傳到服務器,此時為受控組件。<input type='file'> <!--value只讀,是React中的一個非受控組件-->
-
受控組件輸入空值
ReactDOM.render(<input value='hi'/>, mountNode) setTimeout(function(){ ReactDOM.render(<input value={null}/>, mountNode) }, 1000)
狀態提升
多個組件需要反映相同的變化數據,將共享狀態提升到最近的共同父組件中。實現一個輸入的溫度轉換功能(攝氏度與華氏度)
class Temperature extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value)
// 移除組件自身的 state,通過獲取 props 獲取溫度數據,當想要響應數據改變時,需要調用 父組件提供的 this.props.onTemperatureChange(), 不再使用 this.setState()
}
render() {
const temperature = this.props.temperature
const scale = this.props.scale
return (
<fieldset>
<legend>Enter temperature in {scaleNmaes[scale]}</legend>
<input value={temperature}
onChange={this.handleChange}/>
</fieldset>
)
}
}
針對整體的Calculator
組件
class Calculator extends React.Component {
constructor(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.steState({scale: 'f', temperature})
}
render() {
const scale = this.state.scale
const temperature = this.state.temperature
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}/>
/>
</div>
)
}
}
在React
中,任何可變數據應當只有一個唯一數據源,state是首先添加到需要渲染數組的組件中,如果其他組件也需要這個state
,可以將他提升到這些組件的最近共同父組件中,依靠自上而下的數據流,而不是嘗試在不同組件間同步state
。提升state
的方式比雙向綁定方式需要編寫更多代碼,但是可以使得排查和隔離bug
所需的工作量變小。
組合 & 繼承
-
使用
children prop
將子組件傳遞到渲染結果中function SplitPane(props) { return ( <div className='SplitPane'> <div className='SplitPane-left'> {pros.left} </div> <div className='SplitPane-right'> {pros.right} </div> </div> ) } function App() { return ( <SplitPane left={ <Contacts/> }/> <SplitPane right={ <Chat/> }/> ) }
在
FaceBook
的各個應用場景下,沒有發現需要使用繼承來構建組件層次的情況。props
和組合提供了定制組件的靈活方式,組件可以接受任意props
,包括基本數據類型,React
元素及函數。如果想在組件間復用非UI的功能,可以將其提取為單獨的javascript
模塊,組件可以直接import
,無需通過extend
繼承。