iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 10
5
Modern Web

一步一腳印的React旅程系列 第 10

[筆記][React]使用React開發的思考模式(1)-記得把共同狀態提升

前面幾篇文章把React基本的用法都說完了,剩下的就是該如何將它應用在實務面中,所以接下來我會試著和各位一同做出一些小作品,但是在那之前,我們得先了解如何正確的使用React開發網站是最好的!當然小弟也還是新手,所以如果接下來的文章中有任何不適當的地方,還麻煩請各位大大留言告訴我了!謝謝!


記得提升狀態!

沒錯!要記得提升狀態!再解釋這個觀念前,先說說狀態吧!

狀態

之前大多時候都是用英文,所以看中文可能反而不認識他XD,哎!但是套一句某牌奶茶的原來我們這麼近,這個「狀態」就是我們常用的state

那要提升狀態是什麼?就是盡量把可共用的state往外面的組件放,例如說有A和B組件同時都需要一個state的值,那我們就可以把這個state放在離A和B最近的一個父組件上,例如官方給了溫度計的例子。

實例

上面提到了官方文件上的例子:溫度計,以下讓我們來實做看看:

//判斷溫度是否達沸點
class Title extends React.Component {
    render(){
        //溫度100度以上就到達沸點
        return <h1>{(this.props.temperature>=100 ? '達到沸點!!!':'未到沸點...')}</h1>
    }
}

class Celsius extends React.Component {
    constructor(props) {
        super(props)
        //用state來記錄溫度數值
        this.state = ({ temperature : 0 })
        //設定changeState是在此class下執行
        this.changeState = this.changeState.bind(this)
    }

    changeState(event){
        //取得目前輸入的值
        let temperature = event.target.value
        //把值寫到state裡面
        this.setState({ [event.target.name] : temperature })
    }

    render() {
        return(
            /*用Title組件顯示目前輸入的溫度*/
            <div>
                <Title temperature={this.state.temperature} />
                {/*這裡顯示輸入的溫度*/}
                <span>目前攝氏溫度是:{this.state.temperature}</span><br/>
                {/*輸入溫度的地方*/}
                <input name="temperature" 
                        value={this.state.temperature} 
                        onChange={this.changeState}/>
            </div>
        )
    }
}

ReactDOM.render(<Celsius  />, document.getElementById('root'))

結果會如下,在組件中使用state儲存溫度,並判斷他如果超過100度就達到沸點:
https://ithelp.ithome.com.tw/upload/images/20180930/20106935eJ705K25ti.jpg

GitPage頁面連結
Github程式碼連結

目前乍看之下感覺也沒什麼,只是用之前學到的東西做出一個簡單的例子,但是這也只是目前而已,當我們再進階一點,將上方的例子再加入華氏的溫度輸入,那該怎麼做呢?首先讓我們保持著組件「Don't repeat yourself」的概念,試著改寫Celsius讓他可以透過傳入props來重複使用在兩種溫度單位上。

這邊Title的部分就先不說,其他就像上面說的,先將輸入溫度的Input組件先做出來,之後再建立另一個EasyForm組件當作他的父組件並在render使用它,以下會把EasyFormInput一起說明:

//輸入溫度的組件
class InputTemperature extends React.Component{
    /*因為將該組件需要用到的state(例如:state.temperature或state.changeState)都移到共同的父組件<EasyForm>
    所以這裡只需要render負責輸出組件內容*/
    render(){
        return(
            /*所有的資料都從父組件傳進來,所以使用props接收資料,包括function*/
            <div>
                <span>目前輸入溫度是:{this.props.temperature}度{this.props.type}</span><br/>
                <input name="temperature" 
                        value={this.props.temperature} 
                        onChange={this.props.changeState} />度{this.props.type}
            </div>
        )
    }
}


class EasyForm extends React.Component {
    constructor(props) {
        super(props)
        //用state來記錄溫度數值和該數值是哪個溫度單位(攝氏或華氏)
        this.state = ({ temperature : 0,type : '' })
        //設定changeState是在此class下執行
        this.changeState = this.changeState.bind(this)
    }

    //轉換溫度單位
    toConvert(temperature,type){
        //如果type是C就帶公式將華氏轉攝氏,F就轉華氏
        if (type == 'C')
            return (temperature-32)*5/9
        else
            return (temperature*9/5)+32

    }

    //傳入type代表現在是哪種溫度變化
    changeState(type){
        //取得目前輸入的值
        let temperature = window.event.target.value
        //將目前溫度和溫度的單位寫進去state中
        this.setState({ 'temperature' : temperature,'type' : type })
    }

    render() {
        /*在render的時候,先去取state判斷目前儲存的溫度是攝氏還華氏
        華氏的話temperature_F就不用轉,攝氏的話換temperature_C不轉
        但是如果有不同就得傳進toConvert中做單位的轉換*/
        let temperature_C = this.state.type=='F' ? this.toConvert(this.state.temperature,'C') : this.state.temperature
        let temperature_F = this.state.type=='C' ? this.toConvert(this.state.temperature,'F') : this.state.temperature

        return(
            <div>
                {/*因為條件設定設是大於100度,所以傳入攝氏溫度*/}
                <Title temperature={temperature_C} />

                {/*同樣都是<InputTemperature />,
                但根據設置的props不同,就會有不同的結果*/}
                <InputTemperature type="C" 
                temperature={temperature_C} 
                /*設定事件changeState,
                所以在<InputTemperature />中就可以用props呼叫該事件*/
                changeState={this.changeState.bind(this,'C')} />

                <InputTemperature type="F" 
                temperature={temperature_F} 
                changeState={this.changeState.bind(this,'F')} />
            </div>
        )
    }
}

