目錄:
1. 如何定義 State
2. 如何修改 State
3. State 的不可變原則
React 的核心思想是組件化的思想,應用由組件搭建而成。組件中最重要的概念是 State,State 是組件的 UI 數據模型,是組件渲染時的一個數據依據。
State 代表組件 UI 狀態的變化。
在 React 應用中,當涉及到組件 UI 變化的時候,必須要將變量定義成 State 值。
定義 State 值的方式可以是在 constructor 中初始化 state值。
例 子
父組件是 App.js,子組件是 listItem.jsx。目前在頁面上點擊“+”“-”按鈕可以改變 count 的值,但是 UI 不會變化。

import React, { Component } from 'react'; import ListItem from './components/listItem' const listData = [ { id: 1, name: '紅蘋果', price: 2 }, { id: 2, name: '青蘋果', price: 3 }, ] class App extends Component { renderList(){ return listData.map( item => { return( <ListItem key={item.id} data={ item } onDelete={this.handleDelete} /> ) }) } handleDelete = (id) => { console.log( 'id:', id ); } render() { return( <div className="container"> { listData.length === 0 && <div className="text-center">購物車是空的</div> } { this.renderList() } </div> ) } } export default App;

import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); let count = 0; class ListItem extends Component { doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease = () => { count --; this.doSomethingWithCount(); console.log( count ); } handleIncrease = () => { count ++; console.log( count ); } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${count ? '' : '-s'}`}> <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> <div className="col-1 themed-grid-col"> <button onClick={()=>{this.props.onDelete(this.props.data.id)}} className="btn btn-danger btn-sm" type="button" >刪除 </button> </div> </div> ); } } export default ListItem;
頁面表現:
為了讓 count 在 UI 上發生變化,在 listItem.jsx 中定義 State。在 constructor 里使用 this.state,賦給 this.state 一個對象,並將變量 count 寫到對象里。
引用 state 的時候,不能直接使用 count ,而是應該使用 this.state.count 去引用。
例子
下面代碼演示如何將變量 count 定義為 state。
( 因為在幾個函數里對 count 的修改方式錯誤,所以代碼會報錯,在筆記下面有修改 state 的部分。)

import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); class ListItem extends Component { constructor(props){ super(props) this.state = { count : 0 } } doSomethingWithCount(){ if(count<0){ count = 0 } } handleDecrease = () => { count --; this.doSomethingWithCount(); console.log( count ); } handleIncrease = () => { count ++; console.log( count ); } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}> <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{this.state.count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> <div className="col-1 themed-grid-col"> <button onClick={()=>{this.props.onDelete(this.props.data.id)}} className="btn btn-danger btn-sm" type="button" >刪除 </button> </div> </div> ); } }
怎么樣定義需要一個 State
定義 State 值之前需要做一些思考。
第一、判斷這個狀態是否需要通過 props 從父組件獲取,從父組件中獲取並且在整個組件中不發生變化,如果是這樣的話,可能就不需要去定義 state。第二、判斷是否可以通過其它 state 和 props 計算得到,如果可以,可能就不需要去定義 state。第三、判斷是否在 render 方法中使用,如果不在 render 方法中使用,可能就不需要去定義 state。
比如上面的例子,將 count 定義成 state,是因為 count 不能從父組件中獲取,用戶的交互使 count 發生變化,並且在 render 方法中會使用到 count。
下面說明如何修改 state 的值,從而改變 UI 的狀態。
例 子
在 “-”、“+” 的 button 元素上添加了點擊事件,在事件回調函數中對變量 count 進行操作。如果 count 是 state 值,只能通過 setState 方法對 count 進行修改。在本例中,給 setState 方法賦一個對象作為參數,在對象中修改 count 的值。

import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); class ListItem extends Component { constructor(props){ super(props) this.state = { count : 0 } } handleDecrease = () => { this.setState({ count : this.state.count - 1 }) } handleIncrease = () => { this.setState({ count : this.state.count + 1 }) } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}> <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{this.state.count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> <div className="col-1 themed-grid-col"> <button onClick={()=>{this.props.onDelete(this.props.data.id)}} className="btn btn-danger btn-sm" type="button" >刪除 </button> </div> </div> ); } } export default ListItem;
頁面表現:
初學者可能會犯的錯誤
錯誤1. 通過類似“ this.state.count -- “ 這樣的方式修改 state 值,這完全沒有作用。
錯誤2. 在 constructor 中使用 setState 方法進行狀態改變,這會報錯。
這是因為 React 執行 setState 的時候會優化 setState 執行的時機,有可能會因為性能優化將多個 setState 合並在一起去執行,所以 setState 不是同步執行的。所以,不要依賴當前的 state 去計算另外一個 state ,如果必須要拿到修改完成之后的 state 可以通過回調函數的方式去實現。
例 子
點擊 “ + ” 按鈕時,會調用 handleIncrease 函數,在函數中使用 setState 方法修改 count ,讓 count 加 1。為了演示 setState 是異步的,在使用 setState 修改 count 前后添加標記,輸出 count。

import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); class ListItem extends Component { constructor(props){ super(props) this.state = { count : 0 } } handleDecrease = () => { this.setState({ count : this.state.count - 1 }) } handleIncrease = () => { console.log('step 1', this.state.count); this.setState({ count : this.state.count + 1 }) console.log('step 2', this.state.count); } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}> <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{this.state.count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> <div className="col-1 themed-grid-col"> <button onClick={()=>{this.props.onDelete(this.props.data.id)}} className="btn btn-danger btn-sm" type="button" >刪除 </button> </div> </div> ); } } export default ListItem;
頁面表現:
點擊按鈕在控制台輸出的 count 均為 0 ,說明 setState 是異步的,因為如果不是異步的話 step 2 輸出的 count 應該是 1。
如果必須要使用變化后的 state 值進行下一步的操作,可以給 setState 方法傳入第二個參數,可以傳入一個回調函數,這個回調函數執行的時機就是 setState 已經執行完成的一個時機。
例 子

import React, { Component } from 'react'; import style from './listItem.module.css'; import classnames from 'classnames/bind' const cls = classnames.bind(style); class ListItem extends Component { constructor(props){ super(props) this.state = { count : 0 } } handleDecrease = () => { this.setState({ count : this.state.count - 1 }) } handleIncrease = () => { console.log('step 1', this.state.count); this.setState({ count : this.state.count + 1 }, ()=>{ console.log('step 3', this.state.count); }) console.log('step 2', this.state.count); } render() { return ( <div className="row mb-3"> <div className="col-6 themed-grid-col"> <span className={ cls('title', 'list-title') }> {this.props.data.name} </span> </div> <div className="col-1 themed-grid-col">¥{this.props.data.price}</div> <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}> <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button> <span className={ cls('digital') }>{this.state.count}</span> <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button> </div> <div className="col-1 themed-grid-col"> <button onClick={()=>{this.props.onDelete(this.props.data.id)}} className="btn btn-danger btn-sm" type="button" >刪除 </button> </div> </div> ); } } export default ListItem;
頁面表現:
點擊按鈕之后,可以看到 step 3 輸出的 count 值加了 1。
3. state 的更新是一個淺合並(shallow merge)的過程
例 子
如果 state 有多個屬性,比如說有 count、price。在只需要修改 count 而不需要修改 price 時,只需要給 setState 傳入 count 即可,不需要傳入整個 state。
在創建新狀態的時候要遵循一些原則。
當狀態發生變化的時候,根據狀態的類型可以分為以下三種情況:
1. 值類型 : 數字、字符串、布爾值、null、undefined
2. 數組類型
3. 對象
創建新的狀態
狀態是不可變類型,直接給要修改的狀態賦予新值即可。使用 setState 的時候可以直接修改 state 值,比如說數字、字符串、布爾值等。
有 2 種方法:① concat;② 使用“...”解構符去生成新數組
① concat
concat 方法用於合並兩個或多個數組,此方法不會更改原來的數組,而是會返回一個新數組。
注意,不要使用諸如 push 、pop、shift、unshift、spice 等方法,因為這些方法都是在原數組的基礎上修改的。可以使用 concat、slice、filter 這些方法,這些方法會返回一個新數組。
② 使用 “...” 解構符去生成新數組
使用 “...” 解構符去生成新數組原理跟使用 concat 是一樣的。生成 _books 新數組之后,使用 setState 將 _books 賦予給原來的狀態。
通常會使用 Object.assign 方法生成新對象,Object.assign 用於將所有可枚舉屬性的值從一個或多個對象復制到目標對象。
如果要進行深拷貝,還是要使用 JSON.stringify() 等方法。
① Object.assign
下面例子使用 Object.assign 合並對象 {}、this.state.item、{price:9000},將 this.state.item 跟 {price:9000} 合並到空對象 { } 中,從而去生成一個新對象。
② “...” 解構符
使用 “...” 解構符道理跟使用 Object.assign 是一樣的。
小結
創建新的狀態對象的關鍵是避免使用會修改原對象的方法,而是使用可以返回一個新對象的方法。推薦使用類似 Immutable 這樣的 JS 庫,可以方便地創建和管理不可變的數據。
1. 可變的
state 用於組件保存、控制、修改
2. 組件內部
state 在組件內部初始化,可以被組件自身修改,但是外部不能訪問也不能修改,所以,可以認為 state 是一個局部只能被組件自身控制的數據源
3. 交互或其他 UI 造成的數據更新
state 是交互或其他 UI 造成的數據更新。通常情況下,state 的這種變化會觸發組件的重新渲染。
1. 在組件內部不可變
props 是父組件傳入的參數對象,它是外部傳來的配置參數。
2. 父組傳入
在組件內部無法控制也無法修改,除非外部組件主動傳入新的 props,否則組件的 props 保持不變。
3. 簡單的數據流
可以把 props 看成是一個從上而下的一個簡單的數據流。
組件中 state 的數據可以通過 props 傳入子組件,反過來,組件也可以用外部傳入的 props 來初始化自己的 state。
state 是讓組件控制自己的狀態,而 props 是讓外部對組件進行配置。