React學習(6)-React中組件的數據-state

React學習(6)-React組件中的數據-state.png

前言

  • 組件中的state具體是什麼?怎麼更改state的數據?
  • setState函數分別接收對象以及函數有什麼區別?
  • 如何劃分組件的狀態數據,進行自個人靈魂拷問,以及props與state的靈魂對比

那麼本節就是你想要知道的javascript

若是想閱讀體驗更好,可戳連接-React學習(6)-React組件中的數據,內有視頻css

React中的state

一個組件最終渲染的數據結果,除了prop還有state,state表明的是當前組件的內部狀態,你能夠把組件當作一個'狀態機",它是可以隨着時間變化的數據,更多的是應當在實現交互時使用,根據狀態state的改變呈現不一樣的UI展現java

在React中,由於不能直接修改外部組件傳入的prop值react

當須要記錄組件自身數據變化時,想要使組件具有交互的能力,那麼須要有觸發該組件基礎數據模型改變的能力,那麼此時就須要使用state算法

一旦組件的狀態(數據)發生更改,組件就會自動的調用render函數從新渲染UI,更改這個state狀態是經過this.setState方法來觸發實現的編程

下面咱們從一個簡單的點擊按鈕,顯示和隱藏的效果開始: 效果以下所示:數組

連續點擊按鈕,上方的itclanCoder文本在顯示和隱藏進行切換,當狀態爲true時,itclanCoder文本顯示,狀態爲false時,itclanCoder文本隱藏,注意控制檯調試器
state狀態的切換.gif
具體代碼以下所示:瀏覽器

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';

class Button extends Component {
  constructor(props){
  super(props);

  // 初始化state
  this.state = {
    isShow: true
  }
}

render(){
return(
   <div style = {{ textAlign: "center"}}>
     <div className = { this.state.isShow?"showText":"hiddenText" }>itclanCoder</div>
     <div>{ String(this.state.isShow) }</div>
     <div><button onClick = { this.handleBtnClick }>點擊按鈕切換文本狀態</button></div>
</div>
);
}

handleBtnClick = () => {
  this.setState({
    isShow: !this.state.isShow
  });
}
}

const container = document.getElementById('root');

ReactDOM.render(<Button />, container);

經過上面的代碼,能夠看出初始化state數據,通常在組件的構造器結尾處進行編寫服務器

在上面的Button組件內,經過對this.state的賦值,完成了對該Button組件內部state的初始化微信

注意:

  • this.state放置的位置:應當放在構造器函數內進行使用的,不然是會報錯的
  • 初始化該組件當前狀態的state值必須是一個javascript對象,不能是string,或者number,boolean等簡單的基本數據類型
  • 即便你想要存儲一個只是數字類型的數據,也只能把它存做state對象下的某個字段對應的值中,這個state能夠看作是組件自身提供的一個固定的對象,用於存儲當前組件自身的狀態,它是私有的對象,而且徹底只受控於當前組件

在以上代碼中,經過給button按鈕監聽綁定onClick屬性掛載點擊事件處理函數(上面是handleBtnClick),來達到控制組件state中的isShow這個狀態,從而讓文本顯示仍是隱藏

顯示和隱藏是經過添加class層疊樣式進行設置,可是控制這個行爲切換動做的,倒是js

這裏用的是箭頭函數,若是不用此方法,必定要記得用bind進行this壞境的綁定

在代碼中,經過this.state能夠讀取當前組件狀態的state,可是想要改變state的狀態,並非直接經過this.state進行更改,而是經過React內置提供的一個setState方法進行觸發的

爲了解釋不能直接更改this.state,咱們來看另外一個加減數字的例子,代碼以下所示

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import "./index.css";


class Count extends Component {
constructor(props){
super(props);

this.state = {
   count: 0
}

  // this壞境的綁定
  this.handleBtnIncrease = this.handleBtnIncrease.bind(this);
  this.handleBtnReduce = this.handleBtnReduce.bind(this);
}

render(){
return (
<Fragment>
  <div style = {{textAlign: "center"}}>
    <button onClick = { this.handleBtnReduce }>-</button>
    <span className = "text">{ this.state.count}</span>
  <button onClick = { this.handleBtnIncrease }>+</button>
</div>
</Fragment>
);
}

  handleBtnReduce() {
    this.setState({
      count: this.state.count-1
    });
  }

  handleBtnIncrease() {
   // 嘗試直接更改this.state的值,這樣是有問題的
   this.state.count = this.state.count+1;
  }
}

const container = document.getElementById('root');

ReactDOM.render(<Count />, container);

