[轉] 深入理解React 組件狀態(State)

React 的核心思想是組件化的思想,應用由組件搭建而成,而組件中最重要的概念是State(狀態),State是一個組件的UI數據模型,是組件渲染時的數據依據。

一. 如何定義State

定義一個合適的State,是正確創建組件的第一步。State必須能代表一個組件UI呈現的完整狀態集,即組件的任何UI改變,都可以從State的變化中反映出來;同時,State還必須是代表一個組件UI呈現的最小狀態集,即State中的所有狀態都是用于反映組件UI的變化,沒有任何多余的狀態,也不需要通過其他狀態計算而來的中間狀態。

組件中用到的一個變量是不是應該作為組件State,可以通過下面的4條依據進行判斷:

  1. 這個變量是否是通過Props從父組件中獲取?如果是,那么它不是一個狀態。
  2. 這個變量是否在組件的整個生命周期中都保持不變?如果是,那么它不是一個狀態。
  3. 這個變量是否可以通過其他狀態(State)或者屬性(Props)計算得到?如果是,那么它不是一個狀態。
  4. 這個變量是否在組件的render方法中使用?如果不是,那么它不是一個狀態。這種情況下,這個變量更適合定義為組件的一個普通屬性,例如組件中用到的定時器,就應該直接定義為this.timer,而不是this.state.timer。

請務必牢記,并不是組件中用到的所有變量都是組件的狀態!當存在多個組件共同依賴一個狀態時,一般的做法是狀態上移,將這個狀態放到這幾個組件的公共父組件中。

二. State 與 Props 區別

除了State, 組件的Props也是和組件的UI有關的。他們之間的主要區別是:State是可變的,是組件內部維護的一組用于反映組件UI變化的狀態集合;而Props對于使用它的組件來說,是只讀的,要想修改Props,只能通過該組件的父組件修改。在組件狀態上移的場景中,父組件正是通過子組件的Props, 傳遞給子組件其所需要的狀態。

三. 如何正確修改State

1.不能直接修改State。

直接修改state,組件并不會重新重發render。例如:

// 錯誤
this.state.title = 'React';

正確的修改方式是使用setState():

// 正確
this.setState({title: 'React'});

2. State 的更新是異步的。

調用setState,組件的state并不會立即改變,setState只是把要修改的狀態放入一個隊列中,React會優化真正的執行時機,并且React會出于性能原因,可能會將多次setState的狀態修改合并成一次狀態修改。所以不要依賴當前的State,計算下個State。當真正執行狀態修改時,依賴的this.state并不能保證是最新的State,因為React會把多次State的修改合并成一次,這時,this.state將還是這幾次State修改前的State。另外需要注意的事,同樣不能依賴當前的Props計算下個狀態,因為Props一般也是從父組件的State中獲取,依然無法確定在組件狀態更新時的值。

舉個例子,對于一個電商類應用,在我們的購物車中,當我們點擊一次購買數量按鈕,購買的數量就會加1,如果我們連續點擊了兩次按鈕,就會連續調用兩次this.setState({quantity: this.state.quantity + 1}),在React合并多次修改為一次的情況下,相當于等價執行了如下代碼:

Object.assign(previousState,{quantity: this.state.quantity + 1}, {quantity: this.state.quantity + 1} ) 

于是乎,后面的操作覆蓋掉了前面的操作,最終購買的數量只增加了1個。

如果你真的有這樣的需求,可以使用另一個接收一個函數作為參數的setState,這個函數有兩個參數,第一個是當前最新狀態(本次組件狀態修改后的狀態)的前一個狀態preState(本次組件狀態修改前的狀態),第二個參數是當前最新的屬性props。如下所示:

// 正確
this.setState((preState, props) => ({ counter: preState.quantity + 1; })) 

3. State 的更新是一個淺合并(Shallow Merge)的過程。

當調用setState修改組件狀態時,只需要傳入發生改變的State,而不是組件完整的State,因為組件State的更新是一個淺合并(Shallow Merge)的過程。例如,一個組件的狀態為:

this.state = {title : 'React',content : 'React is an wonderful JS library!'
}

當只需要修改狀態title時,只需要將修改后的title傳給setState

this.setState({title: 'Reactjs'}); 

React會合并新的title到原來的組件狀態中,同時保留原有的狀態content,合并后的State為:

{title : 'Reactjs',content : 'React is an wonderful JS library!'
}

四. State與Immutable

React官方建議把State當作是不可變對象,一方面是如果直接修改this.state,組件并不會重新render;另一方面State中包含的所有狀態都應該是不可變對象。當State中的某個狀態發生變化,我們應該重新創建這個狀態對象,而不是直接修改原來的狀態。那么,當狀態發生變化時,如何創建新的狀態呢?根據狀態的類型,可以分成三種情況:

1. 狀態的類型是不可變類型(數字,字符串,布爾值,null, undefined)

這種情況最簡單,因為狀態是不可變類型,直接給要修改的狀態賦一個新值即可。如要修改count(數字類型)、title(字符串類型)、success(布爾類型)三個狀態:

