React文件精讀(上篇)

Cris_冷崢子發表於2019-03-04
  • pre-notify
  • React中的element是什麼
    • 條件渲染
  • element和component
  • 渲染React Element
  • component與pure function
  • 函式式元件和類元件
  • state/狀態
    • setState
      • state設定更新時的自動合併機制
      • setState是非同步的
  • component和事件
    • 和原生事件的區別
    • this
  • 表單
    • state和受控元件
      • value = {null}
      • 關於select
      • 關於file
    • ref和非受控元件
  • 列表和keys
    • key和重繪
    • li 和
    • key值無法獲取
    • 不推薦用索引作為key

pre-notify

previously:JSX,瞭解一下?

(づ ̄ 3 ̄)づ此文會根據文件的變動而不斷更新

React中的element是什麼

上回我們解釋了什麼是JSXJSX是React中對Javascript語法的延伸,它允許我們使用JSX tag來構建一個虛擬DOM作為真實DOM的小型描述文件(它本身是一個Javascript物件)。

其中,一個JSX tag就是一個React Element

條件渲染

我們說過JSX是能參與js流程控制的,So我們能通過if一類的來決定一個React Element什麼時候進行渲染什麼時候不要。

let element =  (
    <div>
      {isLogin}&&
      <h2>hello {userName}</h2>
    </div>
  );
複製程式碼

也支援三元

element和component

上面我們已經知道一個JSX tag就是一個React Element,但一個JSX tag也可以是一個React Component

它長得像這樣

let elements = <Component10086 />
複製程式碼

這。。。和之前的有撒區別?

首先我們需要注意一個代表Component/元件的JSX tag必須首字母大寫,否則它就會把它當做一個普通的element去渲染。

嗯。。。很自然我們會提出一個疑問,那當做普通的element去渲染和當做一個元件去渲染有什麼區別呢?

其實,React中的元件就像是Javascript 中的函式,可以接收任意的輸入(我們稱之為props)並且能夠返回React elements,這和我們直接得到React Element的區別在於,我們能在函式內部再對element進行一次封裝,做一些條件渲染,設定state什麼的。


So,我們光建立一個JSX tag是不夠的,我們還需要定義一個對應的函式

function Component10086(props){
  return <h1>Hello Component!</h1>;
}
複製程式碼

這個函式有一個pros屬性,我們可以像呼叫函式一樣在建立一個JSX tag時傳遞引數給這個函式,像這樣

let elements = <Component10086 prop1='第一個引數' prop2='第二個引數' />

// --- --- ---

//也支援... 來傳遞多個屬性
let data = {
 prop1:'第一個引數'
 ,prop2:'第二個引數'
}
let elements = <Component10086 {...data}/>
複製程式碼

上面的prop1prop2兩個鍵值對會被封裝成一個props物件作為函式的引數傳入

function Component10086(props){
  return (
  <h1 className={props.prop2}/*當做屬性渲染*/>
  	Hello Component!
    {props.prop1} //當做children渲染
  </h1>;
  )
}
複製程式碼

[important] 注意:在JSX那一篇中我們已經說過JSX是javascript語法的延伸,這意味著一個JSX tag可以被賦值給一個js變數,能參與for和if流程控制,能作為函式傳參,能夠被當做函式返回值。So元件傳參時也能將一個JSX tag做為引數進行傳遞


渲染React Element

我們已經知道如何構建一個虛擬DOM來描述真實的DOM

let element = <h1 className={class1}>hello React!</h1>
複製程式碼

(以上經過babel編譯後,我們可以發現它其實是呼叫React.createELement來建立一個javascript物件來描述真實dom,上回已詳細說過不再贅述)

[danger] 引入(import)react庫時,React必須是首字母大寫,因為編譯後我們呼叫createElement方法時就是首字母大寫的React

但怎麼將這個虛擬DOM轉換成真正的DOM渲染在頁面上呢?

這就是我們React中react-dom庫所做的事情了。引入這個庫後,我們能呼叫其中的render方法將對應的真實DOM渲染到指定的掛載點上。

import ReactDOM from 'react-dom';
...

ReactDOM.render(
    element
    ,document.geElementById('root')
)
複製程式碼

值得注意的是如果這個React Element被重新渲染,react只會重新渲染這個element中發生改變的dom節點。(我們說過一個JSX tag可以有children)。


同樣的,我們能使用同樣的方式對一個元件進行渲染,

ReactDOM.render(
    <Component10086 prop1='...' />
    ,document.geElementById('root')
)
複製程式碼

