摘要:的參數既可以是一個對象,也可以是一個回調函數。回調函數提供了兩個參數,第一個參數就是計算過的對象,即便這時還沒有渲染,得到的依然是符合直覺的計算過的值。專題一覽什么是可變狀態不可變屬性生命周期組件事件操作抽象
本文是『horseshoe·React專題』系列文章之一,后續會有更多專題推出
來我的 GitHub repo 閱讀完整的專題文章
來我的 個人博客 獲得無與倫比的閱讀體驗
React使用一個特殊的對象this.state來管理組件內部的狀態。
然后開發者就可以通過描述狀態來控制UI的表達。
如何描述狀態呢?
一般我們會在constructor生命周期鉤子初始化狀態。
import React, { Component } from "react"; class App extends Component { constructor(props) { super(props); this.state = { name: "", star: 0 }; } } export default App;
也可以直接用屬性初始化器的寫法,看起來更加簡潔。
然后通過this.setSatate()來改變狀態。
import React, { Component } from "react"; class App extends Component { state = { name: "", star: 0 }; componentDidMount() { this.setState({ name: "react", star: 1 }); } } export default App;this.state 首先,改變狀態有特殊的門路
開發者不能直接改變this.state的屬性,而是要通過this.setSatate方法。
為什么要這樣設計?
可能是為了更加語義化吧,開發者清楚自己在更新狀態,而不是像Vue那樣改變于無形。
不過別急,我為正在閱讀的你準備了一個炸彈:
猜猜下面例子最終渲染出來的star是多少?
import React, { Component } from "react"; class App extends Component { state = { star: 0 }; componentDidMount() { this.state.star = 1000; this.setState(prevState => ({ star: prevState.star + 1 })); } // componentDidMount() { // this.setState(prevState => ({ star: prevState.star + 1 })); // this.state.star = 1000; // } // componentDidMount() { // this.state.star = 1000; // this.setState({ star: this.state.star + 1 }); // } // componentDidMount() { // this.setState({ star: this.state.star + 1 }); // this.state.star = 1000; // } } export default App;
答案是1001。
誒,不是說不能直接改變this.state的屬性么?
聽我講,首先,this.state并不是一個不可變對象,你(非得較勁的話)是可以直接改變它的屬性的。但是它不會觸發render生命周期鉤子,也就不會渲染到UI上。
不過,既然你確實改變了它的值,如果之后調用了this.setSatate()的話,它會在你直接改變的值的基礎上再做更新。
所以呀少年,要想不懵逼,得靠我們自己的代碼規范。
至于注釋的部分,只是為了說明順序問題。
第一部分注釋渲染出來的star是1001。因為回調會首先計算star的值,而這時候star的值是1000。
第二部分注釋渲染出來的star是1001。這很好理解。
第三部分注釋渲染出來的star是1。這也好理解,這個時候star的值還是0。
其次,狀態更新會合并處理大家也看到了,我們可以每次更新部分狀態。
新狀態并不會覆蓋舊狀態,而是將已有的屬性進行合并操作。如果舊狀態沒有該屬性,則新建。
這類似于Object.assign操作。
而且合并是淺合并。
只有第一層的屬性才會合并,更深層的屬性都會覆蓋。
import React, { Component } from "react"; class App extends Component { state = { userInfo: { name: "", age: 0 } }; componentDidMount() { this.setState({ userInfo: { age: 13 } }); } } export default App;最后,可以有不是狀態的狀態
如果你需要存儲某種狀態,但是不希望在狀態更新的時候觸發render生命周期鉤子,那么完全可以直接存儲到實例的屬性上,只要不是this.state的屬性。使用起來還是很自由的。
異步更新 什么叫異步更新?異步更新說的直白點就是批量更新。
它不是真正的異步,只是React有意識的將狀態攢在一起批量更新。
React組件有自己的生命周期,在某兩個生命周期節點之間做的所有的狀態更新,React會將它們合并,而不是立即觸發UI渲染,直到某個節點才會將它們合并的值批量更新。
以下,組件更新之后this.state.star的值是1。
import React, { Component } from "react"; class App extends Component { state = { star: 0 }; componentDidMount() { this.setState({ star: this.state.star + 1 }); this.setState({ star: this.state.star + 1 }); this.setState({ star: this.state.star + 1 }); } } export default App;
因為這些狀態改變的操作都是在組件掛載之后、組件更新之前,所以實際上它們并沒有立即生效。
this.state.star的值一直是0,盡管狀態被多次操作,它得到的值一直是1,因此合并之后this.state.star的還是1,并不是我們直覺以為的3。
為什么要異步更新?因為this.setSatate()會觸發render生命周期鉤子,也就會運行組件的diff算法。如果每次setState都要走這一套流程,不僅浪費性能,而且是完全沒有必要的。
所以React選擇了在一定階段內批量更新。
還是以生命周期為界,掛載之前的所有setState批量更新,掛載之后到更新之前的所有setState批量更新,每次更新間隙的所有setState批量更新。
非異步情況再來看一種情況:
猜猜最終渲染出來的star是多少?
import React, { Component } from "react"; class App extends Component { state = { star: 0 }; timer = null; componentDidMount() { this.timer = setTimeout(() => { this.setState({ num: this.state.star + 1 }); this.setState({ num: this.state.star + 1 }); this.setState({ num: this.state.star + 1 }); }, 5000); } componentWillUnmount() { clearTimeout(this.timer); } } export default App;
答案是3。
臥槽!
說實話,這里我也沒想明白。
我在React倉庫的Issues里提過這個情況,這是React主創之一Dan Abramov的回答:
setState is currently synchronous outside of event handlers. That will likely change in the future.
Dan Abramov所說的event handlers應該指的是React合成事件回調和生命周期鉤子。
我的理解,因為只有這些方法才能回應事件,所以它們之中的狀態更新是批量的。但是它們之中的異步代碼里有狀態更新操作,React就不會批量更新,而是符合直覺的樣子。
我們看下面的例子,正常的重復setState只會觸發一次更新,但是http請求回調中的重復setState卻會多次觸發更新,看來異步的setState不在React掌控之內。
import React, { Component } from "react"; class App extends Component { state = { star: 0 }; componentDidMount() { fetch("https://api.github.com/users/veedrin/repos") .then(res => res.json()) .then(res => { console.log(res); this.setState({ star: this.state.star + 1 }); this.setState({ star: this.state.star + 1 }); this.setState({ star: this.state.star + 1 }); }); } } export default App;
還有一種情況就是原生的事件回調,比如document上的事件回調,也不是異步的。
總結一下:所謂的異步只是批量更新而已。真正異步回調和原生事件回調中的setState不是批量更新的。
不過,Dan Abramov早就提到過,會在將來的某個版本(可能是17大版本)管理所有的setState,不管是不是在所謂的event handlers之內。
React的設計有一種簡潔之美,從這種對待開發者反饋的態度可見一斑。
回調既然this.setSatate()的設計不符合直覺,React早就為開發者提供了解決方案。
this.setSatate()的參數既可以是一個對象,也可以是一個回調函數。函數返回的對象就是要更新的狀態。
回調函數提供了兩個參數,第一個參數就是計算過的state對象,即便這時還沒有渲染,得到的依然是符合直覺的計算過的值。同時,貼心的React還為開發者提供了第二個參數,雖然并沒有什么卵用。
以下,組件更新之后this.state.star的值是3。
有一個小細節:箭頭函數如果直接返回一個對象,要包裹一層小括號,以區別塊級作用域。
import React, { Component } from "react"; class App extends Component { state = { star: 0 }; componentDidMount() { this.setState((prevState, prevProps) => ({ star: prevState.star + 1 })); this.setState((prevState, prevProps) => ({ star: prevState.star + 1 })); this.setState((prevState, prevProps) => ({ star: prevState.star + 1 })); } } export default App;chaos
總之呢,React更新狀態的設計到處都是坑。
大家對React吐槽最多的點是什么呢?
圈外人吐槽JSX。
圈內人吐槽this.setState。
期盼React給開發者一個不令人困惑的狀態更新API吧。
React專題一覽什么是UI
JSX
可變狀態
不可變屬性
生命周期
組件
事件
操作DOM
抽象UI
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/108402.html
摘要:沒有團伙,單獨作案,干凈利落,便于封口。它最大的特點就是不可變。兄弟組件之間傳值原理和回調函數一樣,只不過這里父組件只是一個橋梁。父組件接收到回調函數的值以后,通過保存該值,并觸發另一個子組件重新渲染,重新渲染后另一個子組件便可以獲得該值。 本文是『horseshoe·React專題』系列文章之一,后續會有更多專題推出來我的 GitHub repo 閱讀完整的專題文章來我的 個人博客 ...
摘要:現代前端框架的使命就是開發者管理狀態,框架根據狀態自動生成。專題一覽什么是可變狀態不可變屬性生命周期組件事件操作抽象 本文是『horseshoe·React專題』系列文章之一,后續會有更多專題推出來我的 GitHub repo 閱讀完整的專題文章來我的 個人博客 獲得無與倫比的閱讀體驗 什么是UI? 如果你指的是布局和色彩,那更偏向于設計師的工作。 在現代web領域,大家已經有一個共識...
摘要:而生命周期鉤子,就是從生到死過程中的關鍵節點。異步渲染下的生命周期花了兩年時間祭出渲染機制。目前為這幾個生命周期鉤子提供了別名,分別是將只提供別名,徹底廢棄這三個大活寶。生命周期鉤子的最佳實踐是在這里初始化。 本文是『horseshoe·React專題』系列文章之一,后續會有更多專題推出來我的 GitHub repo 閱讀完整的專題文章來我的 個人博客 獲得無與倫比的閱讀體驗 生命周期...
摘要:我們可以為元素添加屬性然后在回調函數中接受該元素在樹中的句柄,該值會作為回調函數的第一個參數返回。使用最常見的用法就是傳入一個對象。單向數據流,比較有序,有便于管理,它隨著視圖庫的開發而被概念化。 面試中問框架,經常會問到一些原理性的東西,明明一直在用,也知道怎么用, 但面試時卻答不上來,也是挺尷尬的,就干脆把react相關的問題查了下資料,再按自己的理解整理了下這些答案。 reac...
閱讀 951·2021-09-27 13:36
閱讀 907·2021-09-08 09:35
閱讀 1077·2021-08-12 13:25
閱讀 1447·2019-08-29 16:52
閱讀 2918·2019-08-29 15:12
閱讀 2737·2019-08-29 14:17
閱讀 2625·2019-08-26 13:57
閱讀 1022·2019-08-26 13:51