this.setState({count: 1, title: 'Redux', success: true }) 

2. 狀態的類型是數組

如有一個數組類型的狀態books,當向books中增加一本書時,使用數組的concat方法或ES6的數組擴展語法(spread syntax):

// 方法一:將state先賦值給另外的變量,然后使用concat創建新數組
var books = this.state.books; 
this.setState({ books: books.concat(['React Guide']); }) // 方法二:使用preState、concat創建新數組 this.setState(preState => ({ books: preState.books.concat(['React Guide']); })) // 方法三:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'React Guide']; })) 

當從books中截取部分元素作為新狀態時,使用數組的slice方法:

// 方法一:將state先賦值給另外的變量,然后使用slice創建新數組
var books = this.state.books; 
this.setState({ books: books.slice(1,3); }) // 方法二:使用preState、slice創建新數組 this.setState(preState => ({ books: preState.books.slice(1,3); })) 

當從books中過濾部分元素后,作為新狀態時,使用數組的filter方法:

// 方法一:將state先賦值給另外的變量,然后使用filter創建新數組
var books = this.state.books; 
this.setState({ books: books.filter(item => { return item != 'React'; }); }) // 方法二:使用preState、filter創建新數組 this.setState(preState => ({ books: preState.books.filter(item => { return item != 'React'; }); })) 

注意不要使用push、pop、shift、unshift、splice等方法修改數組類型的狀態,因為這些方法都是在原數組的基礎上修改,而concat、slice、filter會返回一個新的數組。

3. 狀態的類型是普通對象(不包含字符串、數組)

3.1 使用ES6 的Object.assgin方法

// 方法一:將state先賦值給另外的變量,然后使用Object.assign創建新對象
var owner = this.state.owner;
this.setState({ owner: Object.assign({}, owner, {name: 'Jason'}); }) // 方法二:使用preState、Object.assign創建新對象 this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Jason'}); })) 

3.2 使用對象擴展語法(object spread properties)

// 方法一:將state先賦值給另外的變量,然后使用對象擴展語法創建新對象
var owner = this.state.owner;
this.setState({ owner: {...owner, name: 'Jason'}; }) // 方法二:使用preState、對象擴展語法創建新對象 this.setState(preState => ({ owner: {...preState.owner, name: 'Jason'}; })) 

總結一下,創建新的狀態對象的關鍵是,避免使用會直接修改原對象的方法,而是使用可以返回一個新對象的方法。當然,也可以使用一些Immutable的JS庫,如Immutable.js,實現類似的效果。

那么,為什么React推薦組件的狀態是不可變對象呢?一方面是因為不可變對象方便管理和調試,了解更多可參考這里;另一方面是出于性能考慮,當對象組件狀態都是不可變對象時,我們在組件的shouldComponentUpdate方法中,僅需要比較狀態的引用就可以判斷狀態是否真的改變,從而避免不必要的render調用。當我們使用React 提供的PureComponent時,更是要保證組件狀態是不可變對象,否則在組件的shouldComponentUpdate方法中,狀態比較就可能出現錯誤,因為PureComponent執行的是淺比較(比較對象的引用)。



作者:蒼山沭河
鏈接:https://www.jianshu.com/p/c6257cbef1b1
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

轉載于:https://www.cnblogs.com/chris-oil/p/8215756.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/278663.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/278663.shtml
英文地址,請注明出處:http://en.pswp.cn/news/278663.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

vue-router query,parmas,meta傳參

1.query,顯示在導航欄?后,相當于get請求傳參 this.router.push({path:/login,query:{ redirect:/home}}) this.router.push({name:Login,query:{ redirect:/home}})2.parmas,不會顯示,相當于post請求傳參,…

Keras 獲取中間某一層輸出

1.使用函數模型API,新建一個model,將輸入和輸出定義為原來的model的輸入和想要的那一層的輸出,然后重新進行predict. 1 #codingutf-82 import seaborn as sbn3 import pylab as plt4 import theano5 from keras.models import Sequential6 fr…

在Windows 7中禁用或修改Aero Peek的“延遲時間”

Are you looking for an easy way to modify the “delay time” for Aero Peek in Windows 7 or perhaps want to disable the feature altogether? Then see how simple it is to do either with the Desktop Peek Tweak. 您是否正在尋找一種簡便的方法來修改Windows 7中Aer…

php內核分析(六)-opcode

這里閱讀的php版本為PHP-7.1.0 RC3,閱讀代碼的平臺為linux 查看opcode php是先把源碼解析成opcode,然后再把opcode傳遞給zend_vm進行執行的。 // 一個opcode的結構 struct _zend_op {const void *handler; // opcode對應的執行函數,每個opcod…

vue 監聽路由變化

一.watch監聽$route($router的對象) // 監聽,當路由發生變化的時候執行 watch:{$route(to,from){console.log(to.path);} },// 監聽,當路由發生變化的時候執行 watch: {$route: {handler: function(val, oldVal){console.log(val);},// 深度觀察監聽dee…

