[ 一塊兒學React系列 -- 2 ] UI的靈魂--State

概念引入

對於React來講, 沒有State就沒有頁面的渲染, 咱們也將什麼都看不到

咋一聽怎麼那麼唬人?不過的確是這樣,正如標題所言State是UI的靈魂。咱們都知道React的核心思想之一是組件化,將頁面所展現的東西按必定的規則分割成不少份並進行一一封裝最後抽象成咱們如今所稱爲的"組件", 就好像咱們搭積木同樣,一個城堡就是經過一個個小方塊堆疊在一塊兒的。可是FaceBook以爲若是僅僅是簡單的封裝那麼和普通的moudle有什麼區別?和鹹魚又有什麼區別?因而FaceBook給這些"組件"賦予了靈魂(之一) -- State
什麼叫State?顧名思義就是狀態的意思。每一個組件都有本身的狀態,好比開關的閉合、顏色的切換和顯示與隱藏等等都會涉及到狀態的切換。首先賣個關子,下面咱們一塊兒來複習下小學(仍是初中?)的一枚數學知識。前端

y=f(x) -->(假如這是一個一元一次函數)

Are you kidding me?這是要侮辱在座的智商?不不不,請放下手裏40米的大刀,筆者想拋個磚引個玉。
這是再簡單不過的了,它表示y是關於x的函數。函數在數學中是十分的嚴謹,x與y是一一對應關係換句話說就是同一個x代入運算獲得的永遠是同一個y;同一個y代入運算獲得的永遠是同一個x,這個特性很像Redux中的Reducer,本質上是一個純函數react

初識State

那麼若是咱們把這個公式帶到React中會有什麼樣的化學反應呢?git

UI=f(State)

有木有感受眼前一亮,Excuse me?居然把State和UI經過一個公式關聯起來?其實本質上就是這麼簡單。
同時,咱們還能夠繼續用函數的思想去思考它:github

  1. 輸入特定的State只能輸出特定的UI
  2. 根據特定的UI就能反推出相應的State

固然實際結果也是如此,React組件所渲染出來的東西與State有直接並且惟一的關係,換句話說就是State決定組件顯示什麼並且只有State才能決定組件顯示什麼api

好比上面提到的一個組件可能有不少切換的動做,開關、顏色、顯示與消失等等...原本這種切換的動做須要咱們本身經過操做DOM來實現,可是FaceBook在設計React之初就把 直接操做DOM 這條路給堵死了(但仍給咱們提供的必要的接口已備不時之需,後續文章會有相應內容),究竟爲何要這麼作?由於咱們都知道前端開發中特別消耗性能的是DOM操做,一旦處理不當就會影響整個頁面的展現效果,所以FaceBook一不作二不休直接搞出了一個State出來,讓開發者去輸入State隨後React本身去操做相應的DOM。數組

這麼作有兩個好處異步

  1. 使得State成爲頁面的惟一數據來源和頁面元素變換的惟一依據。
  2. 提升頁面的渲染性能(固然這不是React高效渲染的決定性因素)。

State大大提升了開發者對React組件的開發效率而不用擔憂頁面性能問題,可謂是一舉多得。函數

實例展現:靜態State

下面咱們來開發一個簡單的文字展現組件:oop

import React, {Component} from 'react'       ---line 1

class Show extends Component {               ---line 2

    constructor(props) {                     ---line 3
        super(props);
        this.state = {                       ---line 4
            content: 'Hello World'
        }
        //this.propName = propValue;         ---line 5
    }

    render() {                               ---line 6
        return (
            <p>{this.state.content}</p>      ---line 7
        )
    }
}

export default Show;
首先一塊兒來分析下這段代碼:
line 1: 平常導包
line 2: ES6建立對象的方法。強烈推薦這麼寫
line 3: 該組件的構造方法,若是組件有屬性默認值那麼就須要寫構造函數
line 4: 這裏表示該組件有本身的State屬性並且它仍是一個字面量對象,因此與該組件有關的全部State都應該寫在這個字面量對象中。從代碼中看出該組件有一個State對象 content,它包含着這個組件須要展現的一段文字。
line 5: 若是咱們想給這個組件定義State之外的屬性,那麼就能夠項這行所寫同樣,不過須要放在this對象中,這樣才能在組件中經過this對象讀取到。
line 6: render方法是最終構建組件結構的地方,由於組件究竟長什麼樣子,須要在這裏寫。
line 7: 正如這個組件須要作的事情,咱們在render方法中返回這個p標籤用來顯示文字信息。由於所須要的文字信息保存在State對象中,State又保存在this對象中,因此如何去獲取文字信息在這裏不須要過多贅述。 另外須要注意的是,render方法return的節點只能是一個,不能是多個。若是你的組件結構複雜,請在最外層用div這樣的標籤包一下而後再返回

下面看具體效果:組件化

clipboard.png
是否是很簡單?

實例展現:動態State

接下來再說一下狀態變化,由於狀態就是用來更改的,若是不更改那和鹹魚有什麼區別?

先思考下:React狀態應該如何更改?

這個問題筆者第一次遇到的時候第一反應就是:直接改啊!!!(而後被piapiapia打臉),先試下吧:

import React, {Component} from 'react'

class Show extends Component {

    constructor(props) {
        super(props);
        this.state = {
            content: 'Hello World'
        }
    }

    changeState = () => {
        this.state.content = 'I\'m React State';
    };

    render() {
        return (
            <div>
                <p>{this.state.content}</p>
                <button onClick={this.changeState}>變變變</button>
            </div>
        )
    }
}

export default Show;

咱們建立一個函數用來更改響應的State,而後實際運行的時候發現無論怎麼點按鈕都毫無做用?Why?

由於React給咱們提供了專門用於更改狀態的方法, this.setState()