嗚嗚,我一直很想講的是JSX的程式碼區塊真的不太容易閱讀,還請各位大大包含/images/emoticon/emoticon02.gif
上方的程式碼已經有註解了,但下方還是說明一下程式撰寫的過程:

  1. 建立一個InputTemperatureclass組件,並在render回傳:

    <div>
        <span>目前輸入溫度是:{this.props.temperature}度{this.props.type}</span>
        <br/>
        <input name="temperature" 
               value={this.props.temperature} 
               onChange={this.props.changeState} />度{this.props.type}
    </div>
    

    雖然InputTemperature內只有render但是input內有不少用props傳進來的資料,像是控制值的value={this.props.temperature}和觸發事件會執行的onChange={this.props.changeState},這些資料從哪裡來呢?讓我們繼續看下去!

  2. 從上方程式碼中還有一個EasyForm,先來說說他的組成,首先是constructor中的state設定了this.state = ({ temperature : 0,type : '' })temperature用來儲存溫度另一個this.changeState = this.changeState.bind(this)用來設定溫度變化時執行的function

  3. 接著來看看changeState的內容:

    changeState(type){
         //取得目前輸入的值
         let temperature = window.event.target.value
         //將目前溫度和溫度的單位寫進去state中
         this.setState({ 'temperature' : temperature,'type' : type })
     }
    

    可以看到傳入的參數type,這裡會寫進溫度temperature和溫度單位type(攝氏或華氏),並更新到state中,而這個function也透過props傳入InputTemperatureonChange中。

  4. 眼尖的大大應該也有發現EasyForm中還有一個用來換算單位的function:「toConvert

    toConvert(temperature,type){
         //如果type是C就帶公式將華氏轉攝氏,F就轉華氏
         if (type == 'C')
             return (temperature-32)*5/9
         else
             return (temperature*9/5)+32
    
     }
    

    上方的function用來轉換溫度用的,我們在EasyFormrender中會用到它。

  5. EasyFormrender,以下就是重點了,所以我們分兩段來看,一個是return的部份、另一個是reutrn之前,我們先看return之前做了哪些事情:

    /*在render的時候,先去取state判斷目前儲存的溫度是攝氏還華氏*/
    let temperature_C = this.state.type=='F' ? this.toConvert(this.state.temperature,'C') : this.state.temperature
    let temperature_F = this.state.type=='C' ? this.toConvert(this.state.temperature,'F') : this.state.temperature
    

    這裡要考驗大家的記憶力了!記得我們在InputTemperature改變的時候發生什麼事嗎?沒錯!會透過onChange觸發從props傳入的changeState,然後changeState改變了溫度和溫度單位,所以這時候儲存的溫度單位就是最近一次輸入的溫度是攝氏或華氏,那為什麼還要在這邊放兩個變數去裝?不就把statetemperature傳回去不就好了嗎?

    因為我們這時候分成攝氏和華氏,如果我在華氏的input輸入新溫度,那攝氏的input就要跟著變動,所以如果我在某一個input輸入溫度那這裡就會判斷state中的type是攝氏還華氏(這裡攝氏用C儲存、華氏用F),所以用來放攝氏溫度的temperature_C變數會先去判斷type是不是攝氏,如果是攝氏就直接帶入state中的temperature,但是如果是華氏就要先經過toConvert轉換成攝氏後再給temperature_C,而華氏temperature_F的部分就是反過來。

  6. 總算是最後一步了,算是把所有放出去的線都收回來,讓我們看看EasyFormrenderreturn了什麼:

    <div>
        <Title temperature={temperature_C} />
    
        {/*同樣都是<InputTemperature />,
        但根據設置的props不同,就會有不同的結果*/}
        <InputTemperature type="C" 
            temperature={temperature_C} 
            /*設定事件changeState,
            所以在<InputTemperature />中就可以用props呼叫該事件*/
            changeState={this.changeState.bind(this,'C')} />
    
         <InputTemperature type="F" 
            temperature={temperature_F} 
            changeState={this.changeState.bind(this,'F')} />
    </div>
    

    Title的部份因為我們是用攝氏判斷沸點,所以要傳轉換成攝氏的temperature_C給他。

    InputTemperature的部分傳入了三個值,第一個是type用來渲染單位,第二個是把溫度各自轉換後的值temperature,最後是設定onChange觸發後執行的functionfunction後帶有.bind(this,'C'),之前文章有提過觸發事件傳入參數的方法,所以後面的'C'會傳入到changeState(type)type中更新state

以上!就是撰寫的過程了!我們把原本第一個範例中輸入溫度的Celsius組件中的state都放到外面的EasyForm,這樣紀錄temperature溫度的值就是單向的,不會分散在兩個組件(就算是同一組件用兩次也是)中的state紀錄,所以以後有儲存相同資料的state,不妨就把它拿出來到共同的父組件吧!

結果如下:
https://ithelp.ithome.com.tw/upload/images/20181002/20106935ljQd16K6tV.jpg

GitPage頁面連結
Github程式碼連結


最後如果上方文章中有任何錯誤或解釋不清楚的地方,再麻煩各位大大留言告訴我,小弟會再盡快修正!謝謝大家/images/emoticon/emoticon41.gif

參考文章:

  1. https://reactjs.org/docs/lifting-state-up.html

上一篇
[筆記][React]受不受控的Component與Form表單
下一篇
[筆記][React]使用React開發的思考模式(2)-組件的構成模式
系列文
一步一腳印的React旅程30

尚未有邦友留言

立即登入留言