React進階之路——React基礎
JSX是一種用於描述UI的JavaScript擴充套件語法,React使用這種語法描述元件的UI。
1.基本語法
JSX的基本語法和XML語法相同,都是使用成對的標籤構成一個樹狀結構的資料。
const element = (
<div>
<h1>Hello, world!</h1>
</div>
)
2.標籤型別
兩種標籤型別:
(1)DOM型別的標籤(div,span等), 使用時,標籤首字母必須小寫
(2)React元件型別的標籤,使用時,元件名稱的首字母必須大寫
React通過首字母的大小寫
3.JavaScript表示式
JSX的本質是JavaScript。在JSX中使用JSX表示式需要用{}將表示式包起來。
表示式使用主要場景:
- 通過表示式給標籤屬性賦值
const element = <MyComponent foo={ 1 + 2 }/>
- 通過表示式定義子元件
const todos = ['item1', 'item2', 'item3']; const element = { <ul> {todos.map(message => <Item key={message} message={message} />)} </ul> };
=》是ES6中定義函式的方式。map是一種陣列遍歷方式,message是箭頭函式的引數,key是索引,message是對應todos的值。這裡將陣列todos依次遍歷在HTML程式碼中展示。
另外,JSX中只能使用JS表示式,不能使用多行JS語句。JSX可以使用三目運算子或者邏輯與(&&)運算子代替if語句。
4.標籤屬性
1.當JSX標籤是DOM型別的標籤時,對應的DOM標籤支援的屬性JSX也支援,部分屬性名稱有所變化,例如class寫成className,之所以改變是因為React對DOM標籤支援的事件重新做了封裝。事件屬性名稱採用駝峰格式。
2.當JSX標籤是React元件型別時,可以自定義標籤的屬性名。
5.註釋
JSX的註釋需要大括號”{}”將/**/包裹起來。
在《React進階之路》一書中提到JSX不是必需的。
2.2 元件
2.2.1元件定義
定義元件的兩種方式:使用ES6 class(類元件)和使用函式(函式元件)。
使用class定義元件滿足的兩個條件:
- class繼承自React.Component
- class內部必須自定義render方法,render方法返回代表該元件UI的React元素。
使用ReactDom.render()將PostList掛載到頁面的DOM節點上,在使用ReactDOM.render()需要先匯入react-dom庫,這個庫會完成元件所代表的虛擬DOM節點到瀏覽器的DOM節點的轉換。
import React from "react";
import ReactDOM from "react-dom";
import PostList from "./PostList";
ReactDOM.render(<PostList />, document.getElementById("root"));
2.2.2元件的props
元件的props用於把父元件中的資料或方法傳遞給子元件,供子元件使用。
Props是一個簡單結構的物件,它包含的屬性正是由元件作為JSX標籤使用時的屬性組成。 下面是一個使用User元件作為JSX作為JSX標籤的宣告:
<User name='React' age='4' address='America' >
props = {
name: 'React',
age: '4',
address: 'America'
}
{data.map(item =>
<PostItem
title={item.title}
date={item.date}
author={item.author}
/>
)}
這是類元件PostList中使用PostItem的程式碼,對data陣列中的元素遍歷一遍,然後返回大小相同的陣列(其中包含了title,author,date屬性),最終在UI展示。
2.2.3元件的state
元件的state是元件內部的狀態,state的變化最終將反映在元件UI的變化上。在構造方法constructor中通過this.state定義元件的初始狀態,並呼叫this.setState方法改變元件的狀態。
constructor(props){
super(props);
this.state = {
vote: 0
};
}
///處理點贊邏輯
handleClick(){
let vote = this.state.vote;
vote++;
this.setState({
vote:vote
});
}
- constructor內首先呼叫super(props),這一步實際上是呼叫React.Component這個class的constructor方法來完成React元件的初始化;
- constructor通過this.state定義元件的初始狀態;
- render方法中定義了處理點選事件的響應函式,響應函式內部會呼叫this.setState更新元件點贊數。
UI = Component(props,state)
React元件是通過props和state兩中資料驅動渲染出元件UI。Props是元件對外的介面,它是只讀的,元件通過props接受外部傳入的資料;state是元件對內的介面,它是可變的,元件內部狀態通過state來反映。
2.2.4有狀態元件和無狀態元件
state是用來反映元件內部狀態的變化,如果一個元件的內部狀態是不變的,這樣的元件稱為無狀態元件;反之稱為有狀態元件。
定義無狀態元件除了使用ES6 class方式外,還可以用函式定義。一個函式元件接收props作為引數,返回代表這個元件UI的React元素結構。
function Welcome(props){
return <h1>Hello, {props.name}</h1>;
}
有狀態元件主要關注狀態業務變化的業務邏輯,無狀態元件主要關注元件UI的渲染。
2.2.5屬性校驗和預設屬性
React提供了ProTypes這個物件,用於校驗元件屬性的型別。ProTypes包含元件屬性所有可能的型別,通過定義一個物件(物件的key是元件屬性名,val是對應屬性型別)實現元件屬性型別的校驗。
想要知道一個物件的結構或資料元素的型別,比較好的做法是使用ProTypes.shape或Protypes.arrayof。 如果屬性是元件的必需屬性,也就是使用某個元件時必須傳入的屬性,就要愛ProTypes的型別屬性上呼叫isRequired。
PostItem.propTypes = {
post: PropTypes.shape({
id: PropTypes.number,
title: PropTypes.string,
author: PropTypes.string,
date: PropTypes.string,
vote: PropTypes.number
}).isRequired,
onVote: PropTypes.func.isRequired
}
如上面程式碼,在PostItem元件中post和onVote是必需屬性。
React還提供了為元件屬性指定預設值的特性,這個特性通過元件的defaultProps實現。
2.2.6元件樣式
1. 外部CSS樣式表【首選】
此方式和平時開發Web應用時使用外部CSS檔案相同,CSS樣式表中根據HTML標籤型別、ID、class等選擇器定義元素樣式。唯一區別:React要用className代替class作為選擇器。
樣式表的引入方式:
- 使用元件的HTML頁面中通過標籤引入
- 把樣式表文件當做一個模組,在使用該樣式表的元件中,像匯入其他元件一樣匯入樣式表文件
注意class名稱衝突
2. 內聯樣式
2.2.7元件和元素
React元素是一個普通的JS物件,這個物件通過DOM節點或React元件描述介面是什麼樣子的。JSX語法就是用來建立React元素的。
<div className='foo'>
<Button color='blue'>
OK
</Button>
</div>
Button是一個自定義的React元件。
React元件是一個class或函式,它接收一些屬性作為輸入,返回一個React元素。React元件是由若干元素元件而成。下面的例子可以解釋React元件與React元素的關係:
class Button extends React.Component{
render(){
return (<button>OK</button>);
}
}
//在JSX中使用元件Button,button表示元件Button的一個react元素
const button = <Button />;
//在元件中使用React元素button
class Page extends React.Component{
render(){
return (
<div>
{button}
</div>
);
}
}
2.3 元件的生命週期
2.3.1掛載階段
這個階段元件被建立,執行初始化,並被掛載到DOM中,完成元件的第一次渲染。依次呼叫的生命週期方法有:constructor,componentWillMount,render,componentDidMount。
- constructor
這是ES6class的構造方法,元件被建立時,會先呼叫元件的構造方法。如果父元件沒有傳入屬性而元件自身定義了預設屬性,那麼引數props指向的是元件的預設屬性。Constructo通常用於初始化元件的state以及繫結時間處理方法等工作。
- componentWillMount
這個方法在元件被掛載到DOM前呼叫,且只會被呼叫一次。在實際專案中很少會用到,因為可以將該方法提前到constructor中執行。在這個方法中呼叫this.setState不會引起元件的重新渲染。
- render
這是定義元件時唯一必要的方法(元件的其他生命週期方法都可以省略)。在這個方法中,根據元件的props和state返回一個React元素,用於描述元件的UI,通常React元素使用JSX語法定義。注意:render並不負責元件的實際渲染工作,它只是返回一個UI的描述,真正的渲染出頁面DOM的工作由React自身負責。Render是一個純函式,在這個方法中不能執行任何有副作用的操作。
- componentDidMount
在元件被掛載到DOM後呼叫,且只會被呼叫一次。這時候已經可以獲取到DOM結構了。這個方法通常還會用於向伺服器端請求資料。在這個方法中呼叫this.setState會引起元件的重新渲染。
2.3.2更新階段
元件被掛載到DOM後,元件的props或state可以引起元件更新。props引起的元件更新,本質上是由渲染該元件的父元件引起的,也就是父元件的render方法被呼叫時,元件會發生更新過程,這個過程無論props是否改變,父元件render方法每一次呼叫,都會導致元件更新。State引起的元件更新,是通過呼叫this.setState修改元件 state觸發的。元件更新階段依次呼叫的宣告週期方法有:compoentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate。
- compoentWillReceiveProps(nextProps)
這個方法只有在props引起的元件更新過程中,才會被呼叫。State引起的元件更新並不會觸發該方法的執行。方法的引數nextProps是父元件傳遞給當前元件的新的props。因此往往需要比較nextProps和this.props來決定是否執行props發生變化後的邏輯,比如根據新的props呼叫this.setState觸發元件的重新渲染。
- shouldComponentUpdate(nextProps,nextState)
這個方法決定元件是否繼續執行更新過程。方法返回true時,元件繼續更新過程;返回false時,更新過程停止。後續的方法也不會再被呼叫給你。一般通過比較nextProps、nextState和當前元件的props、state決定這個方法的返回結果。這個方法可以用來減少組價不必要的渲染從而優化元件效能。
- componentWillUpdate(nextProps,nextState)
這個方法在元件render呼叫前執行,可以作為元件更新發生前執行,某些工作的地方,一般也很少用到。
- componentDidUpdate(prevProps,prevState)
元件更新後被呼叫,可以作為操作更新下後DOM的地方。這個方法的兩個引數分別代表元件更新前的props和state。
2.3.3解除安裝階段
元件從DOM中被解除安裝的過程,這個過程只有一個生命週期方法:componentWillUnMount。 這個方法在元件被解除安裝之前呼叫,可以在這裡執行一些清理工作,比如清理元件中使用的定時器等等,避免引起記憶體洩露。
最後注意:只有類元件具有生命週期方法,函式元件是沒有生命週期方法的。
2.4 列表和keys
①當列表資料為多組時,呼叫資料執行時為提示警告(瀏覽器F12->Console):” Each child in an array or iterator should have a unique ‘ key ’ prop ” 說明如果沒有給每條資料新增一個’ key ‘ 的話,當資料組數過多時,不同組數有相同的資料,呼叫就可能會出現錯誤。因此這裡的’ key ’ 就相當於資料庫裡的主鍵,給每組資料調取都有一個參照。
<div>
<h2>帖子列表</h2>
<ul>
{this.state.posts.map(item =>
<PostItem
key = {item.id}
post = {item}
onVote = {this.handleVote}
/>
)}
</ul>
</div>
②雖然列表元素的key不能重複,但這個唯一性僅限至於在當前列表中,而不是全域性唯一。
③不推薦使用索引作為key,因為一旦列表中的資料發生重排,資料的索引也會發生變化,不利於React的渲染優化。
2.5 事件處理
1.React元素繫結事件有兩點注意事項:
①在React中,事件的命名採用駝峰的形式(兩個或多個單片語成的事件第一個單詞首字母小寫,其餘的都要大寫),而不是DOM元素中的小寫字母命名方式。如:JavaScript裡的onclick要改為onClick、onchange要改為onChange。
②處理事件的響應函式要以物件的形式賦值給事件屬性,而不是DOM中的字串形式。
如:在DOM中繫結一個點選事件是這樣(傳統的js寫法):
<button onclick="clickButton()">
Click
</button>
而在React元素中繫結一個點選事件變成這種形式:
class App extends Component {
clickButton(){}; //clickButton函式
render() {
return (
<div>
<button
onclick={ this.clickButton }> //clickButton是一個函式
Click
</button>
</div>
)
}
}
2.React事件處理函式的三種寫法:
①使用箭頭函式(以下幾種情況不使用箭頭函式
class ...... {
constructor(props){
super(props);
this.state = { number:0 }
}
......
<button onClick={(event)=>{console.log(this.state.number)}>Click</button>
}
②使用元件方法(直接將元件的方法賦值給元素的事件屬性,同時在類的構造中,將這個方法的this繫結到當前物件)
class ...... {
constructor(props){
super(props);
this.state = { number:0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick(event){
const number = ++this.state.number;
this.setState({
number:number;
})
}
}
......
<div>{ this.state.number }</div>
<button onClick={ this.handleClick }>Click</button>
}
③屬性初始化語法(property initializer syntax) (使用ES7的property initializers會自動為class中定義的方法繫結this)
handleClick = (event) => {
const number = ++this.state.number;
this.setState({
number:number
})
}
三種方法進行比較:第一種每次呼叫render時,會重新建立一個新的事件處理函式,帶來額外的效能開銷;第二種建構函式中需要為事件處理函式繫結this,多個處理事件時,程式碼就會顯得繁瑣;第三種為推薦使用方法,這種方式既不需要在建構函式中手動繫結this,也不需要擔心元件重複渲染導致的函式重複建立問題。
2.6表單
2.6.1 受控元件
如果一個表單元素的值是由React來管理的,那它就是一個受控元件對於不同的表單元素,React的控制方式略有不同。下面展示一下三種長用的表單元素的控制方式。
1.文字框 文字框包含型別為text的input元素和textarea元素。它們受控的主要原理是:通過表單元素的value屬性設定表單元素的值,通過表單元素的o'nChange事件監聽值的變化,並將變化同步到React元件的state中。
import React,{Component} from 'react';
export default class Demo1 extends Component{
constructor(props){
super(props);
this.state = {name:'',password:''};
handleChange(event){ //監聽使用者名稱和密碼兩個input值的變化
const target = event.target;
this.setState({[target.name]: target.value});
}
handleSubmit(event){ //表單提交的響應函式
console.log('login successfully');
event.preventDefault();
}
}
render(){
return(
<form onSubmit={ this.handleSubmit }>
<label>
使用者名稱:<input type="text" name="name" value={this.state.name} onChange={this.handleChange}/>
</label>
<label>
密碼:<input type="password" name="password" value={this.state.password} onChange={this.handleChange}/>
</label>
<input type="submit" value="登陸"/>
</form>
)
}
}
使用者名稱和密碼兩個表單元素的值是從元件的state中獲取的,當用戶更改表單元素的值時,onChange事件會被觸發,對應的handleChange處理函式會把變化同步到元件的state,新的state又會觸發表單元素重新渲染,從而實現表單元素狀態的控制。
- 列表 列表select元素時最複雜的表單元素,他可以用來建立一個下拉表
在React中,通過在select上定義value屬性來決定哪一個option元素處於選中狀態。這樣,對select的控制只需要在select這一個元素上修改即可,不需要再關注option元素。 - 複選框和單選框 複選框的型別為checkbox的input元素,單選框為radio的input元素。復和單選框的值時不變的,需要改變的是他們的checked狀態。React控制的屬性變為checked
P48 程式碼類圖及關係的描述
PostItem和PostList都繼承於Component,兩個類之間相互關聯
PostList渲染的是
{this.state.posts.map(item =>
<PostItem key = {item.id} post= {item} onVote={this.handleVote} onSave={this.handleSave} /> )}
onVote這裡用到自己定義的方法handleVote(id),根據帖子的id進行過濾,找到待修改vote屬性的帖子,返回新的posts物件,然後更新state。
onSave這裡用到自己定義的方法handleSave(post),但用到PostItem中的屬性prop,根據post的id,過濾出當前要更新的post
PostItem渲染的是標題(通過handleTitleChange方法處理<textarea>值的變化)
點贊圖片實現函式綁在了onClick(即handleVote) handleEditPost來處理儲存編輯按鈕點選後的邏輯
2.6.2 非受控元件
非受控元件看似簡化了操作表單元素的過程,但這種方式破壞了React對元件狀態管理的一致性,往往出現不容易排查的問題,因此不建議使用。
2.7 本章小結
本章詳細介紹了React的主要特性及其用法。React通過JSX語法宣告介面UI,將介面UI和它的邏輯封裝在同一個JS檔案中。元件是React的核心,根據元件的外部介面props和內部介面state完成自身的渲染。使用元件需要理解它的生命週期,藉助不同的生命週期方法,元件可以實現複雜邏輯。渲染時,注意key的使用,事件處理時,注意事件名稱和事件處理函式的寫法。最後介紹表單元素的用法,使用方式分受控組價和非受控元件。另外,在工程專案中,注意有狀態元件和無狀態元件的劃分從而妥善運用props和state。