[TOC]
## pre-notify
> previously:[http://a1](http://a)
(づ ̄ 3 ̄)づ此文會根據文檔的變動而不斷更新
## React中的element是什么
上回我們解釋了什么是`JSX`,`JSX`是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}/>
```
上面的`prop1`和`prop2`兩個鍵值對會**被封裝成一個`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()

## 列表和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.](https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318)
---
參考:
- [React官方文檔](https://reactjs.org/docs/hello-world.html)
--: ToBeContinue...
- 空白目錄
- 01.JSX,了解一下?
- JSX與虛擬DOM
- React
- 02.React文檔精讀(上)`
- React路由
- 關于BrowserRouter
- 關于Route
- 應用
- 權限認證
- case1
- context
- 新context
- 03.React路由
- 04.Diff
- 05.styled-components
- redux設計思想與API
- redux實現1
- 06.redux2
- 06.redux3
- 關于狀態初始化
- saga
- 新版
- 使用saga進行業務邏輯開發
- react-router-redux
- React性能優化
- immutable使用
- 未整理
- FAQ
- 常用中間件
- pureComponent
- 項目相關總結
- antd分尸
- 按需加載
- ReactWithoutJSX
- 我的組件庫
- C領域
- 用戶接口
- htmlType
- style
- show
- conjure
- grid
- inject
- stop
- 內部接口
- 衍生組件
- Button
- 報錯集錦
- ReactAPI
- 類上的那些屬性
- prop-types
- React.createElement
- React.cloneElement
- React.Children和props.children
- react元素和react組件關于作為children方面的那些問題
- react組件與虛擬dom
- ref