1. 程式人生 > >React進階之路——React基礎

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通過首字母的大小寫

判斷渲染的是DOM型別標籤還是React元件型別標籤。

3.JavaScript表示式

JSX的本質是JavaScript。在JSX中使用JSX表示式需要用{}將表示式包起來。

表示式使用主要場景:

  1. 通過表示式給標籤屬性賦值
const element = <MyComponent foo={ 1 + 2 }/>
  1. 通過表示式定義子元件
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定義元件滿足的兩個條件:

  1. class繼承自React.Component
  2. 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

    });

}
  1. constructor內首先呼叫super(props),這一步實際上是呼叫React.Component這個class的constructor方法來完成React元件的初始化;
  2. constructor通過this.state定義元件的初始狀態;
  3. 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作為選擇器。

樣式表的引入方式:

  1. 使用元件的HTML頁面中通過標籤引入
  2. 把樣式表文件當做一個模組,在使用該樣式表的元件中,像匯入其他元件一樣匯入樣式表文件

注意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。

  1. constructor

這是ES6class的構造方法,元件被建立時,會先呼叫元件的構造方法。如果父元件沒有傳入屬性而元件自身定義了預設屬性,那麼引數props指向的是元件的預設屬性。Constructo通常用於初始化元件的state以及繫結時間處理方法等工作。

  1. componentWillMount

這個方法在元件被掛載到DOM前呼叫,且只會被呼叫一次。在實際專案中很少會用到,因為可以將該方法提前到constructor中執行。在這個方法中呼叫this.setState不會引起元件的重新渲染。

  1. render

這是定義元件時唯一必要的方法(元件的其他生命週期方法都可以省略)。在這個方法中,根據元件的props和state返回一個React元素,用於描述元件的UI,通常React元素使用JSX語法定義。注意:render並不負責元件的實際渲染工作,它只是返回一個UI的描述,真正的渲染出頁面DOM的工作由React自身負責。Render是一個純函式,在這個方法中不能執行任何有副作用的操作。

  1. componentDidMount

在元件被掛載到DOM後呼叫,且只會被呼叫一次。這時候已經可以獲取到DOM結構了。這個方法通常還會用於向伺服器端請求資料。在這個方法中呼叫this.setState會引起元件的重新渲染。

2.3.2更新階段

元件被掛載到DOM後,元件的props或state可以引起元件更新。props引起的元件更新,本質上是由渲染該元件的父元件引起的,也就是父元件的render方法被呼叫時,元件會發生更新過程,這個過程無論props是否改變,父元件render方法每一次呼叫,都會導致元件更新。State引起的元件更新,是通過呼叫this.setState修改元件 state觸發的。元件更新階段依次呼叫的宣告週期方法有:compoentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render和componentDidUpdate。

  1. compoentWillReceiveProps(nextProps)

這個方法只有在props引起的元件更新過程中,才會被呼叫。State引起的元件更新並不會觸發該方法的執行。方法的引數nextProps是父元件傳遞給當前元件的新的props。因此往往需要比較nextProps和this.props來決定是否執行props發生變化後的邏輯,比如根據新的props呼叫this.setState觸發元件的重新渲染。

  1. shouldComponentUpdate(nextProps,nextState)

這個方法決定元件是否繼續執行更新過程。方法返回true時,元件繼續更新過程;返回false時,更新過程停止。後續的方法也不會再被呼叫給你。一般通過比較nextProps、nextState和當前元件的props、state決定這個方法的返回結果。這個方法可以用來減少組價不必要的渲染從而優化元件效能。

  1. componentWillUpdate(nextProps,nextState)

這個方法在元件render呼叫前執行,可以作為元件更新發生前執行,某些工作的地方,一般也很少用到。

  1. 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又會觸發表單元素重新渲染,從而實現表單元素狀態的控制。

  1. 列表 列表select元素時最複雜的表單元素,他可以用來建立一個下拉表
    在React中,通過在select上定義value屬性來決定哪一個option元素處於選中狀態。這樣,對select的控制只需要在select這一個元素上修改即可,不需要再關注option元素
  2. 複選框和單選框  複選框的型別為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。