說說React組件的State

說說React組件的State

React的核心思想是組件化的思想,應用由組件搭建而成, 而組件中最重要的概念是State(狀態)。react

正肯定義State

React把組件當作一個狀態機。經過與用戶的交互,實現不一樣狀態,而後渲染UI,讓用戶界面和數據保持一致。組件的任何UI改變,均可以從State的變化中反映出來;State中的全部狀態都用於反映UI的變化,不該有多餘狀態。git

那麼什麼樣的變量應該作爲組件的State呢:github

  1. 能夠經過props從父組件中獲取的變量不該該作爲組件State。
  2. 這個變量若是在組件的整個生命週期中都保持不變就不該該做爲組件State。
  3. 經過其餘狀態(State)或者屬性(Props)計算獲得的變量不該該做爲組件State。
  4. 沒有在組件的render方法中使用的變量不用於UI的渲染,那麼這個變量不該該做爲組件的State 。這種狀況下,這個變量更適合定義爲組件的一個普通屬性。編程

    React中的immutability

    React官方建議把State當作是不可變對象,State中包含的全部狀態都應該是不可變對象,當State中的某個狀態發生變化,咱們應該從新建立這個狀態對象,而不是直接修改原來的狀態。State根據狀態類型能夠分爲三種。
  5. 數字,字符串,布爾值,null,undefined這五種不可變類型。數組

由於其自己就是不可變的,若是要修改狀態的話,直接賦新值就能夠,例如:
this.setState({ num: 1, string: 'hello', ready: true });
二、數組類型less

js中數組類型爲可變類型。假若有一個state是數組類型,例如students。修改students的狀態應該保證不會修改原來的狀態,
例如新增一個數組元素,應使用數組的concat方法或ES6的數組擴展語法。
```
let students = this.state.students;
this.setState({
students: students.concat(['xiaoming'])
});函數式編程

//或者
this.setState(preState => ({
students: [ ...preState.books, 'xiaogang']
});
```
從數組中截取部分做爲新狀態時,應使用slice方法;當從數組中過濾部分元素後,做爲新狀態時,使用filter方法。不該該使用push、pop、shift、unshift、splice等方法修改數組類型的狀態,由於這些方法都是在原數組的基礎上修改的。應當使用不會修改原數組而返回一個新數組的方法,例如concat、slice、filter等。函數

  1. 普通對象

對象也是可變類型,修改對象類型的狀態時,應該保證不會修改原來的狀態。可使用ES6的Object.assign方法或者對象擴展語法。組件化

//Object.assign方法
this.setState(preState => ({
  school: Object.assign({}, preState.school, {classNum: 10})
}));

//對象擴展語法
let school = this.state.school;
this.setState({
  school: { ...school, { classNum: 10 } }
})

不一樣方式建立的組件中的State

  1. 無狀態組件(Stateless Functional Component)

這種組件自身沒有狀態,不須要管理state狀態,全部數據都是從props傳入。性能

const Teacher = ({
  name,
  age
}) => {
  return (
    <div>Teacher {name} is {age} years old.</div>
  )
}

相同的輸入(props)必然有相同的輸出,所以這種組件能夠寫成無反作用的純函數,且適合函數式編程(函數的compose,curring等組合方式)

  1. 純組件(PureComponent)

咱們知道,當組件的props或者state發生變化的時候React會對組件當前的Props和State分別與nextProps和nextState進行比較,當發現變化時,就會對當前組件以及子組件進行從新渲染,不然就不渲染。有時候咱們會使用shouldUpdateComponent來避免沒必要要的渲染。固然有時候這種簡單的判斷,顯得有些多餘和樣板化,因而react就提供了PureComponent來自動幫咱們完成這件事,簡化了咱們的代碼,提升了性能。例如:

class CounterButton extends React.pureComponent {
  constructor(props) {
    super(props)
    this.state = { count: 1 };
  }
  render() {
    return (
      <button 
        color={this.props.color}
        onClick={() => this.setState(state = > ({count: state.count + 1}))}
      >
       Count: {this.state.coount}
      </button>
    )
  }
}