音頻剪切_音頻編輯入門指南:剪切,修剪和排列

音頻剪切Audacity novices often start with lofty project ideas, but sometimes they lack the basics. Knowing how to cut and trim tracks is basic audio editing and is a fundamental starting point for making more elaborate arrangements. 大膽的新手通常從崇高的項…

Mybatis自定義SQL攔截器

本博客介紹的是繼承Mybatis提供的Interface接口,自定義攔截器,然后將項目中的sql攔截一下,打印到控制臺。 先自定義一個攔截器 package com.muses.taoshop.common.core.database.config;import org.apache.commons.lang3.StringUtils; import…

搭建spring boot環境并測試一個controller

Idea搭建spring boot環境一、新建項目二、起步依賴三、編寫SpringBoot引導類四、編寫Controller五、熱部署一、新建項目 1.新建project 2.選擇SpringInitializr,選擇jdk,沒有則需要下載并配置(若選擇Maven工程則需要自己添加pom.xml所需依賴坐標和Java…

音頻噪聲抑制_音頻編輯入門指南:基本噪聲消除

音頻噪聲抑制Laying down some vocals? Starting your own podcast? Here’s how to remove noise from a messy audio track in Audacity quickly and easily. 放下人聲? 開始自己的播客? 這是在Audacity中快速輕松地消除雜亂音軌中噪聲的方法。 Th…

Dubbo集群容錯

轉自dubbo官網文檔http://dubbo.apache.org/zh-cn/blog/dubbo-cluster-error-handling.html Design For failure 在分布式系統中,集群某個某些節點出現問題是大概率事件,因此在設計分布式RPC框架的過程中,必須要把失敗作為設計的一等公民來對…

Linux基礎(day53)

2019獨角獸企業重金招聘Python工程師標準>>> 12.21 php-fpm的pool php-fpm的pool目錄概要 vim /usr/local/php/etc/php-fpm.conf//在[global]部分增加include etc/php-fpm.d/*.confmkdir /usr/local/php/etc/php-fpm.d/cd /usr/local/php/etc/php-fpm.d/vim www.co…

Mysql+Navicat for Mysql

一、mysql 1.下載安裝 Mysql官網下載地址 下載后解壓 .zip (或安裝.msi) 2.可加入全局變量mysqld (可選) 我的電腦->屬性->高級->環境變量->Path(系統變量),添加mysql下的bin目錄,如 D:\Pr…

公鑰,私鑰和數字簽名

一、公鑰加密 假設一下,我找了兩個數字,一個是1,一個是2。我喜歡2這個數字,就保留起來,不告訴你們(私鑰),然后我告訴大家,1是我的公鑰。 我有一個文件,不能讓別人看&…

MySQL中的日志類型(二)-General query log

簡介 General query log記錄客戶端的連接和斷開,以及從客戶端發來的每一個SQL語句。 日志內容格式 General query log可以記錄在文件中,也可以記錄在表中,格式如下:在文件中會記錄時間、線程ID、命令類型以及執行的語句示例如下&a…

android wi-fi_如何在Android手機上查找3G或Wi-Fi速度

android wi-fiAre you curious about what kind of connection speed you are getting with your Android phone? Today we’ll take a look at how to easily check your Wi-Fi or 3G speeds with Speedtest.net’s Speed Test app. 您是否對Android手機的連接速度感到好奇&a…

vue引入全局less實現全局變量的控制

vue引入全局less1.設置全局樣式變量的好處:2.以less為例(sass等同原理)1.vue-cli2搭建的項目(1)2.vue-cli2搭建的項目(2)3.vue-cli3、vue-cli43.vue-cli2和vue-cli3的區別4.vue-cli3和vue-cli4的…

如何在eclipse中對項目進行重新編譯

有時由于eclipse異常關閉,當我們重啟Eclipse,在啟動項目時,會報錯,說:ClassNotFound類似的錯誤,引起這種問題的原因可能是由于,Eclipse異常關閉引起的。 解決:在一個項目中&#xff…

SQL 查詢數據庫中包含指定字符串的相關表和相關記錄

declare str varchar(100)set str我要找的 --要搜索的字符串declare s varchar(8000)declare tb cursor local forselect if exists(select 1 from [b.name] where [a.name] like %str%)print [b.name].[a.name]from syscolumns a join sysobjects b on a.idb.idwhere b.xtype…

如何在Gmail的圖片中插入超鏈接

Adding hyperlinks is an efficient way of getting your reader to the intended web page. Though it’s no secret that you can add hyperlinks to text, Gmail also lets you add hyperlinks to images in the body of the email. Here’s how to make it happen. 添加超鏈…

內聯元素居中

父元素&#xff1a; height:100px; line-height:100px; // 與高相同 text-align:center; 子元素: display:inline; vertical-align: middle; 適用圖片、文字 <div><div class"wrapper"><span>我是文字</span></div><div class&qu…