那麼,render是怎樣區分渲染的是一個元件還是一個普通的element呢?

我們呼叫ReactDOM.render去渲染一個JSX tag時,

我們首先會檢視這個tag的首字母是否是大寫,如果是大寫就代表是一個component,那麼react就會把它當做一個元件去渲染,

它會首先將JSX tag做為引數傳遞的屬性封裝成一個props物件,然後再將這個物件傳遞給元件函式

元件函式接收到引數物件後會進行一系列處理,最終返回處理完成後的React element

最終,render拿到element轉換成真正的DOM元素渲染到頁面上。

component與pure function

關於React Component,有一點我們需要注意的是,所有的react元件都必須是一個純函式。

什麼是純函式呢?

Such functions are called “pure” because they do not attempt to change their inputs, and always return the same result for the same inputs.

React官方給出的即是是,相同的輸入會產生相同的輸出,並且我們不能在函式內部更改傳遞過來的props

這意味著,props是靜止的,不可更改的,但一些互動性灰常強的UI元件的一些屬性是經常會變化的。

那怎麼解決這個問題呢?這就是後話了,後面講到React state就是用來解決這個問題的。

[important] 注意: 如果屬性是不參與render()的那麼它就不應該被設計成state

函式式元件和類元件

元件有兩種形式,函式式的和類形式的,

//function
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
}

//class
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

ReactDOM.render(<Welcome name='ahhh' age='123'/>,document.getElementById('root'));
複製程式碼

此時這兩種寫法是等價的,但!

class類的形式支援一些函式式元件不支援的功能,比如說state

state/狀態

首先state也是prop,但不同於普通的props

普通的props我們可以在JSX tag上傳參,react會自動幫我們將這些引數打包後掛載到元件上(this.props)。

state需要我們在元件物件上手動設定,

setState

首先我們在元件中初始化一個元件例項的state

class Clock extends React.Component{
  constructor(props){
    super(props);
    // 在建構函式內部定義初始狀態
    this.state = {date:new Date()};
  }
複製程式碼

如果這個state需要發生改變,我們需要注意,我們不能直接通過this.state.date = xxx 這樣的形式去改變state,這樣是不會觸發render()進行重繪的。

我們需要通過 this.setState 方法(來自繼承的React.Component)來改變我們原有的state

語法: setState(updater[, callback])注意此方法是支援回撥的

this.setState({
  date:new Date()
})
複製程式碼

另外關於setState有兩點需要額外注意

state設定更新時的自動合併機制

我們可能在一個建構函式中初始化多個state鍵值對

...
this.state = {
    state1:'xx'
    state2:'yy'
}
..
複製程式碼

React state更新時的自動合併機制允許我們這樣去更新state

this.setState({
    state1:'aaa'
})
複製程式碼

可以發現我們只需要在setState中填上我們要更改的部分而不是整個state物件。

setState是非同步的

我們可能在一個方法中連續使用多次setState,但由於設定是非同步的,我們不能在第二次呼叫setState方法時拿到第一次呼叫setState所設定的值,它們的state都是基於最初的state的。

那麼這個問題如何解決呢?

其實setState還有第二種形式,使用回撥函式而非物件的形式去更新state,像這樣

this.setState((prevState,props)=>({counter:prevState.counter + Math.random()}));
this.setState((prevState,props)=>({counter:prevState.counter + props.increment}))
複製程式碼

值得一提的是這種寫法是 setState(updater[, callback])的語法糖形式,最終仍然會編譯成這樣執行。

component和事件

和原生事件的區別