在上例中,雖然沒有添加shouldUpdateComponent代碼,可是react自動完成了props和state的比較,當props和state沒有發生變化時不會對組件從新渲染。可是PureComponent的自動爲咱們添加的shouldComponentUpate函數,只是對props和state進行淺比較(shadowcomparison),當props或者state自己是嵌套對象或數組等時,淺比較並不能獲得預期的結果,這會致使實際的props和state發生了變化,但組件卻沒有更新的問題。

淺比較:比較 Object.keys(state | props) 的長度是否一致,每個 key 是否二者都有,而且是不是一個引用,也就是隻比較了第一層的值,確實很淺,因此深層的嵌套數據是對比不出來的。

例如

class Ul extends PureComponent {
  constructor(props) {
    super(props)
    this.state = { 
      items: [1, 2, 3] 
    };
  }
  handleClick = () => {
    let { items } = this.state;
    items.push(4);
    this.setState({ items });
  }
  render() {
    return (
    <div>
      <ul>
        {this.state.items.map(i => <li key={i}>{i}</li>)}
      </ul>
      <button onClick={this.handleClick}>add</button>
    </div>)
  }
}

會發現,不管怎麼點 add 按鈕, li 都不會變多,由於 pop方法是在原數組上進行的修改,items的preState與nextState 用的是一個引用, shallowEqual 的結果爲 true 。改正:

handleClick = () => {
  let { items } = this.state;
  this.setState({ items: items.concat([4]) });
}

這樣每次改變都會產生一個新的數組,items的preState與nextState 用的是不一樣引用, shallowEqual 的結果爲 false,也就能夠 render 了。
在PureComponent中要避免可變對象做爲props和state,你能夠考慮使用Immutable.js來建立不可變對象,Immutable Data就是一旦建立,就不能再被更改
的數據。對 Immutable 對象的任何修改或添加刪除操做都會返回一個新的 Immutable 對象。從而避免出現props和state發生改變而組件沒有從新渲染的問題。

  1. Component

與PureComponent不一樣的是,Component須要開發者顯示定義shouldUpdateComponent且定製性更強。對於一些不管怎麼修改都不該該讓組件從新渲染的props就沒必要在shouldUpdateComponent中進行比較。同PureComponent同樣Component中的state也應該是不可變對象。

使用Object.assign或者concat等方法避免修改原來的對象或數組是經過將屬性/元素從一個對象/數組複製到另外一個來工做,這種拷貝方式只是淺拷貝。Object.assign()方法實行的就是淺拷貝,而不是深拷貝。也就是說源對象某個屬性的值是對象,那麼目標對象拷貝獲得的是這個對象的引用。例若有school狀態:

this.state = {
  school: {
    classOne: {
      ...
    },
    classTwo: {
      ...
    }
  }
}

經過Object.assign修改狀態

let classOne = {
  teacher: [...],
  students: [...],
};
this.setState(preState => ({
  school: Object.assign({}, preState.school, {classOne: classOne})
}));

上面代碼中Object.assign拷貝的只是classOne對象的引用,任何對classOne的改變都會反映到React的State中。深拷貝並非簡單的複製引用,而是在堆中從新分配內存,而且把源對象實例的全部屬性都新建複製,以保證複製的對象的引用不指向任何原有對象上或其屬性內的任何對象,複製後的對象與原來的對象是徹底隔離的(關於深拷貝可參考這篇文章)。
深拷貝一般須要把整個對象遞歸的複製一份,十分影響性能。對於大型對象/數組來講,操做比較慢。當應用複雜後,props和state也變得複雜和龐大,經過淺拷貝和深拷貝就會影響性能和形成內存的浪費。且對象和數組默認是可變的,沒有什麼能夠確保state是不可變對象,你必須時刻記住要使用這些方法。

使用immutable.js能夠很好的解決這些問題。Immutable.js 的基本原則是對於不變的對象返回相同的引用,而對於變化的對象,返回新的引用。同時爲了不 deepCopy 把全部節點都複製一遍帶來的性能損耗,Immutable 使用了 Structural Sharing(結構共享),即若是對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。(關於immutab.js能夠看這篇文章或者immutable.js)

總結

正確的定義state不只便於狀態的管理與調試,並且在複雜應用中保持state的簡潔,在組件更新時能減小比較次數,提升性能。保證state的不可變性不只保證數據更容易追蹤、推導,並且能避免組件更新時shouldComponent出現狀態比較錯誤。

相關文章
相關標籤/搜索