瀏覽 React.js 生態系統

已發表: 2022-03-11

瀏覽 react.js 生態系統

JavaScript Land 的創新速度如此之快,以至於有些人甚至認為它適得其反。 圖書館可以在幾個月的時間內從早期採用的玩具變成最先進的玩具,再到過時。 能夠識別一種至少能再流行一年的工具本身就是一門藝術。

兩年前發布 React.js 時,我剛剛學習 Angular,我很快就將 React 視為一些晦澀難懂的另一個模板庫。 在那兩年裡,Angular 真正在 JavaScript 開發者中站穩了腳跟,幾乎成了現代 JS 開發的代名詞。 我什至開始在非常保守的企業環境中看到 Angular,我認為它的光明未來是理所當然的。

但是突然,奇怪的事情發生了。 Angular 似乎成為了奧斯本效應的受害者,或者說是“預先宣布死亡”。 該團隊宣布,首先,Angular 2 將完全不同,沒有從 Angular 1 明確的遷移路徑,其次,Angular 2 將在一年左右的時間內無法使用。 這告訴想要開始一個新的網絡項目的人是什麼? 你想在一個將被新版本發布過時的框架中編寫你的新項目嗎?

開發人員之間的這種焦慮在 React 的手中發揮了作用,它正在尋求在社區中建立自己的地位。 但 React 總是將自己標榜為“MVC”中的“V”。 這在習慣於使用完整框架的 Web 開發人員中造成了一定程度的挫敗感。 如何填寫缺失的部分? 我應該自己寫嗎? 我應該只使用現有的庫嗎? 如果有,是哪一個?

果然,Facebook(React.js 的創造者)又多了一張王牌:Flux 工作流,它承諾填補缺失的“M”和“C”函數。 為了讓事情變得更有趣,Facebook 表示 Flux 是一種“模式”,而不是框架,他們對 Flux 的實現只是該模式的一個示例。 正如他們所說,他們的實現非常簡單,並且涉及編寫大量冗長、重複的樣板來讓事情順利進行。

開源社區出手相救,一年後,我們有了幾十個 Flux 庫,甚至還有一些旨在比較它們的元項目。 這是一件好事; Facebook 沒有發布一些現成的企業框架,而是設法激發了社區的興趣,並鼓勵人們提出自己的解決方案。

這種方法有一個有趣的副作用:當您必須組合許多不同的庫來獲得完整的框架時,您實際上是在擺脫供應商鎖定,並且在您自己的框架構建過程中出現的創新可以很容易地重用別處。

這就是為什麼圍繞 React 的新東西如此有趣的原因; 其中大部分可以很容易地在其他 JavaScript 環境中重用。 即使你不打算使用 React,看看它的生態系統也是鼓舞人心的。 您可能希望使用功能強大但相對容易配置的模塊捆綁器 Webpack 來簡化構建系統,或者現在開始使用 Babel 編譯器編寫 ECMAScript 6 甚至 ECMAScript 7。

在本文中,我將介紹一些可用的有趣功能和庫。 那麼,讓我們探索一下 React 生態系統吧!

React 探險者

構建系統

在創建新的 Web 應用程序時,構建系統可以說是您應該關心的第一件事。 構建系統不僅是運行腳本的工具,而且在 JavaScript 世界中,它通常會塑造應用程序的一般結構。 構建系統必須涵蓋的最重要任務如下:

  • 管理外部和內部依賴項
  • 運行編譯器和預處理器
  • 優化生產資產
  • 運行開發 Web 服務器、文件觀察器和瀏覽器重新加載器

近年來,Bower 和 Grunt 的 Yeoman 工作流被認為是現代前端開發的三位一體,分別解決了樣板生成、包管理和常見任務運行的問題,最近更進步的人從 Grunt 切換到 Gulp。

在 React 環境中,您可以放心地忘記這些。 並不是說你不能使用它們,而是你很有可能使用 Webpack 和舊的 NPM 就可以僥倖逃脫。 這怎麼可能? Webpack 是一個模塊捆綁器,它實現了 CommonJS 模塊語法,在 Node.js 世界中很常見,在瀏覽器中也是如此。 它實際上使事情變得更簡單,因為您不需要學習另一個前端包管理器; 您只需使用 NPM 並在服務器和前端之間共享依賴項。 您也不需要處理以正確順序加載 JS 文件的問題,因為它是從每個文件中指定的依賴項導入推斷出來的,並且整個 swarm 正確連接到一個可加載腳本。