  • React事件命名採用的是駝峰式而非原生中的小寫形式
  • 原生繫結時傳遞的是一個字串,而react則是利用{}
//原生
<button onclick="activateLasers()">
  Activate Lasers
</button>

//React
<button onClick={activateLasers}>
  Activate Lasers
</button>
複製程式碼
  • react中的ev物件是經過react封裝過的
  • 不支援return false
function ActionLink() {
  function handleClick(e) { //這個event不是原生的,而是自己封裝的,故不存在相容問題
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}
複製程式碼

this

如果是用類的形式定義的元件,我們需要注意事件函式中this的指向問題。

在react中我們不需要手動使用addEventListener對元素進行事件函式的繫結,只需要在JSX tag上像原生行內式繫結事件函式一樣註冊事件即可。

但這存在一個問題,react並不會幫我們把回撥中的this指向元件例項,甚至這個this也不是指向那個應該繫結的DOM元素

我們是希望這個this指向元件例項的,這樣我們能拿到掛載在這個元件例項上的state。而改變回撥this指向的方式大概有三種。

  • 通過bind繫結事件處理函式
  • 通過箭頭函式包裝事件處理函式
  • 通過ES7初始值語法來建立事件處理函式

我們推薦第三種,十分方便

handleClick = ()=>{
    this.setState(...);
}
複製程式碼

表單

state和受控元件

正常來說當我們在一個input輸入後會立刻得到相應的顯示在介面上。

但React允許我們在輸入後,先把輸入的資訊hold住,進行一些處理後再顯示在介面上。

這是怎麼做到的呢?這就是通過我們之前所說的React state

首先我要讓一個input的value等於一個state

 <input type="text" onChange={this.handleChange.bind(this,'username')} value={this.state.username}/>
複製程式碼

我們知道一個state在react中只能通過呼叫setState才會觸發render導致重繪UI,這意味著只要我們不立刻呼叫setState,那麼input的值就不會立刻改變。

在上面的示例中我們通過給input繫結一個事件,來對輸入進行處理

handleChange = (key,event)=>{
    let val = event.target.value;
    this.setState({[key]:val})
}
複製程式碼

這樣我們就完成了對錶單輸入的截獲,使其受到了我們的控制,我們將這樣的表單元素稱之為受控元件。


有一點要注意我們在上慄用到了es6的computed property語法

setState({[key]:val})
複製程式碼

其中{[key]:val}就相當於let o={};o[key]=val,這種語法允許我們在設定物件的鍵名時也能使用變數。

我們之所以要在事件處理函式中傳遞一個key過去,是因為一張表裡可能有很多表單元素,每一個都對應我們元件中的一個state,而這個key就是用來區分他們的。

value = {null}

表示不再是受控元件

關於select

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>
複製程式碼

不推薦以上,推薦在select裡寫value

<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>
複製程式碼

多選

<select multiple={true} value={['a', 'b']}>
複製程式碼

關於file

input=file 是不會受到react控制的

ref和非受控元件

我們在受控元件中能通過繫結事件處理函式的形式拿到元件中的表單元素。

但是像<input type="file">這種非受控元件,我們如果還想要截獲它的值怎麼截獲到呢?

首先我們不可能像其它表單元素一樣在它自個兒身上繫結事件處理函式,因為對於file,只有form表單提交時我們才能拿到file的值,

so我們只能在form上繫結事件處理函式,那怎麼拿到form中的file元素呢?

這就需要利用到React ref

<input type="text" ref="username" />

// 處理函式中
let username = this.refs.username.value
複製程式碼

以上寫法ref=一個字串已經被廢棄

現在推薦這麼寫

<input type="text" ref={input=>this.username=input} /> //input就是渲染出的真實dom
複製程式碼

可以發現,這裡的ref裡對應的是一個函式,此函式會在當此虛擬DOM轉成真實DOM並插入到頁面之後立刻呼叫,引數接收到的就是插入的真實dom

更新:16.3新特のReact.createRef(),ref={input=>this.username=input}仍可用

React文件精讀(上篇)

列表和keys

記得我們說過,JSX tag可以作為函式返回值

So,我們能夠這樣渲染一個列表

let array = [1,2,3,4,5];
let lists = array.map((item,index)=><li key={index}>{item}</li>);

ReactDOM.render((
  <ul>
    {lists}
  </ul>
), document.getElementById('root'));
複製程式碼

注意上慄中我們給每個li都繫結了一個key,這個key是陣列中的索引位置,是獨一無二的。

key和重繪

如果我們不給li繫結key,React會報一個警告,

Warning: Each child in an array or iterator should have a unique "key" prop.

但其實它已經自動幫我們加上key了。

為什麼react渲染列表的時需要一個key呢?

記得我們上面說過react重繪時不會將整個React Element都重繪的嗎。

嗯。。。那它是怎麼做到的呢?就是利用這個key了,如果沒有這個key,它是無法區別element中的tag誰是誰的。

li 和 <ListItem>

有些時候一個li內的內容過於複雜,我們會將其封裝成一個元件

這個時候我們推薦把key掛載這個li的元件上,而不是元件內部返回的li上

以下示例出資React文件

unction NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()}
          value={number} />

);
複製程式碼

key值無法獲取

如果我們在元件上傳遞了一個key值,這個key值並不會被包裝進props物件

不推薦用索引作為key

詳見 in-depth explanation on the negative impacts of using an index as a key.


參考:

--- ToBeContinue... ---

相關文章