首頁 > 軟體

React元件三大屬性之state,props,refs

2022-07-21 18:00:52

1.1基本理解和使用

1.1.1 使用React開發者工具偵錯

React Developer Tools

1.1.2 定義元件的方式

Ⅰ.函數式元件:

<script type="text/babel">
    // 1.建立函數式元件  (首字母必須大寫)
    function MyComponent() { //(函數必須有返回值)
        console.log(this)  //undefined (本來this指向的是window,但是由於babel翻譯完之後啟用es5的嚴格模式,自定義的函數裡的this不讓指向window)  
      return <h2>我是用函數定義的元件(適用於【簡單元件】的定義)</h2>
    }
    // 2.渲染元件到頁面  (必須寫元件標籤)
    ReactDOM.render(<MyComponent />, document.getElementById('test'))
  </script>

執行了ReactDOM.render(…之後發生了什麼?

1.React解析元件標籤,找到了MyComponent元件。

2.發現元件是使用函數定義的,隨後呼叫該函數,將返回的虛擬DOM轉為真實DOM,隨後呈現在頁面中。

類的基本知識複習移步到vscode

Ⅱ.類式元件:

複雜元件:如果元件是有狀態的,那麼就是複雜元件。

<script type="text/babel">
    // 1.建立類式元件
    class MyComponent extends React.Component {
      render() {
        // render是放在哪裡的?——MyComponent的原型物件上,供範例使用
        // render中的this是誰?——MyComponent的範例物件 <=> MyComponent元件範例物件
        console.log('render中的this是誰:', this)
        return <h2>我是用類定義的元件(適用於【複雜元件】的定義)</h2>
      }
    }
    // 渲染元件到頁面
    ReactDOM.render(<MyComponent />, document.getElementById('test'))

執行了ReactDOM.render(…之後發生了什麼?

1.React解析元件標籤,找到了MyComponent元件。

2.發現元件是使用類定義的,隨後new出來該類的範例,並通過該範例呼叫到原型上的render方法。

3.將render返回的虛擬DOM轉為真實DOM,隨後呈現在頁面中。

1.1.3 注意

  • 元件名必須首字母大寫
  • 虛擬DOM元素只能有一個根標籤
  • 虛擬DOM元素必須有結束標籤

1.1.4 渲染類元件標籤的基本流程

  • React內部會建立元件範例物件
  • 呼叫render()返回得到虛擬DOM, 並解析為真實DOM
  • 插入到指定的頁面元素內部

1.2 元件範例的三大核心屬性之一:state

1.2.1 理解

  • state是元件物件最重要的屬性, 值是物件(可以包含多個key-value的組合)
  • 元件被稱為"狀態機", 通過更新元件的state,來更新對應的頁面顯示(重新渲染元件)
  • 資料存放在狀態裡,來驅動對應頁面的顯示

1.2.2 案例

需求: 定義一個展示天氣資訊的元件

  • 預設展示天氣炎熱 或 涼爽
  • 擊文字切換天氣

效果如下:

1.2.3 在類式元件使用state

<script type="text/babel">
    // 1.建立元件
    class Weather extends React.Component {
      // 藉助構造器初始化狀態
      // 構造器呼叫了幾次?————1次
      constructor(props) {
        console.log('constructor')
        super(props)
        // 初始化狀態   
        this.state = { isHot: true, wind: '微風' }
        //解決changeWeather中this指向的問題
        this.changeWeather = this.changeWeather.bind(this)
        // 這是一個賦值語句,右邊的this.changeWeather是指順著原型鏈找到了changeWeather(),然後呼叫bind(),bind可以生成一個新的函數,改變this指向。括號中為傳入的this,指得就是類中的範例物件 
        // 拿到了原型上的changeWeather,通過bind生成了一個新的changeWeather掛在範例自身
      }
      // render呼叫了幾次?————1+n次 1是初始化的那次,n是狀態更新的次數
      render() {
        console.log('render')
        // 讀取狀態
        const { isHot, wind } = this.state
        //        react:將changeWeather呼叫的返回值賦值給onClick,不用加括號  
        //        原生js:onclick時呼叫changeWeather函數,要加括號
        return <h1 onClick={this.changeWeather}>今天天氣很{isHot ? '炎熱' : '涼爽'},{wind}</h1>
        //    含義是:這是一個賦值語句,把右邊這個函數交給onClick作為回撥,
        //            等你點選的時候,react幫你調changeWeather()函數
      }

      // changeWeather呼叫了幾次?————點幾次調幾次
      changeWeather() {
        console.log('changeWeather')
        // changeWeather放在哪裡?——Weather的原型物件上,供範例使用
        // 通過Weather範例呼叫changeWeather時,changeWeather中的this就是Weather範例
        console.log(this)   //undefined
        // 因為changeWeather不是通過範例呼叫的,是直接呼叫,所以this不會指向 範例物件
        // 那麼 changeWeather 的this 是指向 undefined還是 window呢
        // 是 undefined 因為 類中的區域性函數預設開啟了嚴格模式(類自動開的,與babel無關),所以不能指向 window

        // 獲取原來的isHot值
        const isHot = this.state.isHot
        // 嚴重注意:狀態(state)裡的資料不能直接進行更改,下面這行就是直接更改,react不認可!!!
        // this.state.isHot = !isHot  錯誤寫法

        // 嚴重注意:狀態必須通過setState進行修改,並且修改是一種合併,不是替換,只修改了isHot,wind不會丟失
        this.setState({ isHot: !isHot })
      }
    }
    // 2.渲染元件到頁面
    ReactDOM.render(<Weather />, document.getElementById('test'))

  </script>

1.2.4 在類式元件使用state的簡寫方式

<script type="text/babel">
    // 1.建立元件
    class Weather extends React.Component {
      // 初始化狀態時直接在類裡面寫賦值語句
      state = { isHot: true, wind: '微風' }
      render() {
        const { isHot, wind } = this.state
        return <h1 onClick={this.changeWeather}>今天天氣很{isHot ? '炎熱' : '涼爽'},{wind}</h1>
      }

      // 自定義方法 ————在用類去建立一個元件時,元件裡所有自定義的方法都要用 賦值語句+箭頭函數 的形式
      changeWeather = () => {   //箭頭函數沒有this,箭頭函數內的this指向其外側
        const isHot = this.state.isHot
        this.setState({ isHot: !isHot })
        console.log(this)
      }
    }
    ReactDOM.render(<Weather />, document.getElementById('test'))
  </script>

1.2.3 強烈注意

  • 元件中render方法中的this元件範例物件
  • 元件自定義的方法中this為undefined,如何解決?

①強制繫結this:通過函數物件的bind()
②箭頭函數 ()=>{}

狀態資料,不能直接修改或更新,狀態必須通過setState進行修改

1.3 元件範例的三大核心屬性之一:props

1.3.1 理解

  • 每個元件物件都會有props(properties 的簡寫)屬性
  • 元件標籤所有屬性都儲存在props中 1.3.2 案例

需求: 自定義用來顯示一個人員資訊的元件

  • 姓名必須指定,且為字串型別
  • 性別為字串型別,如果性別沒有指定,預設為男
  • 年齡為字串型別,且為數位型別,預設值為18

效果如下:

1.3.3 作用

  • 通過標籤屬性從元件外向元件內傳遞變化的資料
  • 注意: 元件內部不要修改props資料,因為props是唯讀的

1.3.4 在類式元件使用props

先從內部讀取某個屬性值:

//範例物件身上有個屬性props,需要傳值進去,那怎麼傳呢?html標籤能寫標籤屬性(key:value),那麼元件標籤(<Person/>)也能寫屬性
// 解構賦值 提前從props身上拿到這三個屬性 
const { name, age, sex } = this.props

對props中的屬性值進行型別限制和必要性限制:

第一種方式(React v15.5 開始已棄用):

Person.propTypes = {
 name: React.PropTypes.string.isRequired,
 age: React.PropTypes.number
}

第二種方式(新):使用prop-types庫進限制(需要引入prop-types庫)

//給Person加上propTypes屬性,react就能幫你限制了
//寫在Person類外面
Person.propTypes = {
  // 具體的propTypes規則,要去PropTypes(React裡面內建的一個屬性)裡面找
  name: PropTypes.string.isRequired, //限制name必傳,且為字串
  age: PropTypes.number //限制age為數值
}

在類式元件使用props的簡寫方式

// 用static表示給類自身加上一個propTypes和defaultProps屬性,而不是給類的範例物件加屬性
// 寫在Person類裡面
static propTypes = {
   name: PropTypes.string.isRequired,  //限制name必傳,且為字串
   sex: PropTypes.string, //限制sex為字串
   age: PropTypes.number, //限制age為數值
 }

static defaultProps = {
   sex: '男',  //sex預設值為不男不女
   age: 18  //age預設值為18
 }

擴充套件屬性: 將物件的所有屬性通過props傳遞

// 展開運運算元在對物件使用時,應當注意以{}包裹起來
<Person {...person}/>
//...展開運運算元具體運用看vscode

預設屬性值:

Person.defaultProps = {
  age: 18, //age預設值為18
  sex:'男'//sex預設值為男
}

元件類別建構函式:

//開發中很少寫構造器,能省則省
// 構造器是否接收props,是否傳遞給super,取決於:是否希望在構造器中通過this存取props
constructor(props){
  // 只要寫了構造器,就一定要呼叫super(),一定要傳props
  super(props)
  console.log(props)//列印所有屬性
}

1.3.5 在函數式元件使用props

<script type="text/babel">
    // 建立元件
    //函數式元件能接收引數
    function Person(props) {
      // console.log(props)
      const { name, sex, age } = props
      return (
        <ul>
          <li>姓名:{name}</li>
          <li>性別:{sex}</li>
          <li>年齡:{age}</li>
        </ul>
      )
    }
    Person.propTypes = {
      name: PropTypes.string.isRequired,  //限制name必傳,且為字串
      sex: PropTypes.string, //限制sex為字串
      age: PropTypes.number, //限制age為數值
    }

    Person.defaultProps = {
      sex: '男',  //sex預設值為不男不女
      age: 18  //age預設值為18
    }
    ReactDOM.render(<Person name='旭旭' />, document.getElementById('test'))
  </script>

1.4 元件範例的三大核心屬性之一:refs與事件處理

1.4.1 理解

元件內的標籤可以定義ref屬性來標識自己(相當於原生裡是id)

1.4.2 效果

需求: 自定義元件, 功能說明如下:

  • 點選按鈕, 提示第一個輸入框中的值
  • 當第2個輸入框失去焦點時, 提示這個輸入框中的值

1.4.3 字串形式的ref

<script type="text/babel">
    // 建立元件
    class Demo extends React.Component {
      // 展示左側輸入框的資料
      showData = () => {
        const { input1 } = this.refs
        alert(input1.value)
      }
      // 展示右側輸入框的資料
      showData2 = () => {
        const { input2 } = this.refs
        alert(input2.value)
      }

      render() {
        return (
          <div>
            <input ref="input1" type="text" placeholder="點選按鈕提示資料" /> &nbsp;
            <button onClick={this.showData}>點我提示左側的資料</button> &nbsp;
            <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦點提示資料" />
          </div>
        )
      }
    }
    //渲染元件
    ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>

過時的API:String 型別的 Refs

如果你之前使用過 React,你可能瞭解過之前的 API 中的 string 型別的 ref 屬性,例如 "textInput"。你可以通過 this.refs.textInput 來存取 DOM 節點。我們不建議使用它,因為 string 型別的 refs 存在 一些問題。它已過時並可能會在未來的版本被移除。

效果如下:

1.4.4 回撥形式的ref

<script type="text/babel">
    // 建立元件
    class Demo extends React.Component {
      // 展示左側輸入框的資料
      showData = () => {
        const { input1 } = this
        alert(input1.value)
      }
      // 展示右側輸入框的資料
      showData2 = () => {
        const { input2 } = this
        alert(input2.value)
      }

      // 回撥函數的特點:你定義的,別人調的,最終執行了
      // 回撥函數的引數正是ref當前所處的那個input節點
      render() {
        return (
          <div>
            {/* 程式碼執行步驟: */}
            {/* React載入Demo元件時,執行render函數內的jsx程式碼,發現input中有ref屬性,屬性內容是一個箭頭函數,React就會幫我們呼叫這個回撥函數,並且把當前的DOM傳進這個函數,這樣就可以接收到當前的DOM節點了,並把這個DOM放在元件範例自身上 */}
            {/* 箭頭函數接收到引數(當前的DOM節點)命名為(currentNode),將currentNode賦值給範例物件下的input1這個屬性 */}
            {/* 箭頭函數只有一個引數可以省略'()',箭頭函數右邊只有一條函數體可以省略'{}' */}
            {/*<input ref={currentNode => this.input1 = currentNode} type="<text>" placeholder="點選按鈕提示資料" />&nbsp;*/}
            <input ref={(currentNode) => { this.input1 = currentNode }} type="text" placeholder="點選按鈕提示資料" /> &nbsp;
            <button onClick={this.showData}>點我提示左側的資料</button> &nbsp;
            <input ref={(currentNode) => { this.input2 = currentNode }} onBlur={this.showData2} type="text" placeholder="失去焦點提示資料" />
          </div>
        )
      }
    }
    //渲染元件
    ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>

效果如下:

1.4.5 回撥ref中回撥執行次數的問題(class的繫結函數)

<script type="text/babel">
    // 建立元件
    class Demo extends React.Component {

      state = { isHot: true }

      showInfo = () => {
        const { input1 } = this
        alert(input1.value)
      }

      changeWeather = () => {
        // 獲取原來的狀態
        const { isHot } = this.state
        // 更新狀態
        this.setState({ isHot: !isHot })
      }

      savaInput = (currentNode) => {
        this.input1 = currentNode;
      }

      render() {
        const { isHot } = this.state
        return (
          <div>
            <h1>今天天氣很{isHot ? '炎熱' : '涼爽'}</h1>
            {/* React在更新元件時,會先傳入null呼叫一次ref中的回撥,以清空之前的ref;然後再傳入引數currentNode,以呼叫第二次。每更新一次元件,ref中的回撥函數就會被呼叫兩次,一次傳入null,一次傳入引數currentNode。為了應對這種情況的出現,官方建議將ref的回撥函數定義成 class 的繫結函數 的方式去避免上述的問題。 */}

            {/*行內函式*/}
            {/*<input ref={(currentNode) => { this.input1 = currentNode; console.log('@', currentNode) }} type="text" /><br /><br />*/}
            <input ref={this.savaInput} type="text" /><br /><br />
            <button onClick={this.showInfo}>點我提示輸入的資料</button>&nbsp;
            <button onClick={this.changeWeather}>點我切換天氣</button>
          </div>
        )
      }
    }
    //渲染元件
    ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>

React在更新元件時,會先傳入null呼叫一次ref中的回撥,以清空之前的ref;然後再傳入引數currentNode,以呼叫第二次。每更新一次元件,ref中的回撥函數就會被呼叫兩次,一次傳入null,一次傳入引數currentNode。為了應對這種情況的出現,官方建議將ref的回撥函數定義成 class 的繫結函數 的方式去避免上述的問題。

效果如下:

1.4.6 createRef建立ref容器

<script type="text/babel">
    // 建立元件
    class Demo extends React.Component {
      myRef = React.createRef()
      myRef2 = React.createRef()
      /* React.createRef呼叫後可以返回一個容器,該容器可以儲存被ref所標識的節點,該容器是「專人專用」的,也就是,呼叫React.createRef()建立了一個容器,通過賦值語句賦值給範例自身名為myRef的屬性上 */
      // 展示左側輸入框的資料
      showData = () => {
        // console.log(this.myRef)
        alert(this.myRef.current.value)
      }
      // 展示右側輸入框的資料
      showData2 = () => {
        alert(this.myRef2.current.value)
      }

      // 回撥函數的特點:你定義的,別人調的,最終執行了
      // 回撥函數的引數正是ref當前所處的那個input節點
      render() {
        return (
          <div>
            {/* React執行render函數中的jsx程式碼時,
            發現input上有一個ref屬性而且是通過createRef方法建立的,
            React就會把當前ref屬性所在的DOM節點放到之前建立的那個容器上,
            也就是把當前input這個DOM節點放到了範例自身名為myRef的容器上 */}
            {/* 可以簡寫成ref={this.myRef = React.createRef()} */}
            <input ref={this.myRef} type="text" placeholder="點選按鈕提示資料" /> &nbsp;
            <button onClick={this.showData}>點我提示左側的資料</button> &nbsp;
            {/* 發生事件的元素正好是需要操作的元素本身,可以省略ref */}
            <input ref={this.myRef2} onBlur={this.showData2} type="text" placeholder="失去焦點提示資料" /> &nbsp;
          </div>
        )
      }
    }
    //渲染元件
    ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>

效果如下:

1.4.7 事件處理

  • 通過onXxx屬性指定事件處理常式(注意大小寫)
  • React使用的是自定義(合成)事件, 而不是使用的原生DOM事件-------為了更好的相容性
  • React中的事件是通過事件委託方式處理的(委託給元件最外層的元素)--------為了高效

通過event.target得到發生事件的DOM元素物件------不要過度使用ref

<script type="text/babel">
    //  建立一個元件 
    class Demo extends React.Component {
      showInfo = () => {
        alert(this.myRef.current.value)
      }
      showData = (event) => {
        // 傳入的event是發生onBlur事件的事件源,也就是失去焦點提示資料的input框,
        // 通過event.target.value拿到input中的值
        alert(event.target.value)
      }
      render() {
        return (
          <div>
            <input ref={this.myRef = React.createRef()} type="text" placeholder="點選按鈕提示資料" />&nbsp;
            <button onClick={this.showInfo}>點我提示左側資料</button>&nbsp;
            <input onBlur={this.showData} type="text" placeholder="失去焦點提示資料" />
          </div>
        )
      }
    }

    // 渲染元件到頁面
    ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>

1.5 收集表單資料

1.5.1 理解

包含表單的元件分類

  • 受控元件
  • 非受控元件

1.5.2 案例

需求: 定義一個包含表單的元件

輸入使用者名稱密碼後, 點選登入提示輸入資訊

效果如下:

Ⅰ.非受控元件

對input(以及頁面中其他的一些輸入型DOM)中輸入的資料現用現取,就是非受控元件

<script type="text/babel">
    // 建立一個元件
    class Login extends React.Component {
      // handleSubmit函數中直接取出input中輸入的資料(現用現取)
      handleSubmit = (event) => {
        event.preventDefault()     //阻止表單提交
        const { username, password } = this
        alert(`你輸入的使用者名稱是: ${username.value}, 你輸入的密碼是: ${password.value}`)
      }
      render() {
        return (
          // 對input(以及其他的一些輸入型DOM)中輸入的資料現用現取,就是非受控元件
          <form onSubmit={this.handleSubmit}>
            使用者名稱:<input ref={(c) => { this.username = c }} type="text" name="username" />
            密碼:<input ref={(c) => { this.password = c }} type="password" name="password" />
            {/* form中的button預設提交,點選後觸發表單提交事件onSubmit */}
            <button>登入</button>
          </form>
        )
      }
    }
    // 渲染元件到頁面
    ReactDOM.render(<Login />, document.getElementById('test'))
  </script>

Ⅱ.受控元件

頁面中所有輸入類的DOM(input框),隨著使用者的輸入,能把輸入的東西維護到狀態裡(state),等需要用時,直接從狀態裡面取出來,就是受控元件。(類似於vue裡的雙向繫結

<script type="text/babel">
    // 建立一個元件
    class Login extends React.Component {
      // 初始化狀態
      state = { username: '', password: '' }

      // 儲存使用者名稱到狀態中
      saveUsername = (event) => {
        this.setState({ username: event.target.value })
      }

      // 儲存密碼到狀態中
      savePassword = (event) => {
        this.setState({ password: event.target.value })
      }

      // 表單提交的回撥
      handleSubmit = (event) => {
        event.preventDefault()     //阻止表單提交
        const { username, password } = this.state
        alert(`你輸入的使用者名稱是: ${username}, 你輸入的密碼是: ${password}`)
      }

      /* 
      受控元件:(能夠省略ref)
      頁面中所有輸入類的DOM(input框),隨著使用者的輸入,能把輸入的東西維護到狀態裡(state),等需要用時,直接從狀態裡面取出來

      資料被onChange函數監聽,只要輸入型DOM資料一改變就觸發onChange中指定的回撥函數
      回撥函數saveUsername和savePassword中對改變的資料進行儲存,儲存到state中,需要使用的時候才取出。
      */
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            使用者名稱:<input onChange={this.saveUsername} type="text" name="username" />
            密碼:<input onChange={this.savePassword} type="password" name="password" />
            <button>登入</button>
          </form>
        )
      }
    }
    // 渲染元件到頁面
    ReactDOM.render(<Login />, document.getElementById('test'))
  </script>

到此這篇關於React元件三大屬性之state,props,refs的文章就介紹到這了,更多相關React元件屬性內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com