Webpack 徽標
網頁包

為了讓事情更具吸引力,Webpack 與它的老兄弟 Browserify 不同,它也可以處理其他資產類型。 例如,使用加載器,您可以將任何資產文件轉換為內聯或加載引用文件的 JavaScript 函數。 因此,忘記手動預處理和引用 HTML 中的資產。 只require JavaScript 中的 CSS/SASS/LESS 文件,Webpack 通過一個簡單的配置文件來處理其餘的事情。 Webpack 還包括一個開發 Web 服務器和一個文件觀察器。 另外,您可以使用package.json中的"scripts"鍵來定義 shell 單行:

 { "name": "react-example-filmdb", "version": "0.0.1", "description": "Isomorphic React + Flux film database example", "main": "server/index.js", "scripts": { "build": "./node_modules/.bin/webpack --progress --stats --config ./webpack/prod.config.js", "dev": "node --harmony ./webpack/dev-server.js", "prod": "NODE_ENV=production node server/index.js", "test": "./node_modules/.bin/karma start --single-run", "postinstall": "npm run build" } ... }

這就是替換 Gulp 和 Bower 所需的全部內容。 當然,您仍然可以使用 Yeoman 來生成應用程序樣板。 當你想要的東西沒有 Yeoman 生成器時不要氣餒(最前沿的庫通常沒有)。 你仍然可以從 GitHub 克隆一些樣板,然後破解。

相關: React 組件如何簡化 UI 測試

明天,今天的 ECMAScript

近年來,JavaScript 語言開發的步伐大幅加快,經過一段時間的消除怪癖和穩定語言之後,我們現在看到了強大的新功能。ECMAScript 6 (ES6) 規範草案已經完成,儘管它尚未正式發布,它已經被廣泛採用。 ECMAScript 7 (ES7) 的工作正在進行中,但它的許多功能已經被更前沿的庫所採用。

ECMAScript 7 徽標
ECMAScript 7

這怎麼可能? 也許您認為在 Internet Explorer 支持之前,您無法利用這些閃亮的新 JavaScript 功能,但請再想一想。 ES 轉譯器已經變得如此普遍,我們甚至可以在沒有適當的瀏覽器支持的情況下完成。 目前可用的最好的 ES 轉譯器是 Babel:它將獲取您最新的 ES6+ 代碼,並將其轉換為原始 ES5,因此,您可以使用任何新的 ES 功能,一旦它被發明(並在 Babel 中實現,這通常發生迅速地)。

通天塔標誌
通天塔

最新的 JavaScript 功能在所有前端框架中都很有用,並且最近更新了 React 以很好地與 ES6 和 ES7 規範配合使用。 在使用 React 進行開發時,這些新功能應該會消除很多令人頭疼的問題。 讓我們來看看一些最有用的添加,以及它們如何使 React 項目受益。 稍後,我們將看到如何在 React 中使用一些有用的工具和庫,同時利用這種改進的語法。

ES6 類

面向對象編程是一種強大且被廣泛採用的範例,但 JavaScript 對它的理解有點奇怪。 大多數前端框架,無論是 Backbone、Ember、Angular 還是 React,都採用了自己專有的方式來定義類和創建對象。 但是在 ES6 中,我們現在在 JavaScript 中有傳統的類,使用它們而不是編寫我們自己的實現是很有意義的。 所以,而不是:

 React.createClass({ displayName: 'HelloMessage', render() { return <div>Hello {this.props.name}</div>; } })

我們可以寫:

 class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } }

對於更詳細的示例,請考慮使用舊語法的代碼:

 React.createClass({ displayName: 'Counter', getDefaultProps: function(){ return {initialCount: 0}; }, getInitialState: function() { return {count: this.props.initialCount} }, propTypes: {initialCount: React.PropTypes.number}, tick() { this.setState({count: this.state.count + 1}); }, render() { return ( <div onClick={this.tick}> Clicks: {this.state.count} </div> ); } });