當你點擊加按鈕的時候,頁面不會有任何反應,打開控制檯,會有一個警告提示 不要直接的更改state的值,當你在點擊減號時,你會發現計數發生階躍性變化,好比初始計數值是0的狀況下,在你連續點擊加按鈕三次時,計數值沒有發生任何變化

可是當你點擊減號時計數值就會變成2,這個就很是詭異了,效果以下所示
直接修改this.state會產生詭異的bug.gif
直接修改this.state的值,雖然改變了組件的內部狀態,可是並無驅動組件進行從新渲染,既然組件沒有從新渲染,頁面上的UI這個this.state固然不會有任何變化

可是React中的setState方法卻可以觸發頁面的渲染,它能夠接收一個對象或者函數

正確的寫法應當是:利用setState進行對組件state的更改

handleBtnIncrease() {
  this.setState({
    count: this.state.count+1;
  });
}

React中setState要知道的

定義: setState方法是ReactReact.Component組件所提供的一個內置的方法,當你調用這個setState方法的時候,React會更新組件的狀態state,而且從新調用render方法,最終實現當前組件內部state的更新,從而最新的內容也會渲染到頁面上

做用:修改組件的內部state的狀態,每每用於更新用戶界面以響應事件處理器和處理服務器數據的主要方式

參數:setState函數接收參數有兩種方式,一個是對象,另外一個是函數

注意事項

  • 不能直接修改state,它並不會從新渲染組件,以下所示

// 錯誤的寫法 this.state.xxx = "新的值"
this.state.count = this.state.count+1;

應該使用`setState()`函數去更新當前組件的狀態

<!--this.setState({-->
<!-- xxx: "新的值" -->
<!--});-->
this.setState({
count: this.state.count+1
})

通常而言,**經過在React中封裝的事件,例如:onChange,onClick,onKeyDown,onFocus,onBlur等這些事件類型裏面綁定事件方法內的setState都是異步的**

有時候,this.props和this.state可能會異步更新,在調用setState以後,並不會立馬更新組件

其實它是會批量延遲更新

**也就是props,state的值並不會立馬的映射更新,它是把這個state對象放到一個更新隊列裏面,而後從隊列當中把新的狀態提出來合併到state中,最後在觸發render函數組件的更新,從而致使UI界面的改變**

你不能依賴它來更新下一個狀態

對於SetState何時同步何時異步?**若是是React控制的事件處理程序以及在它的鉤子(生命週期)函數內調用setState,它不會同步的更新state**

也就是說:React控制以外的事件調用setState是同步更新的,例如原生js綁定的事件,setTimeout/setInterval等,固然在React中絕大多數都是異步處理的

