React.js 查看狀態管理教程
已發表: 2022-03-11前端 Web 開發中最大和最常見的問題之一是狀態管理。 像我這樣的自由前端開發人員一直專注於保持狀態對象與其視圖和 DOM 表示同步。 用戶可以通過多種方式與應用程序交互,提供從一種狀態到另一種狀態的干淨轉換是一項艱鉅的任務。
一點歷史
不久前,Web 應用程序的數據流要簡單得多。 瀏覽器將向服務器發送請求,所有應用程序邏輯都將在服務器上執行,完整的 HTML 視圖將被發送回瀏覽器以呈現給用戶。 隨後的用戶操作(如點擊、表單提交等)將再次觸發相同的流程。 應用程序不必擔心用戶狀態,並且可以通過向服務器發送新請求來重新生成每個視圖。
然而,Web 應用程序的複雜性不斷增加,用戶對 UI/UX 的需求也在不斷提高。 重新加載整個頁面,當它只有一部分發生變化時,效率低下且速度慢。 我們需要一種對 UI 產生直接影響的快速、快速、響應式的交互。
JavaScript 來救場了。 開發人員開始編寫大量代碼,這些代碼在將請求發送到服務器之前在瀏覽器中執行。 jQuery 還為前端 Web 開髮帶來了重大進步,因為它提供了簡單有效的開箱即用功能,例如客戶端驗證、模式窗口、警報消息、動畫,甚至基於 Ajax 的部分頁面更新。
了解複雜性
讓我們看一個評估密碼強度的簡單示例。 如果密碼正確,輸入框應該有一個綠色的邊框,並且應該顯示一個不錯的消息。 如果密碼較弱,則輸入框應有紅色邊框並顯示警告消息。 當密碼足夠強大時,我們也可能會顯示笑臉。
以下代碼演示瞭如何通過 DOM 操作來完成此操作。 這裡有很多“ifs”,代碼也不是很容易閱讀。
if (hasInputBorder()) { removeInputBorder(); } if (text.length === 0) { if (hasMessage()) { removeMessage(); } if (hasSmiley()) { removeSmiley(); } } else { var strength = getPasswordStrength(text); if (!hasInputBorder()) { addInputBorder(); } var color = (strength == 'weak' ? 'red' : 'green'); setInputBorderColor(color); var message = (strength == 'weak' ? "Password is weak" : "That's what I call a password!"); if (hasMessage()) { setMessageText(message); } else { addMessageWithText(message); } if (strength == 'weak') { if (hasSmiley()) { removeSmiley(); } } else { if (!hasSmiley()) { addSmiley(); } } }
如上所示,首先我們需要檢查用戶是否提供了任何密碼,並處理密碼字段為空的情況。 在所有情況下,我們都需要確保所有相關的 DOM 元素都正確更新。 這包括消息、邊框和笑臉。
我們的密碼字段可以處於以下三種狀態之一:空、弱或強。 如前所述,我們有三個不同的 DOM 元素,它們受密碼字段狀態的影響。 處理所有組合,並確保我們的視圖正確顯示,即使是這樣一段簡單的代碼也會增加圈複雜度。
DOM 以保留模式工作,這意味著它只記住當前狀態。 為了修改我們的視圖,我們需要為每個 DOM 元素提供指令並對轉換進行編程。
編碼轉換而不是狀態可能很複雜。 我們需要在代碼中執行的分支和檢查的數量隨著要管理的視圖狀態的數量呈指數增長。
在我們的示例中,我們定義了三個視圖狀態,這給了我們3 * 2 = 6
轉換。 一般來說,給定 N 個狀態,我們需要建模N * (N - 1) = N^2 - N
轉換。 想想如果我們在示例中添加第四個狀態會增加複雜性。
通常有太多與轉換建模相關的代碼。 如果我們可以只定義我們的視圖狀態,而不用擔心從一種狀態轉換到另一種狀態的所有細節,那就更好了。
降低複雜性
假設我們可以根據模型狀態聲明視圖狀態,而不是顯式編碼從一種狀態到另一種狀態的轉換,我們可以這樣:
var strength = getPasswordStrength(text); if (text.length == 0) { return div(input({type: 'password', value: text})); } else if (strength == 'weak') { return div( input({type: 'password', value: text, borderColor: 'red'}), span({}, "Weak") ); } else { return div( input({type: 'password', value: text, borderColor: 'green'}), span({}, "That's what I call a password!"), img({class: 'icon-smiley'}) ); }
這裡我們有三個簡單的代碼分支,代表我們應用程序的三種可能狀態。 我們只是根據模型狀態返回每個分支中視圖的規範。 移除所有 DOM 操作代碼; 我們只是提供有關我們想要什麼的信息,而不是如何到達那裡。
雖然這種方法確實顯著降低了代碼複雜性,但它也假設有人或其他人代表我們處理實際的 DOM 操作。
這就是 React 的用武之地。React 將確保根據底層數據模型的狀態立即管理和更新視圖狀態。
反應
React 是 Facebook 創建的 JavaScript 庫。 它旨在處理 Web 應用程序的 UI 部分。 您可以將其視為 MVC 架構中的 V。 它非常專注。 它不對您的技術堆棧的其餘部分做任何假設,並且除了渲染組件之外不處理任何事情。 它不提供通常捆綁在較大框架中的任何路由機制、模型或其他功能。 因此,您可以混合使用它並將其與您想要的任何其他庫或框架一起使用。
React 允許我們將 UI 定義為複合組件的樹。 給定輸入狀態,React 開發人員通過指定描述組件的渲染函數來定義這些組件。 該函數應該是純函數(即,它不應有任何副作用或依賴於其顯式輸入以外的任何內容)。
render 函數返回一個視圖描述,React 將其稱為Virtual DOM 。 將其視為與渲染的 DOM 元素相對應的 JavaScript 對象。
當您更改組件的狀態時,它只會重新渲染自身及其所有子元素,並返回一個新的Virtual DOM 。
此外,當從一種狀態轉換到另一種狀態時,React 不會進行簡單的 HTML 替換。 它將找到前一個狀態和新狀態之間的差異,併計算最有效的 DOM 操作集以執行轉換。
即使不考慮性能,代碼複雜性的降低本身也很重要,它使我們能夠將精力集中在應用程序中更獨特和更複雜的部分。
為了更具體一點,這就是我們的教程示例如何使用 React 來管理視圖狀態。
注意:以下代碼示例是用 JSX 預處理器編寫的,這是編寫基於 React 的 UI 的常用方法。
function getPasswordStrength(text) { // Some code that calculates the strength given the password text. } var PasswordWithStrength = React.createClass({ getInitialState: function() { return {value: ''}; }, render: function() { var strength = getPasswordStrength(this.state.value); if (this.state.value.length == 0) { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} /> </div>; } else if (strength == 'weak') { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid red'} } /> <span style={{color: 'red'}}>Weak!</span> </div>; } else { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid green'} } /> <span style={{color: 'green'}}>That's what I call a password!</span> <Emoji value="smiley" /> </div>; } }, handleInputChange: function(ev) { this.setState({value: ev.target.value}); } }); React.render(<PasswordWithStrength />, document.body);
<Emoji value="smiley" />
密碼強度正常時呈現的Emoji
組件只是另一個自定義組件(就像PasswordWithStrength
一樣)。 它是這樣定義的:
var Emoji = React.createClass({ render: function() { var emojiSrc = this.props.value + '.png'; return <img src={emojiSrc}></img>; } });
React.js 與其他
不過,公平地說,還有其他客戶端 JavaScript 框架(例如 Ember、Angular、Knockout 等)也解決了視圖狀態管理問題,甚至為其添加了更多功能。 那麼,為什麼要使用 React 而不是任何其他框架呢?

與大多數其他庫相比,React 有兩個關鍵優勢。
無數據綁定
其他一些替代框架使用數據綁定將 DOM 元素映射到狀態屬性,並通過觀察屬性更改使它們保持同步。 這種方法允許渲染一次視圖,每次更改都只觸發受影響的 DOM 元素的修改。 其他替代方案使用臟檢查; 也就是說,它們不是觀察單個屬性的變化,而是在前一個狀態和新狀態之間進行差異。 React 更類似於後一種方法,但它不是比較狀態,而是比較視圖表示。
React 沒有數據綁定。 當狀態改變時,開發人員應該調用setState
方法,或重新渲染頂部組件。 它包含從狀態到視圖的單向流。
這個概念很容易採用,因為開發人員通常不會考慮數據綁定。 重點是數據的可視化表示。 因此,您無需考慮依賴屬性、格式、綁定特殊 HTML 標記等。使用 React,您只需在模型更改時重新渲染組件。
要了解這裡的視圖狀態管理的區別,讓我們比較一下 Ember 和 React 。 我們將創建一個以大寫形式輸出全名的對象person
。 兩秒鐘後,我們將模擬更改並更新視圖。
// EXAMPLE USING EMBER App = Ember.Application.create(); App.Person = Ember.Object.extend({ firstName: null, lastName: null, fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.property('firstName', 'lastName') }); var person = App.Person.create({ firstName: "John", lastName: "Doe" }); Ember.Handlebars.helper('upcase', function(value) { return value.toUpperCase(); }); App.IndexRoute = Ember.Route.extend({ model: function () { return person; } }); setTimeout(function() { person.set('firstName', 'Harry'); }, 2000); // Templates: <script type="text/x-handlebars"> <h2>Welcome to Ember.js</h2> {{outlet}} </script> <script type="text/x-handlebars" data-template-name="index"> The current user is: {{upcase model.fullName}} </script>
我們創建了一個具有firstName
、 lastName
和fullName
屬性的對象。 由於 Ember 正在觀察屬性變化,我們必須指定fullName
依賴於firstName
和lastName
。 為此,我們在定義fullName
時添加了.property('firstName', 'lastName')
。
兩秒後, person.set('firstName', 'Harry');
被執行。 這觸發了視圖及其綁定的更新。
現在讓我們在 React 中做同樣的事情。
// EXAMPLE USING REACT var CurrentUser = React.createClass({ render: function() { return <div>The current user is: {this.props.user.fullName().toUpperCase()}</div>; } }); var person = { firstName: 'John', lastName: 'Doe', fullName: function() { return this.firstName + ' ' + this.lastName; } }; var currentUser = React.render(<CurrentUser user={person}/>, document.body); setTimeout(function() { person.firstName = 'Harry'; currentUser.setProps({user: person}); }, 2000);
儘管 Ember 代碼簡單易讀,但很明顯 React 在簡單性方面勝出。 person
是一個普通的 JavaScript 對象, fullName
只是一個函數。
沒有模板
每個替代框架都有不同的處理模板的方式。 其中一些使用編譯成 JavaScript 的字符串,而另一些則直接使用 DOM 元素。 他們中的大多數使用自定義的 HTML 屬性和標籤,然後將其“編譯”成 HTML。
模板不是 JavaScript 代碼的一部分。 因此,每個替代方案都需要一種自定義方式來表示常見操作、條件、迭代、調用函數等。它們最終都會創建一種開發人員必須學習的新偽語言。
React 中沒有模板,一切都只是簡單的舊 JavaScript。
React 使用 JavaScript 的全部功能來生成視圖。 組件的 render 方法是一個 JavaScript 函數。
JSX 可用作將“類 HTML 語法”轉換為普通 JavaScript 的預處理器,但 JSX 是可選的,您可以在沒有任何預處理器的情況下自由使用標準 JavaScript。 您還可以利用現有的 JavaScript 工具。 短絨、預處理器、類型註釋、縮小、死代碼消除等。
讓我們再次使用一個具體的例子來比較 React 和視圖狀態管理的替代框架之一。
以下教程是使用AngularJS列出主題標籤和每個標籤的推文計數的示例。 該列表按計數排序,如果沒有主題標籤,則會顯示一條消息。
<!-- EXAMPLE USING ANGULAR --> <div ng-controller="MyCtrl"> <ul ng-show="hashTags.length > 0"> <li ng-repeat="hashTag in hashTags | orderBy:'tweetCount':true"> {{hashTag.name}} - {{hashTag.tweetCount}} tweets </li> </ul> <span ng-show="hashTags.length == 0">No hashtags found!</span> </div>
為了能夠列出這個列表,開發人員必須了解AngularJS directives
、 ng-show
和ng-repeat
。 然後他需要學習AngularJS filters
來理解orderBy
。 像輸出列表這樣簡單的事情需要做很多工作!
現在讓我們考慮做同樣事情的 React 示例:
// EXAMPLE USING REACT function byTweetCountDesc(h1, h2) { return h2.tweetCount - h1.tweetCount; } //... render: function() { if (this.state.hashTags.length > 0) { var comps = this.state.hashTags.sort(byTweetCountDesc).map(function(hashTag, index) { return <li key={index}> {hashTag.name} - {hashTag.tweetCount} tweets </li>; }); return <ul>{comps}</ul>; } else { return <span>No hashtags found!</span> } }
即使我們使用“更高級”的方法和 JSX,每個對 JavaScript 有基本了解的 Web 開發人員都可以輕鬆閱讀上面的代碼並理解它的作用。 使用if
的標準條件檢查、使用map()
的迭代和標準的 'sort()' 對任何開發人員來說都很自然,因此無需學習額外的語法或其他概念。
結論
這個 React.js 教程的主要內容是 React 使您能夠專注於實際的視圖狀態管理而不是轉換,從而簡化您的工作和應用程序。
採用 React 的學習曲線相當簡單。 無需掌握自定義模板語言,無需擔心數據綁定,一切都歸結為描述 UI 元素的 JavaScript 函數。
要了解有關使用 React 簡化應用程序代碼的更多信息,請查看 Steven Luscher 的演講,Decomplexifying Code with React。
對於想要邁出下一步並開始使用 React 的人來說,這裡有一些額外的閱讀材料:
- http://jlongster.com/Removing-User-Interface-Complexity,-or-Why-React-is-Awesome