並與 ES6 版本進行比較:

 class Counter extends React.Component { static propTypes = {initialCount: React.PropTypes.number}; static defaultProps = {initialCount: 0}; constructor(props) { super(props); this.state = {count: props.initialCount}; } state = {count: this.props.initialCount}; tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div onClick={this.tick.bind(this)}> Clicks: {this.state.count} </div> ); } }

在這裡,不再需要 React 生命週期方法getDefaultPropsgetInitialStategetDefaultProps成為靜態類變量defaultProps ,初始狀態只是在構造函數中定義。 唯一的缺點是,方法不再是自動綁定的,因此在從 JSX 調用處理程序時必須使用bind

裝飾器

裝飾器是 ES7 的一個有用特性。 它們允許您通過將函數或類包裝在另一個函數中來增強其行為。 例如,假設您希望在某些組件上使用相同的更改處理程序,但您不想提交繼承反模式。 您可以改用類裝飾器。 讓我們定義裝飾器如下:

 addChangeHandler: function(target) { target.prototype.changeHandler = function(key, attr, event) { var state = {}; state[key] = this.state[key] || {}; state[key][attr] = event.currentTarget.value; this.setState(state); }; return target; }

這裡重要的是函數addChangeHandlerchangeHandler函數添加到目標類的原型中。

要應用裝飾器,我們可以這樣寫:

 MyClass = changeHandler(MyClass)

或者更優雅,使用 ES7 語法:

 @addChangeHandler class MyClass { ... }

至於changeHandler函數本身的內容,由於 React 沒有雙向數據綁定,在 React 中處理輸入可能很乏味。 changeHandler函數試圖使它更容易。 第一個參數指定狀態對像上的一個key ,它將作為輸入的數據對象。 第二個參數是屬性,輸入字段的值將被保存到該屬性。 這兩個參數是從 JSX 使用bind關鍵字設置的。

 @addChangeHandler class LoginInput extends React.Component { constructor(props) { super(props); this.state = { login: {} }; } render() { return ( <input type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'login', 'username')} /> <input type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'login', 'password')} /> ) } }

當用戶更改用戶名字段時,其值將保存到this.state.login.username ,無需定義更多自定義處理程序。

箭頭函數

JavaScript 的動態this上下文一直是開發人員的痛點,因為有點不直觀,嵌套函數的this上下文被重置為全局,即使在類內部也是如此。 為了解決這個問題,通常需要將this保存到一些外部範圍變量(通常是_this )並在內部函數中使用它:

 class DirectorsStore { onFetch(directors) { var _this = this; this.directorsHash = {}; directors.forEach(function(x){ _this.directorsHash[x._id] = x; }) } }

