ReactState(狀態):React通過this.state來訪問state,通過this.setState()方法來更新state
React State(狀態)
React 把元件看成是一個狀態機(State Machines)。通過與使用者的互動,實現不同狀態,然後渲染 UI,讓使用者介面和資料保持一致。
React 裡,只需更新元件的 state,然後根據新的 state 重新渲染使用者介面(不要操作 DOM)。
以下例項中建立了 LikeButton 元件,getInitialState 方法用於定義初始狀態,也就是一個物件,這個物件可以通過 this.state 屬性讀取。當使用者點選元件,導致狀態變化,this.setState 方法就修改狀態值,每次修改以後,自動呼叫 this.render 方法,再次渲染元件。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>菜鳥教程 React 例項</title>
<script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
<script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
<script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? `喜歡` : `不喜歡`;
return (
<p onClick={this.handleClick}>
你<b>{text}</b>我。點我切換狀態。
</p>
);
}
});
ReactDOM.render(
<LikeButton />,
document.getElementById(`example`)
);
</script>
</body>
</html>
我們都知道,React通過this.state來訪問state,通過this.setState()方法來更新state。當this.setState()方法被呼叫的時候,React會重新呼叫render方法來重新渲染UI
setState非同步更新
setState方法通過一個佇列機制實現state更新,當執行setState的時候,會將需要更新的state合併之後放入狀態佇列,而不會立即更新this.state(可以和瀏覽器的事件佇列類比)。如果我們不使用setState而是使用this.state.key來修改,將不會觸發元件的re-render。如果將this.state賦值給一個新的物件引用,那麼其他不在物件上的state將不會被放入狀態佇列中,當下次呼叫setState並對狀態佇列進行合併時,直接造成了state丟失。(這裡特別感謝@Dcatfly的指正)
我們來看一下React文件中對setState的說明
void setState(
function|object nextState,
[function callback]
)
The second (optional) parameter is a callback function that will be executed once setState is completed and the component is re-rendered.
翻譯一下,第二個引數是一個回撥函式,在setState的非同步操作結束並且元件已經重新渲染的時候執行。也就是說,我們可以通過這個回撥來拿到更新的state的值。
React也正是利用狀態佇列機制實現了setState的非同步更新,避免頻繁地重複更新state(pending的意思是未定的,即將發生的)
//將新的state合併到狀態更新佇列中
var nextState = this._processPendingState(nextProps, nextContext);
//根據更新佇列和shouldComponent的狀態來判斷是否需要更新元件
var shouldUpdate =
this._pendingForceUpdate ||
!inst.shouldComponentUpdate ||
inst.shouldComponentUpdate(nextProps, nextState, nextContext);
setState迴圈呼叫風險
當呼叫setState時,實際上會執行enqueueSetState方法,並對partialState以及_pending-StateQueue更新佇列進行合併操作,最終通過enqueueUpdate執行state更新
而performUpdateIfNecessary方法會獲取pendingElement, pendingStateQueue,_ pending-ForceUpdate,並呼叫receiveComponent和updateComponent方法進行元件更新
如果在shouldComponentUpdate或者componentWillUpdate方法中呼叫setState,此時this._pending-StateQueue != null,就會造成迴圈呼叫,使得瀏覽器記憶體佔滿後崩潰
呼叫棧
既然setState最終是通過enqueueUpdate執行state更新,那麼enqueueUpdate到底是如何更新state的呢? 首先看下面的問題
import React, { Component } from `react`;
class Example extends Component {
constructor(){
super();
//在元件初始化可以直接操作this.state
this.state = {
val: 0
}
}
componentDidMount(){
this.setState({
val: this.state.val + 1
});
//第一次輸出
console.log(this.state.val);
this.setState({
val: this.state.val + 1
});
//第二次輸出
console.log(this.state.val);
setTimeout(()=>{
this.setState({val: this.state.val + 1});
//第三次輸出
console.log(this.state.val);
this.setState({
val: this.state.val + 1
});
//第四次輸出
console.log(this.state.val);
}, 0);
}
render(){
return null;
}
}
上述程式碼中,4次console.log列印出來的val分別是: 0,0,2 ,3
我們來看一個簡化的setState的呼叫棧
this.setState(newState) =>
newState存入pending佇列 =>
呼叫enqueueUpdate =>
是否處於批量更新模式 =>
是的話將元件儲存到dirtyComponents
不是的話遍歷dirtyComponents,呼叫updateComponent,更新pending state or props
enqueueUpdate的原始碼如下(上面流程的第三步)(batching的意思是批量的)
function enqueueUpdate(component){
//injected注入的
ensureInjected();
//如果不處於批量更新模式
if(!batchingStrategy.isBatchingUpdates){
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
//如果處於批量更新模式
dirtyComponents.push(component);
}
如果isBatchingUpdates為true,則對所有佇列中的更新執行batchedUpdates方法,否則只把當前元件(即呼叫了setState的元件)放入dirtyComponents陣列中,例子中4次setState呼叫的表現之所以不同,這裡的邏輯判斷起了關鍵作用
事務
事務就是將需要執行的方法使用wrapper封裝起來,再通過事務提供的perform方法執行,先執行wrapper中的initialize方法,執行完perform之後,在執行所有的close方法,一組initialize及close方法稱為一個wrapper。
那麼事務和setState方法的不同表現有什麼關係,首先我們把4次setState簡單歸類,前兩次屬於一類,因為它們在同一呼叫棧中執行,setTimeout中的兩次setState屬於另一類。
在setState呼叫之前,已經處在batchedUpdates執行的事務中了。那麼這次batchedUpdates方法是誰呼叫的呢,原來是ReactMount.js中的_renderNewRootComponent方法。也就是說,整個將React元件渲染到DOM中的過程就是處於一個大的事務中。而在componentDidMount中呼叫setState時,batchingStrategy的isBatchingUpdates已經被設為了true,所以兩次setState的結果沒有立即生效。
再反觀setTimeout中的兩次setState,因為沒有前置的batchedUpdates呼叫,所以導致了新的state馬上生效。
相關文章
- React學習筆記-State(狀態)React筆記
- Flink狀態專題:keyed state和Operator state
- 使用Laravel框架,怎麼通過訪問/xxxx/ooo.php也通過路由來使用Laravel框架PHP路由
- 通過一道題來看React事件模型React事件模型
- React Native 探索(三)元件的 Props (屬性) 和 State (狀態)React Native元件
- Spring Boot通過@ConfigurationProperties訪問靜態資料 - reflectoringSpring Boot
- 設計模式-狀態模式(State Pattern)設計模式
- Flutter State Management狀態管理全面分析Flutter
- 設計模式之狀態模式(State)設計模式
- React 手稿 – Component stateReact
- react 之 state 物件React物件
- jmeter通過cookies來登入JMeterCookie
- docker 中容器通過 API 互相訪問DockerAPI
- 通過Python檢視Azure VM的狀態Python
- 通過 Certbot 安裝 Let's Encrypt 證書,來實現全站的 HTTPS 訪問HTTP
- 通過Go來分析和建立XMLGoXML
- 通過Go來分析和建立JSONGoJSON
- 通過信鴿來解釋 HTTPSHTTP
- 通過IO模型帶來的思考模型
- 通過外來鍵找主鍵
- 【譯】React v16.4.0:你可能並不需要派生狀態(Derived State)React
- 原生 JavaScript 實現 state 狀態管理系統JavaScript
- 手把手教你通過Thrift訪問ApsaraDBforHBase
- k8s通過Service訪問PodK8S
- Oracle 通過透明閘道器訪問mysqlOracleMySql
- Spring中通過Annotation來實現AOPSpring
- 通過 Grub 來引導啟動 UBUNTUUbuntu
- 來通過寫技術文章掙錢
- 通過了解RejectedExecutionException來分析ThreadPoolExecutor原始碼Exceptionthread原始碼
- [譯] 通過測試來解耦 Activity解耦
- 通過提交資訊來關閉issue
- Java的通過管道來實現執行緒通訊Java執行緒
- 說說React元件的StateReact元件
- 翻譯|A Visual Guide to State in ReactGUIIDEReact
- apache 通過ajp訪問tomcat多個站點ApacheTomcat
- 【AWS】通過對等網路打通VPC訪問
- Java通過SSLEngine與NIO實現HTTPS訪問JavaHTTP
- 使用 grpcurl 通過命令列訪問 gRPC 服務RPC命令列
- 其它語言通過HiveServer2訪問HiveHiveServer