對於實現同步,咱們能夠看一下下面這個代碼,先看下效果:點擊減號(-)按鈕,頁面上count變化與控制檯上的值的對應關係,點擊加(+)按鈕與另加按鈕,觀看控制檯也頁面UI效果
![setState異步.gif](https://upload-images.jianshu.io/upload_images/5356153-0dce0b0b02097759.gif?imageMogr2/auto-orient/strip)

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import "./index.css";

class Count extends Component {
constructor(props){

super(props);

this.state = {
  count: 10
}

// this壞境的綁定
this.handleBtnIncrease = this.handleBtnIncrease.bind(this);
this.handleBtnReduce = this.handleBtnReduce.bind(this);

}

render(){

return (
<Fragment>

<div style = {{textAlign: "center"}}>
  <button onClick = { this.handleBtnReduce }>-</button>
  <span className = "text">{ this.state.count}</span>
  <button onClick = { this.handleBtnIncrease }>+</button>
  <button id="btn-add">另加</button>
</div>

</Fragment>
);
}
// 經過React綁定監聽的onClick事件類型綁定的方法內的setState方法都是異步的
handleBtnReduce() {
this.setState({

count: this.state.count-1

});
console.log("點擊減-count值",this.state.count);
}

handleBtnIncrease() {
setTimeout(() => {
this.setState({

count: this.state.count+1

});
console.log("點擊加-count的值", this.state.count);
},10);

}

// 非React綁定的事件類型方法內調用的setState,是同步的
componentDidMount() {
const btnAdd = document.getElementById('btn-add');
btnAdd.addEventListener('click', () => {

this.setState({
   count: this.state.count+1
 });
console.log(this.state.count);

});
}
}

const container = document.getElementById('root');

ReactDOM.render(<Count />, container);

以上經過setTimeout/setInterval等addEventListener,以js的事件綁定方式內調用setState方法,此時,state的值將是同步更新的

若是要追究setState內部執行過程,其實它是很複雜的,包括了更新state,以及各個生命週期函數,之後有時間單獨在詳聊的

在這裏,你只須要只知道,**對於在React中的JSX綁定的事件處理函數中調用setState方法是異步的就能夠了**

**若是你須要基於當前的state來計算出新的值,那麼setState函數就應該傳遞一個函數,而不是一個對象,它能夠確保每次調用的都是使用最新的state**,這一點正是取決因而否傳對象和函數的區別

**多個setState調用會合並處理**

當在事件處理方法內屢次調用setState方法時,render函數只會執行一次,並不會致使組件的重複渲染,由於React會將多個this.setState產生的修改放在一個隊列裏面進行批量延遲處理,因此從這點上講,React設計這個setState函數是很是高效的,結合了函數式編程,不用考慮性能的問題

以下代碼所示: 在事件處理程序內調用setState方法改變state的值,雖然是兩次調用可是並不會引發render函數的重複渲染,它會合併成到一個隊列中執行一次操做,只有state或者props發生改變時,它纔會引發render函數的從新渲染

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import "./index.css";

class ChangeText extends Component {
constructor(props){

super(props);

this.state = {
   desc: "歡迎關注微信itclanCoder公衆號",
   isStatus: true
}

// this壞境的綁定
this.handleChangeText = this.handleChangeText.bind(this);
}

render(){
console.log("render變化了");
const name = this.state.isStatus? this.props.name:"隨筆川跡";
const age = this.state.isStatus? this.props.age: 20;
return (

<Fragment>
   <div style = {{textAlign: "center"}}>
     <div>{ this.state.desc }</div>
     <div>{ name },永遠的{ age }歲</div>
     <button onClick = { this.handleChangeText }>點擊按鈕改變上方文字</button>
   </div>

</Fragment>
);
}

handleChangeText() {
this.setState((prevState, newProps) => ({

isStatus: !prevState.isStatus

}));

this.setState({

desc: "學習React",

});

}

}

ChangeText.defaultProps = {
name: "川川",
age: 25

}

ChangeText.propTypes = {
name: PropTypes.string,
age: PropTypes.number
}

const container = document.getElementById('root');

ReactDOM.render(<ChangeText name="川川,一個帥小夥" age={ 18 } />, container);

刷新瀏覽器,查看render函數執行的次數,當點擊按鈕時,只要state和props發生了改變,render函數就會從新渲染
![屢次調用setState方法只會執行一次.gif](https://upload-images.jianshu.io/upload_images/5356153-dee2c3c09d628f6d.gif?imageMogr2/auto-orient/strip)
從上面的代碼中,在事件處理**函數中調用setState方法時,當setState函數傳遞的是一個函數時,這個函數接收兩個形參數,第一個參數prevState(參數名任意),是先前組件狀態時的state,然後一個參數newProps(形參名任意)是這次更新被應用時的props,它不是必傳的,具體視狀況而定**

直到如今,知道給setState函數傳遞一個對象與傳遞一個函數的區別是什麼?

**傳遞一個函數可讓你在函數內訪問到當前的state的值**,由於setState的調用是異步的,this.state.以及this.props不會當即更新,它會被放置到一個隊列中延遲合併處理

只有當state和props數據發生改變時,render函數纔會從新渲染

因此你是能夠鏈式的進行更新,並確保它們是創建在另外一個之上的,這樣不會發生衝突

這也正是setState函數傳遞一個函數的緣由,絕大多數時候,最優的方式是,你傳遞一個函數給setState就能夠了,並給該函數傳遞兩個形參(state,prop),而後經過當中的形參來更新state就能夠避免詭異的bug了

**小結一下**:

**setState函數是用於更新當前組件的狀態的,不只能夠更改props也能夠更改state**

它接收兩種參數形式,一個是對象,另外一個是函數

**當須要基於當前的state計算出新的值進行處理,給setState函數應該傳遞一個函數而不是對象,這樣能夠保證每次調用的狀態值都是最新的**

至於爲何React不選擇同步更新this.state

這是由於React是有意這麼設計的,作異步等待,在constructor構造器函數執行完後,在執行render函數,直到全部組件的事件處理函數內調用setState函數完成以後,避免沒必要要的從新渲染來提高性能

你能夠能會想,React不能對this.state進行立馬更新,而不對組件進行從新渲染呢

*   若是this.state能當即更新改變,就會破壞組件的協調,只有當props或者state發生改變時,React經過將最新返回的JSX元素與原先的元素進行對比(diff算法),來決定是否有必要進行一次DOM節點的更新,若是先後JSX元素不相等,那麼React纔會更新DOM
    
*   若是props或者state能被直接被修改,將會破壞組件複用的原則,會出現一些莫名其妙的bug

>### 如何劃分組件的狀態數據

不管是props仍是state都是組件的數據,影響組件最終的UI展現,究竟怎麼樣進行區分,哪一個組件應該擁有某個state狀態,進行設置,有時候,它們是很是模糊的概念

可是在React中應該遵循一些原則:

讓組件儘量的少狀態

若是該組件只是用於UI渲染,數組展現,並沒有複雜的頁面邏輯交互,那麼應該讓組件的數據定義成props,經過外部組件傳入,而並不是將數據設置到狀態當中去

那麼究竟什麼樣的數據屬性能夠視爲狀態?

**狀態(state)應該是會隨着時間產生變化的數據,當更改這個狀態(state),須要更新組件的UI,就能夠將它定義成state,更可能是在實現頁面的交互時使用的**

另外一種程度上講,**在寫靜態,沒有任何交互頁面時,不該該用state定義當前組件的狀態用來填充頁面**

而應該能用外部世界(組件)傳來的數據,就用外部組件傳來的props進行數據的填充

下面的這些就不是狀態(`state`),不該該定義成`state`,**如何斷定該用props仍是state,能夠進行自個人」靈魂拷問「**

* 該數據是否由父組件(外部世界)經過props傳遞給子組件而來的?若是是,那麼它就不是state

* 經過state或者props能夠計算出的數據:好比一個數組的長度等,那麼它就不是state

* 它是否隨着時間的變化而保持不變?若是不改變,那麼它也不該該是state:例如:某些頁面固定的標題,字段

* 與props重複的數據,除非這個數據後期是須要作變動的

而針對這種**無狀態的組件(UI組件/函數式組件)**

能夠用純粹的函數來定義,**所謂純函數,只有輸入和輸出,無狀態,無生命週期鉤子函數**,只是用做於接收父組件傳來的props值渲染生成DOM結構,無交互,無邏輯層的數據展現

無狀態(函數式)組件,在性能上是最高效的,開銷很低,由於沒有那些生命週期函數嘛

就是一普普統統的函數,執行效率是很高的

UI = render(data)

還記得上次提過上面的公式?React組件扮演的角色應該就是一個純函數(UI組件),它是沒有任何反作用的,因爲組件的複用性原則,是不能直接修改props的值的

**若是該組件只用於作數據層展現,無需添加生命週期函數等,就能夠毫無懸念的使用無狀態組件去定義**,固然用箭頭函數也是能夠的,它就是普通函數一簡寫的替換,可是要注意,箭頭函數沒有this的綁定

const Header = (props) => {
return (
<div>Hello, {props.content}</div>
);
}
const container = document.getElementById('root')
ReactDOM.render(<Header content="itclanCoder">, container)

> ### props與state的靈魂對比

**共同點**:

**共同點**:

都是組件內的數據,是一普通的javascript對象,都是用來保存信息的,這些信息能夠控制組件的形態

**不一樣點**:

*   props是由父組件傳入的(相似形參),用於定義外部組件的接口,是React組件的輸入,它是從父組件傳遞給子組件的數據對象,在父(外部)組件JSX元素上,以自定義屬性的形式定義,傳遞給當前組件,而在子組件內部,則以this.props或者props進行獲取
    
*   props只具有讀的能力,不能直接被修改,若是想要修改某些值,用來響應用戶的輸入或者輸出響應,能夠借用React內提供的setState函數進行觸發,並用state來做爲替代
    
*   state是當前組件的內部狀態,它的做用範圍只侷限於當前組件,它是當前組件的一個私有變量.用於記錄組件內部狀態的,若是組件中的一些數據在某些時刻發生變化,或者作一些頁面邏輯交互時,須要更新UI,這個時候就須要使用state來跟蹤狀態(例如控制一元素的顯示隱藏來回切換等狀態),它由組件自己管理,能夠經過setState函數修改state

### 總結

本文主要講述了React組件中的數據屬性-state,它是組件內部的狀態,是一私有的變量,用於記錄組件內部狀態,因爲props不可修改,經過React中內置提供setState方法修改state的值,而且定義state時,它只能是一個對象,用於存儲組件內部特殊的狀態

而且大篇幅的講到setState這個函數須要知道的,可接收兩種類型的參數,一個是對象,另外一個是函數,以及這兩種方式的區別,如何劃分組件的狀態數據,原則上是儘量的減小組件的狀態。以及最後的props與state的靈魂對比

雖然能夠簡單的用幾句話歸納props與state的做用,可是理解它們是很是重要的,每每程序的bug,就是經過props和state進行追蹤查案的線索,是否經得起自個人靈魂拷問,我以爲至今我也在摸索..

可以以props和state這種形式順藤摸瓜,尋本溯源到頁面上任何一個UI組件,這種React的能力能夠說很是重要了
相關文章
相關標籤/搜索