使用新的 ES6 語法, function(x){可以重寫為(x) => { 。 這個“箭頭”方法定義不僅正確地將this綁定到外部範圍,而且相當短,這在編寫大量異步代碼時肯定很重要。

 onFetch(directors) { this.directorsHash = {}; directors.forEach((x) => { this.directorsHash[x._id] = x; }) }

解構賦值

ES6 中引入的解構賦值允許你在賦值的左側有一個複合對象:

 var o = {p: 42, q: true}; var {p, q} = o; console.log(p); // 42 console.log(q); // true

這很好,但它實際上如何幫助我們在 React 中? 考慮以下示例:

 function makeRequest(url, method, params) { var config = { url: url, method: method, params: params }; ... }

通過解構,您可以節省一些擊鍵。 鍵字面量{url, method, params}會自動從作用域中分配與鍵同名的值。 這個成語經常使用,消除重複使代碼不易出錯。

 function makeRequest(url, method, params) { var config = {url, method, params}; ... }

解構還可以幫助您僅加載模塊的子集:

 const {clone, assign} = require('lodash'); function output(data, optional) { var payload = clone(data); assign(payload, optional); }

參數:默認、休息和傳播

函數參數在 ES6 中更強大。 最後,您可以設置默認參數:

 function http(endpoint, method='GET') { console.log(method) ... } http('/api') // GET

厭倦了擺弄笨拙的arguments對象? 使用新規範,您可以將其餘參數作為數組獲取:

 function networkAction(context, method, ...rest) { // rest is an array return method.apply(context, rest); }

如果你不喜歡調用apply() ,你可以將一個數組傳播到函數參數中:

 myArguments = ['foo', 'bar', 123]; myFunction(...myArguments);

生成器和異步函數

ES6 引入了 JavaScript 生成器。 生成器基本上是一個 JavaScript 函數,它的執行可以暫停,然後在稍後恢復,記住它的狀態。 每次遇到yield關鍵字時,都會暫停執行,並將yield參數傳回調用對象:

 function* sequence(from, to) { console.log('Ready!'); while(from <= to) { yield from++; } }

下面是這個生成器的一個例子:

 > var cursor = sequence(1,3) Ready! > cursor.next() { value: 1, done: false } > cursor.next() { value: 2, done: false } > cursor.next() { value: 3, done: false } > cursor.next() { value: undefined, done: true }

當我們調用生成器函數時,它會執行到第一個yield ,然後停止。 在我們調用next()之後,它返回第一個值,然後繼續執行。 每個yield返回另一個值,但是在第三次調用之後,生成器函數終止,並且對next()的每個後續調用都將返回{ value: undefined, done: true }

當然,生成器的目的不是創建複雜的數字序列。 令人興奮的部分是它們能夠停止和恢復函數執行,可以用來控制異步程序流,最終擺脫那些討厭的回調函數。

為了演示這個想法,我們首先需要一個異步函數。 通常,我們會有一些 I/O 操作,但為了簡單起見,我們只使用setTimeout並返回一個 Promise。 (請注意,ES6 還向 JavaScript 引入了原生 Promise。)

 function asyncDouble(x) { var deferred = Promise.defer(); setTimeout(function(){ deferred.resolve(x*2); }, 1000); return deferred.promise; }

接下來,我們需要一個消費者函數:

 function consumer(generator){ var cursor = generator(); var value; function loop() { var data = cursor.next(value); if (data.done) { return; } else { data.value.then(x => { value = x; loop(); }) } } loop(); }

該函數將任何生成器作為參數,只要有要yield的值,它就會不斷調用next() 。 在這種情況下,yield 的值是 Promise,因此需要等待 Promise 解析,並使用遞歸與loop()來實現嵌套函數之間的循環。

返回值在then()處理程序中解析,並傳遞給在外部範圍中定義的value ,並將傳遞給next(value)調用。 此調用使該值成為相應 yield 表達式的結果。 這意味著我們現在可以在沒有任何回調的情況下異步編寫:

 function* myGenerator(){ const data1 = yield asyncDouble(1); console.log(`Double 1 = ${data1}`); const data2 = yield asyncDouble(2); console.log(`Double 2 = ${data2}`); const data3 = yield asyncDouble(3); console.log(`Double 3 = ${data3}`); } consumer(myGenerator);

生成器myGenerator將在每次yield時暫停,等待消費者交付已解決的承諾。 事實上,我們會看到計算出的數字以一秒的間隔出現在控制台中。

 Double 1 = 2 Double 2 = 4 Double 3 = 6

這演示了基本概念,但是,我不建議您在生產中使用此代碼。 相反,請選擇經過良好測試的庫,例如 co。 這將允許您輕鬆編寫帶有 yield 的異步代碼,包括錯誤處理:

 co(function *(){ var a = yield Promise.resolve(1); console.log(a); var b = yield Promise.resolve(2); console.log(b); var c = yield Promise.resolve(3); console.log(c); }).catch(function(err){ console.error(err.stack); });

所以,這個例子展示瞭如何使用 ES6 生成器編寫沒有回調的異步代碼。 ES7 通過引入asyncawait關鍵字將這種方法更進一步,並且完全不需要生成器庫。 使用此功能,前面的示例將如下所示:

 async function (){ try { var a = await Promise.resolve(1); console.log(a); var b = await Promise.resolve(2); console.log(b); var c = await Promise.resolve(3); console.log(c); } catch (err) { console.error(err.stack); } };

在我看來,這消除了在 JavaScript 中使用異步代碼的痛苦。 不僅在 React 中,在其他任何地方也是如此。

生成器不僅更加簡潔明了,而且還允許我們使用在回調中很難實現的技術。 生成器優秀的一個突出例子是 Node.js 的 koa 中間件庫。 它旨在取代 Express,並且為了實現這一目標,它具有一個殺手級功能:中間件鏈不僅流向下游(帶有客戶端請求),而且還流向上游,允許進一步修改服務器的響應。 考慮以下 koa 服務器示例:

 // Response time logger middleware app.use(function *(next){ // Downstream var start = new Date; yield next; // Upstream this.body += ' World'; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); }); // Response handler app.use(function *(){ this.body = 'Hello'; }); app.listen(3000); 

考阿標誌
考阿

響應中間件yield下游進入響應處理程序,設置響應主體,在上游流程中(在yield表達式之後),允許進一步修改this.body ,以及其他功能,如時間記錄,這是可能的因為上游和下游共享相同的函數上下文。 這比 Express 功能強大得多,在 Express 中,完成相同事情的嘗試會像這樣結束:

 var start; // Downstream middleware app.use(function(req, res, next) { start = new Date; next(); // Already returned, cannot continue here }); // Response app.use(function (req, res, next){ res.send('Hello World') next(); }); // Upstream middleware app.use(function(req, res, next) { // res already sent, cannot modify var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); next(); }); app.listen(3000);

您可能已經發現這裡出了什麼問題; 使用“全局” start變量將導致競爭條件,並發請求返回廢話。 解決方案是一些不明顯的解決方法,您可以忘記修改上游流中的響應。

此外,使用 koa 時,您將免費獲得生成器異步工作流:

 app.use(function *(){ try { const part1 = yield fs.readFile(this.request.query.file1, 'utf8'); const part2 = yield fs.readFile(this.request.query.file2, 'utf8'); this.body = part1 + part2; } catch (err) { this.status = 404 this.body = err; } }); app.listen(3000);

您可以想像在 Express 中重新創建這個小示例所涉及的承諾和回調。

Node.js 徽標

所有這些 Node.js 討論與 React 有什麼關係? 好吧,在為 React 考慮合適的後端時,Node 是首選。 由於 Node 也是用 JavaScript 編寫的,它支持後端和前端之間的代碼共享,允許我們構建同構的 React Web 應用程序。 但是,稍後再談。

通量庫

React 擅長創建可組合的視圖組件,但我們需要一些方法來管理整個應用程序的數據和狀態。 幾乎普遍認為 React 是 Fl​​ux 應用程序架構的最佳補充。 如果您對 Flux 完全陌生,我建議您快速刷新一下。

通量標誌
通量

在眾多 Flux 實現中選擇哪一個尚未得到廣泛認同。 Facebook Flux 將是顯而易見的選擇,但對於大多數人來說,它過於冗長。 替代實現主要側重於使用約定優於配置的方法減少所需的樣板數量,以及一些用於高階組件、服務器端渲染等的便利功能。 可以在這裡看到一些具有各種受歡迎程度指標的頂級競爭者。 我研究過 Alt、Reflux、Flummox、Fluxxor 和 Marty.js。

我選擇正確庫的方式絕不是客觀的,但無論如何它可能會有所幫助。 Fluxxor 是我檢查的第一個庫之一,但現在它看起來有點陳舊。 Marty.js 很有趣,有很多功能,但仍然涉及很多樣板,有些功能似乎是多餘的。 Reflux 看起來很棒並且有一定的吸引力,但對於初學者來說感覺有點困難,並且也缺乏適當的文檔。 Flummox 和 Alt 非常相似,但 Alt 的樣板似乎更少,開發非常活躍,文檔最新,Slack 社區很有幫助。因此,我選擇了 Alt。

替代通量

Alt Flux 徽標
替代通量

使用 Alt,Flux 工作流程變得更加簡單,而不會失去任何功能。 Facebook 的 Flux 文檔對調度程序進行了很多說明,但我們可以忽略這一點,因為在 Alt 中,調度程序按照慣例隱式連接到操作,並且通常不需要任何自定義代碼。 這讓我們只剩下存儲操作組件。 這三層可以很好地映射到MVC思想模型中:商店是模型,動作是控制器,組件是視圖。 主要區別在於 Flux 模式的中心單向數據流,這意味著控制器(動作)不能直接修改視圖(組件),而是只能觸發模型(存儲)修改,視圖被動綁定到模型(存儲)。 對於一些開明的 Angular 開發人員來說,這已經是一個最佳實踐。

通量與替代工作流程

工作流程如下:

  1. 組件啟動動作。
  2. 商店監聽動作並更新數據。
  3. 組件綁定到存儲,並在數據更新時重新呈現。

行動

使用 Alt Flux 庫時,動作通常有兩種形式:自動和手動。 自動操作是使用generateActions函數創建的,它們直接發送給調度程序。 手動方法被定義為您的操作類的方法,它們可以通過額外的有效負載進入調度程序。 自動操作最常見的用例是通知商店有關應用程序中的某些事件。 除其他外,手動操作是處理服務器交互的首選方式。

所以 REST API 調用屬於操作。 完整的工作流程如下:

  1. 組件觸發一個動作。
  2. 動作創建者運行一個異步服務器請求,結果作為有效負載發送到調度程序。
  3. 存儲監聽動作,相應的動作處理程序接收結果作為參數,存儲相應地更新其狀態。

對於 AJAX 請求,我們可以使用 axios 庫,其中包括無縫處理 JSON 數據和標頭。 我們可以使用 ES7 async / await模式,而不是 promises 或 callbacks。 如果POST響應狀態不是 2XX,則拋出錯誤,我們分派返回的數據或接收到的錯誤。

讓我們看一個登錄頁面,了解 Alt 工作流程的一個簡單示例。 註銷動作不需要做任何特別的事情,只通知商店,這樣我們就可以自動生成了。 登錄動作是手動的,並且期望登錄數據作為動作創建者的參數。 在我們得到服務器的響應後,我們要么發送成功數據,要么,如果拋出錯誤,我們發送接收到的錯誤。

 class LoginActions { constructor() { // Automatic action this.generateActions('logout'); } // Manual action async login(data) { try { const response = await axios.post('/auth/login', data); this.dispatch({ok: true, user: response.data}); } catch (err) { console.error(err); this.dispatch({ok: false, error: err.data}); } } } module.exports = (alt.createActions(LoginActions));

專賣店

Flux 存儲有兩個目的:它有動作處理程序,並攜帶狀態。 讓我們繼續我們的登錄頁面示例,看看它是如何工作的。

讓我們創建具有兩個狀態屬性的LoginStoreuser ,用於當前登錄的用戶,以及error ,用於當前與登錄相關的錯誤。 本著減少樣板的精神,Alt 允許我們使用單個函數bindActions綁定到一個類中的所有操作。

 class LoginStore { constructor() { this.bindActions(LoginActions); this.user = null; this.error = null; } ...

處理程序名稱是按約定定義的, on相應的操作名稱之前。 因此login操作由onLogin處理,依此類推。 請注意,動作名稱的第一個字母將以駝峰形式大寫。 在我們的LoginStore中,我們有以下處理程序,由相應的操作調用:

 ... onLogin(data) { if (data.ok) { this.user = data.user; this.error = null; router.transitionTo('home'); } else { this.user = null; this.error = data.error } } onLogout() { this.user = null; this.error = null; } }

成分

將 store 綁定到組件的常用方法是使用某種 React mixin。 但是由於 mixins 已經過時了,所以需要一些其他的方式。 其中一種新方法是使用高階組件。 我們將我們的組件放入一個包裝組件中,該組件將負責監聽存儲和調用重新渲染。 我們的組件將在props中接收商店的狀態。 這種方法也有助於將我們的代碼組織成智能和愚蠢的組件,這些組件最近變得很流行。 對於 Alt,組件包裝器由AltContainer實現:

 export default class Login extends React.Component { render() { return ( <AltContainer stores={{LoginStore: LoginStore}}> <LoginPage/> </AltContainer> )} }

我們的LoginPage組件也使用了前面介紹的changeHandler裝飾器。 LoginStore中的數據用於在登錄失敗時顯示錯誤,並由AltContainer重新渲染。 單擊登錄按鈕執行login操作,完成 Alt 通量工作流程:

 @changeHandler export default class LoginPage extends React.Component { constructor(props) { super(props); this.state = { loginForm: {} }; } login() { LoginActions.login(this.state.loginForm) } render() { return ( <Alert>{{this.props.LoginStore.error}}</Alert> <Input label='Username' type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'loginForm', 'username')} /> <Input label='Password' type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'loginForm', 'password')} /> <Button onClick={this.login.bind(this)}>Login</Button> )} }

同構渲染

同構 Web 應用程序現在是一個熱門話題,因為它們解決了傳統單頁應用程序的一些最大難題。 在這些應用程序中,標記是由瀏覽器中的 JavaScript 動態創建的。 結果是內容對關閉 JavaScript 的客戶端不可用,尤其是搜索引擎網絡爬蟲。 這意味著您的網頁未編入索引,也不會出現在搜索結果中。 有一些方法可以解決這個問題,但它們遠非最佳。 同構方法試圖通過在服務器上預渲染請求的單頁應用程序的 URL 來解決這個問題。 使用 Node.js,您在服務器上擁有 JavaScript,這意味著 React 也可以在服務器端運行。 這應該不會太難吧?

一個障礙是某些 Flux 庫,尤其是那些使用單例的庫,在服務器端渲染方面存在困難。 當您有單例 Flux 存儲和多個並發請求到您的服務器時,數據將會混淆。 一些庫通過使用 Flux 實例解決了這個問題,但這也帶來了其他缺點,特別是需要在代碼中傳遞這些實例。 Alt 也提供了 Flux 實例,但它也解決了服務端渲染單例的問題; 它在每個請求之後刷新存儲,以便每個並發請求都以乾淨的狀態開始。

服務器端渲染功能的核心由React.renderToString提供。 整個 React 前端應用程序也在服務器上運行。 這樣,我們不需要等待客戶端 JavaScript 來創建標記; 它在服務器上為訪問的 URL 預先構建,並以 HTML 的形式發送到瀏覽器。 當客戶端 JavaScript 運行時,它會從服務器停止的地方繼續。 為了支持這一點,我們可以使用 Iso 庫,該庫旨在與 Alt 一起使用。

首先,我們使用alt.bootstrap在服務器上初始化 Flux。 可以使用數據預填充 Flux 存儲以進行渲染。 還需要決定為哪個 URL 渲染哪個組件,這是客戶端Router的功能。 我們正在使用alt的單例版本,因此在每次渲染之後,我們需要alt.flush()來清理存儲以處理另一個請求。 使用iso插件,將 Flux 的狀態序列化為 HTML 標記,以便客戶端知道從哪裡獲取:

 // We use react-router to run the URL that is provided in routes.jsx var getHandler = function(routes, url) { var deferred = Promise.defer(); Router.run(routes, url, function (Handler) { deferred.resolve(Handler); }); return deferred.promise; }; app.use(function *(next) { yield next; // We seed our stores with data alt.bootstrap(JSON.stringify(this.locals.data || {})); var iso = new Iso(); const handler = yield getHandler(reactRoutes, this.request.url); const node = React.renderToString(React.createElement(handler)); iso.add(node, alt.flush()); this.render('layout', {html: iso.render()}); });

在客戶端,我們獲取服務器狀態,並使用數據引導alt 。 然後我們在目標容器上運行RouterReact.render ,這將根據需要更新服務器生成的標記。

 Iso.bootstrap(function (state, _, container) { // Bootstrap the state from the server alt.bootstrap(state) Router.run(routes, Router.HistoryLocation, function (Handler, req) { let node = React.createElement(Handler) React.render(node, container) }) })

迷人的!

有用的前端庫

如果不提及一些與 React 配合得特別好的前端庫,React 生態系統的指南將是不完整的。 這些庫處理幾乎所有 Web 應用程序中最常見的任務:CSS 佈局和容器、樣式化的表單和按鈕、驗證、日期選擇等等。 當這些問題已經解決時,重新發明輪子是沒有意義的。

反應引導

React-Bootstrap 徽標

Twitter 的 Bootstrap 框架已經變得司空見慣,因為它對每個不想花大量時間在 CSS 上工作的 Web 開發人員都有巨大的幫助。 In the prototyping phase especially, Bootstrap is indispensable. To leverage the power of bootstrap in a React application, it's good to use it with React-Bootstrap, which comes with nice React syntax and re-implements Bootstrap's jQuery plugins using native React components. The resulting code is terse and easy to understand:

 <Navbar brand='React-Bootstrap'> <Nav> <NavItem eventKey={1} href='#'>Link</NavItem> <NavItem eventKey={2} href='#'>Link</NavItem> <DropdownButton eventKey={3} title='Dropdown'> <MenuItem eventKey='1'>Action</MenuItem> <MenuItem eventKey='2'>Another action</MenuItem> <MenuItem eventKey='3'>Something else here</MenuItem> <MenuItem divider /> <MenuItem eventKey='4'>Separated link</MenuItem> </DropdownButton> </Nav> </Navbar>

Personally, I cannot escape the feeling that this is what HTML should always have been like.

If you want to use Bootstrap source with Webpack, consider using less-loader or bootstrap-sass-loader, depending on the preprocessor you prefer. It will allow you to easily customize which Bootstrap components to include, and also allow the usage of LESS or SASS global variables in your CSS code.

反應路由器

React Router Logo

React Router has become the de-facto standard for routing in React. It allows nested routing, support for redirections, plays nicely with isomorphic rendering, and has a simple JSX-based syntax:

 <Router history={new BrowserHistory}> <Route path="/" component={App}> <Route path="about" name="about" component={About}/> <Route path="users" name="users" component={Users} indexComponent={RecentUsers}> <Route path="/user/:userId" name="user" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>

React Router also provides a Link component that you can use for navigation in your application, specifying only the route name:

 <nav> <Link to="about">About</Link> <Link to="users">Users</Link> </nav>

There is even a library for integration with React-Bootstrap, so if you are using Bootstrap's components and don't feel like setting the active class on them manually all the time, you can use react-router-bootstrap and write code like this:

 <Nav> <NavItemLink to="about">About</NavItemLink> <NavItemLink to="users">Users</NavItemLink> </Nav>

No additional setup is necessary. Active links will take care of themselves.

Formsy-React

Working with forms can be tedious, so let's get some help from the formsy-react library, which will help us manage validations and data models. The Formsy-React library, strangely enough, does not include the actual form inputs because users are encouraged to write their own (which is great). But if you are content with the common ones, just use formsy-react-components. Bootstrap classes are included:

 import Formsy from 'formsy-react'; import {Input} from 'formsy-react-components'; export default class FormsyForm extends React.Component { enableButton() { this.setState({canSubmit: true}); } disableButton() { this.setState({canSubmit: true}); } submit(model) { FormActions.saveEmail(model.email); } render() { return ( <Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}> <Input name="email" validations="isEmail" validationError="This is not a valid email" required/> <button type="submit" disabled={!this.state.canSubmit}>Submit</button> </Formsy.Form> )} }

Calendar and Typeahead

Calendar and typeahead are icing on the cake of every UI toolkit. Sadly, these two components were removed from Bootstrap 3, probably because they are too specialized for a general purpose CSS framework. Luckily, I've been able to find worthy replacements in react-pikaday and react-select. I've tested more than 10 libraries, and these two came out as the best. They are dead easy to use, as well:

 import Pikaday from 'react-pikaday'; import Select from 'react-select'; export default class CalendarAndTypeahead extends React.Component { constructor(props){ super(props); this.options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' } ]; } dateChange(date) { this.setState({date: date}); }, selectChange(selected) { this.setState({selected: selected}); }, render() { return ( <Pikaday value={this.state.date} onChange={this.dateChange} /> <Select name="form-field-name" value={this.state.selected} options={this.options} onChange={selectChange} /> )} }
When it comes to React, it's a jungle out there! Here's a map to help you find your way.
鳴叫

Conclusion - React.JS

In this article I've presented libraries and techniques that I consider some of the most productive in current web development. Some of them are React-specific, but due to React's open nature, many of them are usable in other environments as well. Technological progress is sometimes hindered by fear of the newest stuff, so I hope this article will help to dissipate doubts concerning React, Flux and the newest features in ECMAScript.

React Ecosystem

If you are interested, you can take a look at my example application built with these technologies. The source code is available on GitHub.

謝謝閱讀!

Related: React.js Best Practices and Tips by Toptal Developers