如何将某个groupbox中的数据赋值到另一个groupbox_React中的数据和数据流
3.1 狀態介紹
第2章簡要介紹了如何處理React組件中的數據,但如果我們想構建大型的React應用,我們需要花費更多時間來關注它。在本節中,我們將學習:
- 狀態;
- React如何處理狀態;
- 數據如何通過組件流動。
現代Web應用程序通常構建為數據優先的應用。誠然,仍有許多靜態網站(我的博客就是其中之一),但即便是這些網站也是隨著時間的推移而不斷更新的,而且靜態網站通常被認為是與現代Web應用不同類別的網站。人們經常使用的大多數Web應用是高度動態的并且充滿了隨時間變化的數據。
想想Facebook這樣的應用。作為社交網絡,數據是所有有用東西的生命線,它提供了與互聯網上的其他人進行交互的多種方式,所有這些方法都是通過在瀏覽器(或者其他平臺)上修改和接收數據來實現的。許多其他應用包含要展示在UI中的非常復雜的數據,人們可以理解并容易地使用。開發人員還需要有能力維護和推斷這些界面以及數據如何通過界面進行流動,因此應用處理數據的方法與處理隨時間變化的數據的能力是一樣重要的。我們在下一章開始構建的示例應用Letters Social會使用大量的變化數據,但它不像大多數客戶應用或商業應用那樣復雜。我們將在本章更明確地闡述它,并在本書的其余部分繼續學習如何處理React中的數據。
3.1.1 什么是狀態
讓我們簡單地看看狀態,以便當我們查看React中的狀態時可以更好地理解它。即使之前從未明確地思考過或聽說過程序中的“狀態”,但至少可能見過它。大部分程序都可能有一些與它們對應的狀態。如果你之前用過諸如Vue、Angular和Ember這樣的前端框架,那么幾乎可以肯定你一定編寫過擁有某種狀態的UI。React組件也有狀態。那么當談及“狀態”時,我們究竟討論的是什么?試試下面這個定義。
狀態 程序在特定瞬間可訪問的所有信息。這是一個簡化的定義,這個定義可能忽略了一些學術上的細微差別,但就我們的目的而言已經足夠了。許多學者編寫了大量論文致力于精確定義計算機系統中的狀態,但對我們而言,狀態是程序在一瞬間可訪問的信息。這包括,在某個特定時刻無須任何額外的賦值或計算就能夠引用的所有值,換句話說,它是瞬間對某個程序的了解的快照。
例如,這可能包括先前創建的任何變量或其他可用的值。當改變一個變量(而不是用它獲取值時),程序的狀態就會被改變,它與之前不一樣了。僅通過讀取你就可以檢索給定時刻的狀態,但當你隨著時間的推移而進行了某些修改,程序的狀態就會變化。從技術上講,機器的底層狀態在使用時每時每刻都在變化,但我們只關心程序的狀態。
讓我們看一些代碼并逐句檢查代碼清單3-1中的簡單的程序狀態。我們不會深入那些發生在幕后的所有底層分配或過程,我們只是嘗試更清晰地認識程序中的數據,以便更容易思考React組件。
代碼清單3-1 簡單的程序狀態
const letters = 'Letters'; ?--- 在名為letters的變量中保存一個字符串 const splitLetters = letters.split(''); ?--- 將letters切分為一個字符串數組 console.log("Let's spell a word!"); ?--- 打印出一條消息 splitLetters.forEach(letter => console.log(letter)); ?--- 打印出每個字母代碼清單3-1展示了一個簡單的腳本,它進行了一些基本賦值和數據操作并將其輸出到控制臺。這雖然很乏味,但我們可以用它來更多地了解狀態。Javascript采用所謂的“運行至完成”(run to completion)語義,這意味著程序將按照通常被認為的順序從上到下執行。JavaScript引擎常常會以意想不到的方式優化代碼,但它仍然以與原始代碼一致的方式運行。
嘗試從上到下逐行閱讀代碼清單3-1中的代碼。如果想用瀏覽器的調試器執行它,轉至https://codesandbox.io/s/n9mvol5x9p。打開瀏覽器的開發者工具,逐步執行每行代碼并查看所有的變量賦值和其他東西。
考慮到我們的目的,讓我們將每行代碼當作某個時間點。就狀態的簡單定義“程序在特定瞬間可訪問的所有信息”,如何描述應用在某個時刻的狀態?注意,我們讓事情簡單化了并忽略了閉包、垃圾收集等。
(1)letters是一個變量,將字符串“Letters”賦值給它。
(2)通過從letters拆分出每個字符創建了splitLetters,letters仍舊可用。
(3)來自步驟1和步驟2的所有信息都可用,一個消息被發送到控制臺。
(4)程序遍歷數組中的每一項并打印輸出一個字符。這一過程可能瞬間發生幾次,因此對Array.forEach可用的信息對程序也可用。
隨著程序向前執行,狀態隨時間發生變化,更多的信息變得可用,因為任何信息都沒有被刪除而且引用也沒有被改變。表3-1展示了可用信息是如何隨著程序向前執行而增加的。
試著走查自己的代碼并思考程序的每一行可以用什么信息。我們傾向于簡化代碼——這里正是這樣做的,因為我們不必一次性考慮每個可能的維度——但即便是對于簡單的程序也有大量的信息可用。
我們可以認真考慮的一個因素是,當運行的程序變得相當復雜時(就像最簡單的UI也傾向于變得復雜),對其的推理認知會變得很困難。我的意思是,系統的復雜性可能很難一下子都記住,而且系統中的邏輯很難讓人徹底想清楚。大多數程序就是如此,但涉及UI構建時則尤為困難。
現代瀏覽器應用的UI常常代表了多種技術的交集,包括服務器提供數據、樣式和布局API、JavaScript框架、瀏覽器API等。UI框架上的進展旨在簡化這個問題,但它仍是一個挑戰。隨著Web應用越來越普及并融入社會和日常生活中,這個挑戰往往會隨著人們對這些應用的期望變大而增加。如果React是有用的,它需要幫助我們減少或屏蔽一些現代UI的極度復雜的狀態。希望諸位會認識到React確實能做到這一點。但如何做的的呢?一種方法是提供兩個處理數據的特定API:屬性(props)和狀態(state)。
3.1.2 可變狀態與不可變狀態
在React應用中,有兩種主要的方法來處理組件中的狀態,即通過可以改變的狀態和通過不能改變的狀態。我們在這里進行了簡化:應用中存在多種類型的數據和狀態。可以用許多不同的方式表示數據,如二叉樹、Map或Set,或者常規JavaScript對象。但與React組件中的狀態進行通信和交互的方法歸結為這兩類,在React中,它們被稱為狀態(state)(可以在組件中改變的數據)和屬性(props)(組件接收并且不應該被組件改變的數據)。
你可能聽說過狀態和屬性被稱為可變的與不可變的。這在一定程度上是對的,因為JavaScript并未原生地支持真正的不可變對象(Symbol也許是,但它超出本書的范圍了)。在React組件中,狀態通常是可變的,而屬性不應該被改變。在潛心于React特定的API之前,讓我們先稍微深入地探索可變性與不可變性思想。
在第2章中,當狀態被稱為可變的時,意思是我們可以覆蓋或更新該數據(例如可以被覆蓋的變量)。另一方面,不可變狀態是不能被改變的。還有不可變數據結構,其只能通過受控的方式進行改變(這是React中的狀態API的工作方式)。當在第10章和第11章中學習Redux時會模擬不可變數據結構。
我們可以稍微擴展一下可變的和不可變的概念,將相應的數據結構類型包括進來。
- 不變的——一個不可變的持久數據結構,隨著時間的推移可以支持多個版本,但不能直接覆蓋;不可變數據結構通常是持久的。
- 可變的——一個可變的臨時數據結構,隨著時間的推移只支持一個版本;可變的數據結構在其變化時可以被覆蓋并且不支持其他版本。
圖3-1展示了這些概念。
圖3-1 不可變數據結構與可變數據結構中的持久性和臨時性。不可變或持久的數據結構常常記錄一段歷史并且不會改變,但會對隨著時間的推移發生的變化進行版本化。但是,臨時數據結構通常不記錄歷史并且隨著每次更新都會被拋棄
另一種考慮不可變數據結構和可變的數據結構之間的區別的方法是考慮這兩種數據結構各自擁有的不同能力和內存。臨時數據結構只有能力保存一瞬間的數據,而持久數據結構則能夠記錄數據隨時間的變動情況。這正是讓不可變數據結構的不可變性變得更加清晰的所在:只制作狀態的副本——它們沒有被替換。舊狀態被新狀態替代,但數據卻沒有被替換。圖3-2展示了變化是如何發生的。
圖3-2 處理可變數據和不可變數據的變化。臨時數據結構沒有版本,所以當更改它們時,所有以前的狀態都消失了。它們可以說是活在當下,而不可變數據結構能夠隨時間的推移而持續存在
提示 考慮不可變性與可變性的另一種方法是考慮“保存”和“另存”為之間的區別。許多電腦程序能夠保存文件的當前狀況或者用不同的名字保存當前文件的副本。不可變數據類似于在保存它時保存了一個副本,而可變數據則能夠就地覆蓋。盡管JavaScript本身不支持真正的不可變數據結構,React用可變的方式暴露組件的狀態(通過狀態進行改變)并將屬性作為只讀的。通常不可變性和不可變數據結構還有更多知識,但是我們對此的關注無須超過我們對他們已有的了解。如果仍想了解更多,有學術研究在關注這類問題。通過Immutable JS這樣的庫也能在JavaScript應用中廣泛地使用不可變數據結構,但在React中我們只需應對屬性API和狀態API。
3.2 React中的狀態
現在我們已經學習了更多有關狀態和(不)可變性的知識。所有這些知識如何納入React中?好吧,我們在上一章已經了解了一點props API和state API,因此可以料想到它們必定是構建組件方式的重要組成部分。事實上,它們是React組件處理數據和彼此間通信的兩種主要方法。
3.2.1 React中的可變狀態:組件狀態
讓我們從狀態API開始。雖然我們可以說所有組件都有某種“狀態”(一般概念),但并不是React中的所有組件都有本地組件狀態。從現在起,當我提到狀態(state)時,我是在談論React的API,而不是一般概念。繼承自React.Component類的組件可以訪問該API。React會為以此方式創建的組件建立并追蹤一個支撐實例。這些組件還可以訪問下一章將討論的一系列生命周期方法。
通過this.state可以訪問那些繼承自React.Component的組件的狀態。在這種情況下,this引用的是類的實例,而state則是一個React會進行追蹤的特殊屬性。你可能認為只要對state進行賦值或者修改state的屬性就能夠更新state,但情況并非如此。讓我們看看代碼清單3-2中一個簡單React組件中的組件狀態示例。你可以在本地機器上創建這個代碼。或者直接,訪問https://codesandbox.io/s/ovxpmn340y。
代碼清單3-2 使用setState修改組件狀態
import React from "react"; import { render } from "react-dom"; class Secret extends React.Component{ ?--- 創建一個React組件,隨著時間的推移,它會訪問持久的組件狀態——別忘了將類方法綁定到組件實例上constructor(props) {super(props);this.state = {name: 'top secret!', ?--- 為組件提供一個初始狀態以便在render()中嘗試訪問它時不會返回undefined或拋出錯誤};this.onButtonClick = this.onButtonClick.bind(this); ?--- 創建一個React組件,隨著時間的推移,它會訪問持久的組件狀態——別忘了將類方法綁定到組件實例上}onButtonClick() { ?--- 初識setState,它是用于修改組件狀態的專用API。調用setState時提供一個回調函數,該函數會返回一個新狀態對象供React使用this.setState(() => ({name: 'Mark'}));}render() {return (<div><h1>My name is {this.state.name}</h1><button onClick={this.onButtonClick}>reveal the secret!</button> ?--- 將顯示名字的函數綁定到由按鈕發出的點擊事件上</div>)} }render(<Secret/>,document.getElementById('root') ?--- 將頂層組件渲染到應用最高層的HTML元素中——可以用各種方式確定容器,只要ReactDOM能夠找到它 );代碼清單3-2創建了一個簡單的組件,當點擊按鈕時會使用setState更新組件狀態從而揭示秘密的名字。注意,在this上可以使用setState,這是因為組件繼承了React.Component類。
當點擊按鈕時,點擊事件將被觸發,提供給React用于響應事件的函數會被執行。當函數執行時,它會用一個對象作為參數來調用setState方法。這個對象有一個指向字符串的name屬性。React會安排更新狀態。當更新發生后,React DOM會在需要時更新DOM。render函數會被再次調用,但這一次會有一個不同的值提供給包含this.state.name的JSX表達式語法({})。它會展示“Mark”而不是“top secret!”,我的秘密身份就暴露了!
通常情況下,由于性能和復雜性的影響,開發者想要盡可能謹慎地使用setState(React會為開發者追蹤一些東西,而開發者則要在心里追蹤另一部分數據)。有些模式在React社區中廣受歡迎,它們能夠使你幾乎不使用組件狀態(包括Redux、Mobx、Flux等),這些值得作為應用的可選項進行探索——實際上,我們會在第10章和第11章介紹Redux。盡管通常最好是使用無狀態函數組件或者是依賴像Redux這樣的模式,但使用setState本身并不是糟糕的做法——它仍然是修改組件中數據的主要API。
繼續之前,需要注意絕對不要直接修改React組件中的this.state。如果嘗試直接修改this.state,之后調用setState()可能替換掉已做出的改變,更糟糕的是,React并不知道對狀態所做的變化。即使可以將組件狀態當作可以改變的東西,但仍應該將this.state看作是在組件內不可改變的對象(就像props一樣)。
這之所以重要還在于setState()不會立即改變this.state。相反,它創建了一個掛起的狀態轉換(下一章將更為深入地探討渲染和變更檢測)。因此,調用setState方法后訪問this.state可能會返回現有值。因為所有這一切都使得調試情況變得棘手,所以只使用setState()來更改組件狀態。
即使是像代碼清單3-2中的小交互,也發生了很多事情。我們將在后續章節中繼續分解React執行組件更新時所發生的種種步驟,但現在,更仔細地研究組件的render方法則更為重要。請注意,即便執行了狀態改變并修改了相關數據,它仍會以一種相對可理解和可預測的方式發生。
尤其美妙的是,開發者可以一次性聲明期望的組件外觀和結構。沒必要為了兩個可能存在的不同狀態做大量額外工作(展示或不展示高度機密名字)。React處理所有底層的狀態綁定和更新過程,開發者只需要說 “名字應該在這里”。React的好處在于它不會強迫你思考每部分狀態在每個時刻的情況,就像3.1.1小節所做的那樣。
讓我們更仔細地了解一下setState API。它是改變React組件中的動態狀態的主要方法,并且在應用中經常會使用它。讓我們看一下方法簽名,了解需要給它傳遞什么:
setState(updater,[callback] ) -> voidsetState接收一個用來設置組件新狀態的函數以及一個可選的回調函數。updater函數的簽名如下:
(prevState, props) => stateChange之前版本的React允許傳遞一個對象而不是函數作為setState的第一個參數。之前版本的React與當前版本的React(16及以上)的一個關鍵區別在于:傳遞對象暗示著setState本質上是同步的,而實際發生的情況是React會安排一個對狀態的更改。callback格式的簽名更好地傳遞了這個信息并且更符合React的全面聲明性異步范式:容許系統(React)安排更新,保證順序但不保證時間。這與一種更加聲明式的UI方法相契合,而且這比用命令式的方式在不同時刻指定數據更新(常常是競態條件的源泉)更易于思考。
如果需要根據當前狀態或屬性對狀態做一下更新,可以通過prevState和props參數來訪問這些狀態和屬性。當要實現類似Boolean切換的東西或者在執行更新前需要知道上一個值時,這通常很有用。
讓我們對setState的機制投注更多的關注。setState會使用updater函數返回的對象與當前狀態進行淺合并。這意味著,開發人員可以生成一個對象,而React會將該對象的頂級屬性合并到狀態中。例如,有一個對象有屬性A和屬性B,B有一些深層嵌套的屬性而A只是一個字符串(’Hi!’)。由于執行的是淺合并,只有頂級屬性和它們引用的部分得以保留,而不是B的每個部分。React不會尋找B的深層嵌套屬性進行更新。解決這個問題的方法是制作對象的副本,深層更新它,而后使用更新后的對象。也可以用immutable.js這樣的庫來讓處理React的數據結構更容易。
setState是一個用起來很直觀的API,為ReactClass組件提供一些需要合并到當前狀態中的數據,React會為你把它處理好。如果由于某些原因而需要監聽過程的完成情況,可以使用可選的callback函數掛載到該過程。代碼清單3-3展示了setState的一個實際的淺合并的例子。像之前一樣,使用CodeSandbox可以很容易地創建和運行React組件。這可以省去在自己機器上進行設置的麻煩。
代碼清單3-3 使用setState進行淺合并
import React from "react"; import { render } from "react-dom"; class ShallowMerge extends React.Component {constructor(props) {super(props);this.state = {user: {name: 'Mark', // ?--- name存在于初始state的user屬性中……colors: {favorite: '',}}};this.onButtonClick = this.onButtonClick.bind(this);}onButtonClick() {this.setState({user: { // ?--- ……但正在設置的state 中并沒有name——如果它在上一級的話,淺合并就不會發揮作用了colors: {favorite: 'blue'}}});}render() {return (<div><h1>My favorite color is {this.state.user.colors.favorite} and myname is {this.state.user.name}</h1><button onClick={this.onButtonClick}>show the color!</button></div>)} }render(<ShallowMerge />,document.getElementById('root') );初學React時,忘記淺合并是常見的問題來源。在這個示例中,當點擊按鈕時,內嵌在初始狀態的user鍵內的name屬性會被覆蓋,因為新狀態中沒有它。本來打算保持這兩個狀態,但一個覆蓋了另一個。
練習3-1 思考setState API本章探討了React管理組件內狀態的組件API,所提及的事情之一就是需要通過seState API來修改狀態,而不是直接修改。為什么這是一個問題而且為什么那樣做行不通呢?試試https://codesandbox.io/s/j7p824jxnw。
3.2.2 React中的不可變狀態:屬性
我們已經討論了React如何通過狀態和setState以可變的方式處理數據,但React中的不可變數據怎么樣呢?React中,屬性(props)是傳遞不可變數據的主要方式,所有組件都能接收屬性(不只是繼承自React.Component的組件)并能在其構造函數、render和生命周期方法中使用它們。
React中的屬性或多或少是不可變的。使用庫和其他工具能夠模擬組件中的不可變數據結構,但React的props API本身是半不可變的。如果JavaScript原生的Object.freeze方法可用的話,React會使用它防止添加新屬性或移除現有屬性。Object.freeze也能防止現有屬性(或其可枚舉性、可配置性和可寫性)被修改并防止原型被修改。這很大程度上防止修改props對象,但它在技術上并不是真正的不可變對象(盡管可以這樣想)。
屬性是傳遞給React組件的數據,要么來自父組件要么來自組件自身的defaultProps靜態方法。然而組件狀態只限于單個組件,屬性通常由父組件傳遞。如果開發人員想:“我是否能用父組件的狀態來傳遞屬性給子組件?”,那么你算想到些事了。一個組件的狀態可以是另一個組件的屬性。
屬性通常在JSX中作為屬性進行傳遞,但如果使用React.createElement的話,則可以通過該接口直接將它們傳遞給子組件。可以將任何有效的JavaScript數據作為一個屬性傳遞給其他組件,甚至可以傳遞組件(它們畢竟只是類而已)。一旦屬性被傳遞給組件使用,就不能在組件內部改變它們。盡可以嘗試,但可能會得到一個像,Uncaught TypeError:Cannot assign to read-only property'<myProperty>'ofobject'#<Object>'這樣的錯誤,或者更糟,React應用將無法如預期一樣工作,因為違反了預期的使用方式。
下一節中的代碼清單3-4展示了訪問屬性的方式以及如何不給它們賦值。如前所述,屬性可以隨時間改變,但不是從組建內部改變。這是單項數據流的一部分,后續章節會涵蓋這個主題。簡而言之,單向意味著從父組件到子組件一路向下改變數據流。一個使用狀態的父組件(繼承自React.Component)可以改變自己的狀態,這個改變的狀態可以作為屬性傳遞給子組件,從而改變屬性。
練習3-2 在render方法中調用setState我們已經明確,setState是更新組件狀態的方法。可以在哪里調用setState?我們將在下一章了解組件生命周期的哪個點可以調用setState,但現在讓我們先將注意力只放在render方法上。當在組件的render方法中調用setState會發生什么?去https://codesandbox.io/s/48zv2nwqww做些試驗。
3.2.3 使用屬性:PropTypes和默認屬性
當使用屬性時,有些API可以在開發過程中提供幫助:PropTypes和默認屬性。PropTypes提供了類型檢查功能,可以用它指定組件期望接收什么樣的屬性。可以指定數據類型,甚至可以告訴組件的使用者需要提供什么形式的數據(例如,一個擁有user屬性的對象,user屬性包含特定的鍵)。在之前版本的React中,PropTypes是核心React庫的一部分,但它現在作為prop-types包單獨存在。
prop-types庫并非魔法——它是一組能夠幫助你對輸入進行類型檢查的函數和屬性——可以在其他庫中很容易地用它來對輸入進行類型檢查。例如,可以將prop-types引入另一個類似于React的組件驅動框架(如Preact)并用相似的方式使用它。
要為組件設置PropTypes,需要在類上提供一個叫作propTypes的靜態屬性。注意代碼清單3-4,在組件類上設置的靜態屬性的名字是以小寫字母開頭的,而從prop-types庫訪問的對象名是以大寫字母開頭的(PropTypes)。為了指定組件需要哪個屬性,需要添加要驗證的屬性名并為其分配一個來自prop-types庫默認導出(import PropTypes from 'prop-types')的屬性。使用PropTypes可以為屬性聲明任何類型、形式和必要性(可選還是強制)。
另一個可以讓開發體驗更為簡單的工具是默認屬性。還記得如何使用類的構造方法(constructor)為組件提供初始狀態?也可以為屬性做類似的事情。你可以通過一個名為defaultProps的靜態屬性來為組件提供默認屬性。使用默認屬性可以幫助確保組件擁有運行所需的東西,即便使用組件的人忘記為其提供屬性。代碼清單3-4展示了在組件中使用PropTypes和默認屬性的例子。你可以前往https://codesandbox.io/ s/31ml5pmk4m運行代碼。
代碼清單3-4 React組件的不可變屬性
import React from "react"; import { render } from "react-dom"; import PropTypes from "prop-types";class Counter extends React.Component {static propTypes = { ?--- 指定一個描述“形式”的對象incrementBy: PropTypes.number,onIncrement: PropTypes.func.isRequired ?--- 可以為任何propTypes鏈接isRequired從而確保在屬性沒有出現時展示警告};static defaultProps = {incrementBy: 1};constructor(props) {super(props);this.state = {count: 0};this.onButtonClick = this.onButtonClick.bind(this);}onButtonClick() {this.setState(function(prevState, props) {return { count: prevState.count + props.incrementBy };});}render() {return (<div><h1>{this.state.count}</h1><button onClick={this.onButtonClick}>++</button></div>);} }render(<Counter incrementBy={1} />, document.getElementById("root"));3.2.4 無狀態函數組件
要做些什么才能創建只使用屬性并且沒有狀態的簡單組件?這是通常的使用情況,特別是我們之后將探索的一些常見的對React友好的應用架構模式,如Flux和Redux。在這些情況下,我們通常希望將狀態保存在一個中心位置而不是分散保存到組件中。但其他情形中只使用屬性也是有用的。如果React不必管理支撐實例,那么減少應用在資源使用上的損耗也是不錯的。
事實證明,可以創建一種只使用屬性的組件:無狀態函數組件。這些組件有時被開發人員稱為無狀態組件、函數組件和其他類似的名字,這一點有時讓人很難了解正在討論的內容。它們通常指的是同一件事——一個沒有繼承React.Component的組件,因此不能訪問組件狀態或其他生命周期方法。
不足為奇,無狀態功能組件只是不能訪問或使用React的狀態API(或繼承自React.Component的其他方法)的組件。它之所以沒有狀態并不是因為它沒有任何種類的(一般)狀態,而是因為它不會獲得React進行管理的支撐實例。這意味著沒有生命周期方法(第4章會涵蓋),沒有組件狀態,并且可能占用更少的內存。
無狀態函數組件是函數,因為它們可以被編寫為命名函數或是賦值給變量的匿名函數表達式。它們只接收屬性,而且對于給定的輸入它們會返回相同的輸出,因此基本上被認為是純函數。這使得它們很快,因為React有可能通過避免不必要的生命周期檢查或內存分配來進行優化。代碼清單3-5展示了無狀態函數組件的例子。你可以前往https://codesandbox.io/s/l756002969運行代碼。
代碼清單3-5 無狀態函數組件
import React from "react"; import { render } from "react-dom"; import PropTypes from "prop-types"; function Greeting(props) { ?--- 可以使用函數或匿名函數創建無狀態函數組件return <div>Hello {props.for}!</div>; } Greeting.propTypes = {for: PropTypes.string.isRequired ?--- 對于任何形式的無狀態函數組件,可以用函數或變量的屬性來指定propTypes和默認屬性 }; Greeting.defaultProps = {for: 'friend' }; ?--- 對于任何形式的無狀態函數組件,可以用函數或變量的屬性來指定propTypes和默認屬性render(<Greeting for="Mark" />, mountNode);// 或者使用箭頭函數 // const Greeting = (props) => <div>Hello {props.for}</div>; // 像之前那樣指定PropTypes和默認屬性 // render(<Greeting name="Mark" />, document.getElementById("root")); ?--- 可以使用函數或匿名函數創建無狀態函數組件無狀態函數組件很強大,特別是與擁有支撐實例的父組件結合使用時。與其在多個組件間設置狀態,不如創建單個有狀態的父組件并讓其余部分使用輕量級子組件。第10章和第11章中,我們會使用Redux將這個模式提升到全新的水平。使用Redux的React應用常常會創建更少的有狀態組件(盡管仍有讓有狀態組件發揮作用的情況)并將狀態集中到單個位置中(就是指store)。
練習3-3 使用一個組件的狀態來修改另一個組件的屬性本章討論了屬性和狀態這兩種處理和傳遞React組件中數據的主要方法。絕對不要直接修改狀態或屬性,而是使用setState告訴React更新組件的狀態。如何使用一個組件的狀態來修改另一個組件的屬性?你可以前往https://codesandbox.io/s/38zq71q75進行嘗試。
3.3 組件通信
當構建簡單的評論框組件時,我們已經看到能夠用其他組件創建組件。這是React特別棒的原因之一。開發人員能夠輕易地用子組件構建其他組件,與此同時還能夠保持事物良好地捆綁在一起,并且還能很容易地表示組件間的is-a和has-a關系。這意味著可以將組件看作組件的一部分或是一種特定的東西。
能夠混合和匹配組件并靈活地構建東西是很棒的,但如何讓它們彼此通信呢?許多框架和庫提供了框架特有的方法讓應用的不同部分彼此通信。Angular.js或Ember.js中,你可能聽說過或曾經使用服務在應用的不同部分之間進行通信。通常這些是廣泛可用的長期對象,開發人員可以在其中存儲狀態并從應用的不同部分進行訪問。
React使用了服務或類似的東西嗎?沒有。在React中,如果想讓組件彼此通信,需要傳遞屬性,并且當傳遞屬性時,開發人員做了兩件簡單的事情:
- 訪問父組件中的數據(要么是狀態要么是屬性);
- 傳遞數據給子組件。
代碼清單3-6的示例既展示了你熟悉的父子關系,也展示了所屬關系。你可以前往https://codesandbox.io/s/ pm18mlz8jm運行代碼。
代碼清單3-6 從父組件向子組件傳遞屬性
import React from "react"; import { render } from "react-dom"; import PropTypes from "prop-types"; const UserProfile = props => { ?--- 創建一個返回示例圖片的無狀態函數組件return <img src={`https://*******. *******.com/user/${props.username}`} />; }; UserProfile.propTypes = {pagename: PropTypes.string ?--- 記住,即使是在無狀態函數組件上,仍可以指定默認屬性和propTypes };UserProfile.defaultProps = {pagename: "erondu" ?--- 記住,即使是在無狀態函數組件上,仍可以指定默認屬性和propTypes };const UserProfileLink = props => {return <a href={`https://ifelse.io/${props.username}`}>{props.username}</a>; };const UserCard = props => { ?--- UserCard是UserProfile和UserProfileLink的父組件return (<div><UserProfile username={props.username} /><UserProfileLink username={props.username} /></div>); };render(<UserCard username="erondu" />, document.getElementById("root"));3.4 單向數據流
如果之前使用框架開發過Web應用,可能已經熟悉術語雙向數據綁定(two-way data binding)。數據綁定是建立應用UI與其他數據之間聯系的過程。在實踐中,這常常表現為連接模型這樣的應用數據(如用戶)和用戶界面的庫或框架并會保持兩者同步。它們彼此同步因此被綁定在一起。React中一個更有幫助的思考方法是將其作為投影:UI是投射到視圖中的數據,當數據變化時,視圖隨之變化,如圖3-3所示。
圖3-3 數據綁定通常指的是在應用數據與視圖(該數據的展示)之間建立連接的過程。另一種思考方式是將其作為數據向用戶能夠看到的東西(如視圖)的投射
數據流是另一種思考數據綁定的方法:數據如何流經應用的不同部分?本質上,人們會問:“什么能夠更新什么,從哪里更新,以及如何更新?”如果想用好工具,那么理解正在使用的工具如何塑造、維護和移動數據是無比重要的。不同的庫和框架會采用不同的數據流方法(React對如何處理數據流并沒有不同的想法)。
React中,數據流是單向的。這意味著實體間的流動并非水平的——這種情況下彼此可以相互更新,而是建立了一個層次結構。可以通過組件傳遞數據,但如果不傳遞屬性,就不能觸及和修改其他組件的狀態或屬性,也無法修改父組件中的數據。
但可以通過回調函數將數據傳回層次結構的上層。當父組件接收到來自子組件的回調函數時,它可以修改其數據并將修改的數據傳遞給子組件。即便是對于有回調函數的情況,數據總體上仍是向下流動的并仍由向下傳遞該數據的父組件決定。這就是為什么我們稱React中的數據流是單向的,如圖3-4所示。
圖3-4 數據在React中是按一個方向流動的。屬性由父組件傳遞給子組件(從所有者到擁有者),并且子組件不能編輯父組件的狀態或屬性。每個擁有支撐實例的組件都能修改它自己的狀態但無法修改超出其自身的東西,除了設置其子組件的屬性
單向數據流在構建UI時特別有用,因為它讓思考數據在應用中流動的方式變得更簡單。得益于組件的層次結構以及將屬性與狀態局限于組件的方式,預測數據如何在應用中移動通常更容易。
某種程度上避免這個層次結構聽上去似乎不錯,可以而且從應用的任何部分隨意修改想要修改的東西,但實際上這往往會導致難以琢磨的應用并且可能造成困難的調試情況。后續章節將探索Flux和Redux這樣的架構模式,它允許維護單向數據流范式的同時協調跨組件或跨應用的行動。
3.5 小結
本章討論了如下主題。
- 狀態是程序在特定瞬間可訪問的信息。
- 不可變狀態不會改變,而可變狀態會改變。
- 持久的、不可變的數據結構不會改變——它們只記錄其改變并創建自己的副本。
- 臨時的、可變的數據結構會在更新時被清除。
- React即使用可變數據(組件本地狀態),也使用偽不可變數據(屬性)。
- 屬性是偽不可變的并且一旦設置就不應該被修改。
- 組件狀態由支撐實例追蹤并且可以使用setState進行修改。
- setState執行數據的淺合并、更新組件狀態,保留任何沒有被覆蓋的頂級屬性。
- React中的數據流是單向的,從父組件流向子組件。子組件通過回調函數將數據回送給父組件,但它們不能直接修改父組件的狀態,而且父組件也無法直接修改子組件的狀態。組件通過屬性完成組件交互。
本文摘自《React實戰》
React的設計初衷就是,幫助開發者為用戶提供令人驚嘆的用戶體驗。每位開發者都可以使用React這個強大的工具!管理狀態、數據流和渲染的巧妙設計是成功的關鍵,只有這樣設計的應用才能運行順暢、讓人記憶猶新。開發者只要進入這個由組件和庫構成的極其豐富的生態系統,就可以掌握構建讓開發者和用戶都賞心悅目的Web應用的秘訣。
本書指導讀者像專家一樣思考用戶界面(UI),并教讀者用React構建它們。本書非常實用,配有很多可實際操作的示例,讓讀者快速上手。本書的目標是讓讀者掌握渲染、生命周期方法、JSX、數據流、表單、路由、與第三方庫集成和測試等核心概念,并且幫助讀者利用書中介紹的應用設計理念推動應用的流行。在學習將React集成到全棧應用的過程中,讀者還可以探索通過Redu**行狀態管理和服務器端渲染,甚至可以接觸到用于移動UI 的React Native。
總結
以上是生活随笔為你收集整理的如何将某个groupbox中的数据赋值到另一个groupbox_React中的数据和数据流的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: openstack云主机无法绑定ip_智
- 下一篇: python walk_Python o