咱們來從新試一下:

import React, {Component} from 'react'

class Show extends Component {

    constructor(props) {
        super(props);
        this.state = {
            content: 'Hello World'
        }
    }

    changeState = () => {
        this.setState({
            content: 'I\'m React State'
        })
    };

    render() {
        return (
            <div>
                <p>{this.state.content}</p>
                <button onClick={this.changeState}>變變變</button>
            </div>
        )
    }
}

export default Show;
這個方法須要咱們傳入一個字符量對象,key是咱們須要更改的那個State,這裏是content; value是咱們所指望要更改的值。( [\] 是轉移符)。

clipboard.png

能夠看出頁面中那行字變成了咱們所指望的文字。因此正如咱們所說:

  1. 更改State要使用this.setState()方法。
  2. 一旦更改了State,會觸發組件的從新渲染。其實是運行一次組件中的render方法

異步的setState

這個點筆者想不出合適的引入點,因此就直接拋出來了。這個問題頗有趣,由於巧合的是筆者的一個萌妹子同事在學習React時候剛好遇到這個問題,代碼大概是這樣:

onchange = () => {
    this.setState({
        name: 'Demo'
    });

    this.props.change(this.state.name)//調用父組件經過props傳過來的方法
};

她的本意是在本組件更改了name這個State後再經過調用父組件的方法實現父組件name的從新渲染。咱們看出this.props.change(this.state.name)傳入的參數是直接從State中取的,可是實際運行的時候卻不是如想象中那樣同時更改,現象是第一次點擊時候本組件成功渲染,可是父組件並無同時渲染;第二次點擊時候父組件才渲染成對應的名字。
爲何呢?
由於this.setState這個方法不是同步的而是異步的,瞭解JavaScript中Event Loop機制的朋友都知道,若是一段js代碼中有異步的代碼那麼會將其放在一個隊列中,等待這段代碼其他代碼運行完後再從那個隊列中取出異步代碼運行。this.setState機制也和它差很少,當咱們set一個State後React並不會當即去更改對應的State,而是在合適的時機下進行更改甚至爲了提升性能會將多個setState過程合併成一個。爲了證實這個異步機制,咱們經過打印的方式作個試驗:

import React, {Component} from 'react'

class Show extends Component {

    constructor(props) {
        super(props);
        this.state = {
            content: 'Hello World'
        }
    }

    changeState = () => {
        console.log(`1 -- ${this.state.content}`);
        this.setState({
            content: 'I\'m React State'
        });
        console.log(`2 -- ${this.state.content}`);
        console.log('end');
    };

    render() {
        return (
            <div>
                <p>{this.state.content}</p>
                <button onClick={this.changeState}>變變變</button>
            </div>
        )
    }
}

export default Show;

控制檯打印結果以下:

clipboard.png

能夠看出,頁面正常渲染了說明對應的State已經更改了,可是控制檯顯示的信息卻沒有更改後的現象,因此能夠肯定真正的setState並非調用this.setState()方法的瞬間,而是在以後的某個時間。因此有個問題須要注意:不要用當前的State去計算下一個State,由於你不能保證當前的State是最新的

但若是有個需求,須要在更改State後當即執行某個動做怎麼辦?

正常來講咱們沒法預知真正的setState是在什麼時候,因此React理所固然得給咱們提供了辦法,那麼就是this.setState的第二個參數,第二個參數是一個方法,當對應的State修改爲功後會當即執行,咱們修改下代碼:

...
changeState = () => {
    console.log(`1 -- ${this.state.content}`);
    this.setState({
        content: 'I\'m React State'
    }, () => {
        console.log(`3 -- ${this.state.content}`);
    });
    console.log(`2 -- ${this.state.content}`);
    console.log('end');
};
...

看結果咯:

clipboard.png

與預期一致,沒毛病!!!

State的不可變

看到這個小標題,估計不少人會很懵逼,前面還說不更改的State和鹹魚有什麼區別怎麼到這裏就要不可變了?實際上是混淆了。
官方的建議是將State的全部對象當作是不可變對象,一旦每一個對象更改了那麼須要從新建立這個對象。舉例子說,前面的代碼中有:

this.state = {
    content: 'Hello World'
}

當咱們更改了content的值,用"I'm React State"替換了原有的"Hello World"。其實在這裏,content對用的value不只僅是內容上的變化也是地址上的變化,這種在基本變量上體現不出來,好比咱們有個State要保存一個列表內容那麼就得是個數組(字面量對象亦如此):

this.state = {
    navis: ['React','Vue','Angular']
}

這個時候若是咱們直接將navis的值拿出來push一個元素進去而後setState:

addNavi = () => {
    this.setState({
        navis: this.state.navis.push('React-Native')
    })
};

結果是頁面並無從新渲染,Why? 由於React在對比navis新的和老的兩個值時候發現它們的地址都沒變化就認爲它們內容也沒變化就不會從新渲染。這是個坑!!!。因此此時State對象的不可變原則就有做用了,解決方案有兩個:

一、 複製原來的值,push完後進行setState。
addNavi = () => {
    let navisCopy = this.state.navis.slice();
    this.setState({
        navis: navisCopy.push('React-Native')
    })
};

這樣就能正常運行了,由於navis對應的值不只僅在內容上變了,地址也變化了,React檢測到變化後就進行了從新渲染。

二、第三方插件

  1. Immutable.js
  2. immutability-helper
  3. immutability-helper-x

至於爲何須要這麼作?

  1. State數據更明確,方便管理和開發調試。
  2. 爲了頁面渲染性能的考慮,有助於在shouldComponentUpdate中進行比較並肯定是否從新渲染。

Bingo...本期的博文就結束了,這期筆者也精心準備了好久,但願你們都能喜歡!!

相關文章
相關標籤/搜索