在個人研討會期間,更多的材料是關於JavaScript而不是React。其中大部分歸結爲JavaScript ES6以及功能和語法,但也包括三元運算符,語言中的簡寫版本,此對象,JavaScript內置函數(map,reduce,filter)或更常識性的概念,如:可組合性,可重用性,不變性或高階函數。這些是基礎知識,在開始使用React以前你不須要掌握這些基礎知識,但在學習或實踐它時確定會出現這些基礎知識。javascript
如下演練是我嘗試爲您提供一個幾乎普遍但簡明的列表,其中列出了全部不一樣的JavaScript功能,以補充您的React應用程序。若是您有任何其餘不在列表中的內容,只需對本文發表評論,我會及時更新。css
當你進入React的世界時,一般是使用用於啓動React項目的 create-react-app。設置項目後,您將遇到如下React類組件:html
import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; class App extends Component { render() { return ( <div> <header> <img src alt="logo" /> <h1>Welcome to React</h1> </header> <p> To get started, edit <code>src/App.js</code> and save to reload. </p> </div> ); } } export default App;
能夠說,React類組件可能不是最好的起點。新手有許多東西須要消化,不必定是React:類語句,類方法和繼承。導入語句也只是在學習React時增長了複雜性。儘管主要焦點應該是JSX(React的語法),但一般全部的事情都須要解釋。這篇文章應該揭示全部的東西,大部分是JavaScript,而不用擔憂React。java
在開始時遇到React類組件,只是須要有關JavaScript類的基礎。JavaScript類在語言中是至關新的。之前,只有JavaScript的原型鏈也能夠用於繼承。JavaScript類在原型繼承之上構建,使整個事物更簡單。react
定義React組件的一種方法是使用JavaScript類。爲了理解JavaScript類,您能夠花一些時間在沒有React的狀況下學習它們。ios
class Developer { constructor(firstname, lastname) { this.firstname = firstname; this.lastname = lastname; } getName() { return this.firstname + ' ' + this.lastname; } } var me = new Developer('Robin', 'Wieruch'); console.log(me.getName());
類描述了一個實體,該實體用做建立該實體實例的藍圖。一旦使用new
語句建立了類的實例,就會調用該類的構造函數,該實例化該類的實例。所以,類能夠具備一般位於其構造函數中的屬性。此外,類方法(例如getName())用於讀取(或寫入)實例的數據。類的實例在類中表示爲此對象,但實例外部僅指定給JavaScript變量。git
一般,類用於面向對象編程中的繼承。它們在JavaScript中用於相同的,而extends語句可用於從另外一個類繼承一個類。具備extends語句的更專業的類繼承了更通用類的全部功能,但能夠向其添加其專用功能。github
class Developer { constructor(firstname, lastname) { this.firstname = firstname; this.lastname = lastname; } getName() { return this.firstname + ' ' + this.lastname; } } class ReactDeveloper extends Developer { getJob() { return 'React Developer'; } } var me = new ReactDeveloper('Robin', 'Wieruch'); console.log(me.getName()); console.log(me.getJob());
基本上,它只須要徹底理解React類組件。 JavaScript類用於定義React組件,但正如您所看到的,React組件只是一個React組件,由於它繼承了從React包導入的React Component類的全部功能。編程
import React, { Component } from 'react'; class App extends Component { render() { return ( <div> <h1>Welcome to React</h1> </div> ); } } export default App;
這就是爲何render()方法在React類組件中是必需的:來自導入的React包的React組件指示您使用它在瀏覽器中顯示某些內容。此外,若是不從React組件擴展,您將沒法使用其餘生命週期方法 (包括render()方法)。例如,不存在componentDidMount()生命週期方法,由於該組件將是vanilla JavaScript類的實例。而且不只生命週期方法會消失,React的API方法(例如用於本地狀態管理的this.setState())也不可用。json
可是,正如您所看到的,使用JavaScript類有利於使用您的專業行爲擴展通用類。所以,您能夠引入本身的類方法或屬性。
import React, { Component } from 'react'; class App extends Component { getGreeting() { return 'Welcome to React'; } render() { return ( <div> <h1>{this.getGreeting()}</h1> </div> ); } } export default App;
如今您知道爲何React使用JavaScript類來定義React類組件。當您須要訪問React的API(生命週期方法,this.state和this.setState())時,可使用它們。在下文中,您將看到如何以不一樣的方式定義React組件,而不使用JavaScript類,由於您可能不須要始終使用類方法,生命週期方法和狀態。
畢竟,JavaScript類歡迎使用React中的繼承,這對於React來講不是一個理想的結果,由於React更喜歡組合而不是繼承。所以,您應該爲您的React組件擴展的惟一類應該是官方的React組件。
在教關於React時,我很早就解釋了JavaScript arrow functions。它是ES6的語法之一,它推進了JavaScript在函數式編程中的發展。
// JavaScript ES5 function function getGreeting() { return 'Welcome to JavaScript'; } // JavaScript ES6 arrow function with body const getGreeting = () => { return 'Welcome to JavaScript'; } // JavaScript ES6 arrow function without body and implicit return const getGreeting = () => 'Welcome to JavaScript';
JavaScript箭頭函數一般用在React應用程序中,以保持代碼簡潔和可讀。嘗試從JavaScript ES5到ES6功能重構個人功能。在某些時候,當JavaScript ES5函數和JavaScript ES6函數之間的差別很明顯時,我堅持使用JavaScript ES6的方式來實現箭頭函數。可是,我老是看到React新手的太多不一樣的語法可能會讓人不知所措。所以,我嘗試在使用它們在React中所有使用以前,使JavaScript函數的不一樣特性變得清晰。在如下部分中,您將瞭解如何在React中經常使用JavaScript箭頭函數。
React使用不一樣的編程範例,由於JavaScript是一種多方面的編程語言。在面向對象編程的時候,React的類組件是利用JavaScript類這一種方式(React組件API的繼承,類方法和類屬性,如this.state)。另外一方面,React(及其生態系統)中使用了不少的函數式編程的概念。例如,React的功能無狀態組件是另外一種在React中定義組件的方法。在React無狀態組件就引起了一個新的思考:組件如何像函數同樣使用?
function (props) { return view; }
它是一個接收輸入(例如props)並返回顯示的HTML元素(視圖)的函數(函數)。它不須要管理任何狀態(無狀態),也不須要了解任何方法(類方法,生命週期方法)。該函數只須要使用React組件中render()方法的呈現機制。那是在引入無狀態組件的時候。
function Greeting(props) { return <h1>{props.greeting}</h1>; }
無狀態組件是在React中定義組件的首選方法。它們具備較少的樣板,下降了複雜性,而且比React類組件更易於維護。可是,就目前而言,二者都有本身存在的意義。
之前,文章提到了JavaScript箭頭函數以及它們如何改進您的React代碼。讓咱們將這些函數應用於您的無狀態組件。
來看看Greeting組分別使用ES5和ES6不一樣的寫法:
// JavaScript ES5 function function Greeting(props) { return <h1>{props.greeting}</h1>; } // JavaScript ES6 arrow function const Greeting = (props) => { return <h1>{props.greeting}</h1>; } // JavaScript ES6 arrow function without body and implicit return const Greeting = (props) => <h1>{props.greeting}</h1>
JavaScript箭頭函數是在React中保持無狀態組件簡潔的好方法。當更多的時候沒有計算,所以能夠省略函數體和return語句。
React定義組件的方式隨着時間的推移而演變。在早期階段,React.createClass()方法是建立React類組件的默認方式。現在,它已再也不使用,由於隨着JavaScript ES6的興起,更多的是使用ES6的方法來建立React類組件。
然而,JavaScript不斷髮展,所以JavaScript愛好者一直在尋找新的作事方式。這就是爲何你會常常發現React類組件的不一樣語法。使用狀態和類方法定義React類組件的一種方法以下:
class Counter extends Component { constructor(props) { super(props); this.state = { counter: 0, }; this.onIncrement = this.onIncrement.bind(this); this.onDecrement = this.onDecrement.bind(this); } onIncrement() { this.setState(state => ({ counter: state.counter + 1 })); } onDecrement() { this.setState(state => ({ counter: state.counter - 1 })); } render() { return ( <div> <p>{this.state.counter}</p> <button onClick={this.onIncrement} type="button">Increment</button> <button onClick={this.onDecrement} type="button">Decrement</button> </div> ); } }
可是,當實現大量的React類組件時,構造函數中的class方法的綁定 以及首先具備構造函數變爲繁瑣的實現細節。幸運的是,有一個簡短的語法來擺脫這兩個煩惱:
class Counter extends Component { state = { counter: 0, }; onIncrement = () => { this.setState(state => ({ counter: state.counter + 1 })); } onDecrement = () => { this.setState(state => ({ counter: state.counter - 1 })); } render() { return ( <div> <p>{this.state.counter}</p> <button onClick={this.onIncrement} type="button">Increment</button> <button onClick={this.onDecrement} type="button">Decrement</button> </div> ); } }
經過使用JavaScript箭頭函數,您能夠自動綁定類方法,而無需在構造函數中綁定它們。經過將狀態直接定義爲類屬性,也能夠在不使用props時省略構造函數。 (注意:請注意,類屬性 還沒有使用JavaScript語言。)所以,您能夠說這種定義React類組件的方式比其餘版本更簡潔。
模板文字是JavaScript ES6附帶的另外一種JavaScript語言特定功能。值得一提的是,由於當JavaScript和React的新手看到它們時,它們也會讓人感到困惑。如下是你正在用的鏈接字符串的語法:
function getGreeting(what) { return 'Welcome to ' + what; } const greeting = getGreeting('JavaScript'); console.log(greeting); // Welcome to JavaScript
模板文字能夠用於相同的文字文字,稱爲字符串插值:
function getGreeting(what) { return Welcome to ${what}; }
您只需使用 ` `
和${}表示法來插入JavaScript原語。可是,字符串文字不只用於字符串插值,還用於JavaScript中的多行字符串:
function getGreeting(what) { return Welcome to ${what} ; }
基本上,這就是如何在多行上格式化更大的文本塊。最近在JavaScript中引入了GraphQL也能夠看出它 。
爲React新手教授JSX語法的最佳方法是什麼?一般我首先在render()方法中定義一個變量,並在返回塊中將其用做HTML中的JavaScript。
import React, { Component } from 'react'; class App extends Component { render() { var greeting = 'Welcome to React'; return ( <div> <h1>{greeting}</h1> </div> ); } } export default App;
您只需使用花括號來獲取HTML格式的JavaScript。從渲染字符串到複雜對象並無什麼不一樣。
import React, { Component } from 'react'; class App extends Component { render() { var user = { name: 'Robin' }; return ( <div> <h1>{user.name}</h1> </div> ); } } export default App;
一般接下來的問題是:如何呈現一個項目列表?在我看來,這是解釋React最好的部分之一。沒有特定於React的API,例如HTML標記上的自定義屬性,使您能夠在React中呈現多個項目。您可使用純JavaScript來迭代項目列表並返回每一個項目的HTML。
import React, { Component } from 'react'; class App extends Component { render() { var users = [ { name: 'Robin' }, { name: 'Markus' }, ]; return ( <ul> {users.map(function (user) { return <li>{user.name}</li>; })} </ul> ); } } export default App;
以前使用過JavaScript箭頭函數,你能夠擺脫箭頭函數體和return語句,使你的渲染輸出更加簡潔。
import React, { Component } from 'react'; class App extends Component { render() { var users = [ { name: 'Robin' }, { name: 'Markus' }, ]; return ( <ul> {users.map(user => <li>{user.name}</li>)} </ul> ); } } export default App;
很快,每一個React開發人員都習慣了數組的內置JavaScript map()方法。映射數組並返回每一個項的渲染輸出很是有意義。這一樣適用於自定義的狀況,其中filter()或reduce()更有意義,而不是爲每一個映射項呈現輸出。
import React, { Component } from 'react'; class App extends Component { render() { var users = [ { name: 'Robin', isDeveloper: true }, { name: 'Markus', isDeveloper: false }, ]; return ( <ul> {users .filter(user => user.isDeveloper) .map(user => <li>{user.name}</li>) } </ul> ); } } export default App;
一般,這就是React開發人員如何習慣這些JavaScript內置函數,而沒必要使用React特定的API。它只是HTML中的JavaScript。
使用var,let和const的不一樣變量聲明對於React的新手來講可能會形成混淆,即便它們不是React特定的。也許是由於當React變得流行時引入了JavaScript ES6。總的來講,我嘗試在個人工做室中儘早介紹let和const。它只是從在React組件中與const交換var開始:
import React, { Component } from 'react'; class App extends Component { render() { const users = [ { name: 'Robin' }, { name: 'Markus' }, ]; return ( <ul> {users.map(user => <li>{user.name}</li>)} </ul> ); } } export default App;
而後我給出了使用哪一個變量聲明的經驗法則:
雖然let一般用於for循環來遞增迭代器,但const一般用於保持JavaScript變量不變。儘管在使用const時能夠更改對象和數組的內部屬性,但變量聲明顯示了保持變量不變的意圖。
若是要在render中的JSX中使用if-else語句,可使用JavaScripts三元運算符來執行此操做:
import React, { Component } from 'react'; class App extends Component { render() { const users = [ { name: 'Robin' }, { name: 'Markus' }, ]; const showUsers = false; if (!showUsers) { return null; } return ( <ul> {users.map(user => <li>{user.name}</li>)} </ul> ); } } export default App;
import React, { Component } from 'react'; class App extends Component { render() { const users = [ { name: 'Robin' }, { name: 'Markus' }, ]; const showUsers = false; return ( <div> { showUsers ? ( <ul> {users.map(user => <li>{user.name}</li>)} </ul> ) : ( null ) } </div> ); } } export default App;
另外一種方法是,若是你只返回條件渲染的一邊,則使用&&運算符:
import React, { Component } from 'react'; class App extends Component { render() { const users = [ { name: 'Robin' }, { name: 'Markus' }, ]; const showUsers = false; return ( <div> { showUsers && ( <ul> {users.map(user => <li>{user.name}</li>)} </ul> ) } </div> ); } } export default App;
我不會詳細說明爲何會這樣,但若是你很好奇,你能夠在這裏瞭解它和條件渲染的其餘技術:React中的全部條件渲染。畢竟,React中的條件呈現僅再次顯示大多數React是JavaScript而不是React特定的任何內容。
幸運的是,JavaScript社區肯定了使用JavaScript ES6的import 和 export。
可是,對於React和JavaScript ES6來講,這些導入和導出語句只是另外一個須要在開始使用第一個React應用程序時須要解釋的主題。很早就有了CSS,SVG或其餘JavaScript文件的第一次導入。 create-react-app項目已經從那些import語句開始:
import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; class App extends Component { render() { return ( <div> <header> <img src alt="logo" /> <h1>Welcome to React</h1> </header> <p> To get started, edit <code>src/App.js</code> and save to reload. </p> </div> ); } } export default App;
這對初學者項目來講很是棒,由於它爲您提供了一個全面的體驗,能夠導入和導出其餘文件。 App組件也會在 _src/index.js_文件中導入。可是,在React中執行第一步時,我會嘗試在開始時避免這些導入。相反,我嘗試專一於JSX和React組件。只有在將另外一個文件中的第一個React組件或JavaScript函數分離時纔會引入導入和導出語句。
那麼這些導入和導出語句如何工做呢?假設您要在一個文件中導出如下變量:
const firstname = 'Robin'; const lastname = 'Wieruch'; export { firstname, lastname };
而後,您可使用第一個文件的相對路徑將它們導入到另外一個文件中:
import { firstname, lastname } from './file1.js'; console.log(firstname); // output: Robin
所以,它不必定是關於 importing/exporting 組件或函數,而是關於共享可分配給變量的全部東西(省略CSS或SVG導入/導出,但只談JS)。您還能夠將另外一個文件中的全部導出變量做爲一個對象導入:
import * as person from './file1.js'; console.log(person.firstname); // output: Robin
importing能夠有別名。您可能會從具備相同命名導出的多個文件中導入功能。這就是你可使用別名的緣由:
import { firstname as username } from './file1.js'; console.log(username); // output: Robin
之前的全部案例都被命名爲進口和出口。可是也存在默認聲明。它能夠用於一些用例:
const robin = { firstname: 'Robin', lastname: 'Wieruch', }; export default robin;
您能夠省略導入的大括號以導入默認導出:
import developer from './file1.js'; console.log(developer); // output: { firstname: 'Robin', lastname: 'Wieruch' }
此外,導入名稱可能與導出的默認名稱不一樣。您還能夠將它與命名的export和import語句一塊兒使用:
const firstname = 'Robin'; const lastname = 'Wieruch'; const person = { firstname, lastname, }; export { firstname, lastname, }; export default person;
並在另外一個文件中導入默認導出或命名導出:
import developer, { firstname, lastname } from './file1.js'; console.log(developer); // output: { firstname: 'Robin', lastname: 'Wieruch' } console.log(firstname, lastname); // output: Robin Wieruch
您還能夠節省額外的行並直接爲命名導出導出變量:
export const firstname = 'Robin'; export const lastname = 'Wieruch';
這些是ES6模塊的主要功能。它們能夠幫助您組織代碼,維護代碼和設計可重用的模塊API。您還能夠導出和導入功能以測試它們。
React只是應用程序的視圖層。 React提供了一些內部狀態管理,但除此以外,它只是一個爲您的瀏覽器呈現HTML的組件庫。其餘全部內容均可以從API(例如瀏覽器API,DOM API),JavaScript功能或外部庫中添加。選擇合適的庫來補充React應用程序並不老是很簡單,可是一旦您對不一樣的選項有了很好的概述,就能夠選擇最適合您的技術堆棧的庫。
例如,可使用本機fetch API在React中獲取數據:
import React, { Component } from 'react'; class App extends Component { state = { data: null, }; componentDidMount() { fetch('https://api.mydomain.com') .then(response => response.json()) .then(data => this.setState({ data })); } render() { ... } } export default App;
可是你可使用另外一個庫來獲取React中的數據。 Axios是React應用程序的一個流行選擇:
import React, { Component } from 'react'; import axios from 'axios'; class App extends Component { state = { data: null, }; componentDidMount() { axios.get('https://api.mydomain.com') .then(data => this.setState({ data })); } render() { ... } } export default App;
所以,一旦您瞭解了須要解決的問題,React普遍而創新的生態系統應該爲您提供大量解決方案 。這又不是關於React,而是瞭解全部可用於補充應用程序的不一樣JavaScript庫。
高階函數是一個很好的編程概念,特別是在轉向函數式編程時。在React中,瞭解這類函數是徹底有意義的,由於在某些時候你必須處理高階組件,這些組件在首先了解高階函數時能夠獲得最好的解釋。
能夠在早期的React中展現高階函數,而不會引入更高階的組件。例如,假設能夠根據輸入字段的值過濾呈現的用戶列表。
import React, { Component } from 'react'; class App extends Component { state = { query: '', }; onChange = event => { this.setState({ query: event.target.value }); } render() { const users = [ { name: 'Robin' }, { name: 'Markus' }, ]; return ( <div> <ul> {users .filter(user => this.state.query === user.name) .map(user => <li>{user.name}</li>) } </ul> <input type="text" onChange={this.onChange} /> </div> ); } } export default App;
並不老是但願提取函數,由於它能夠增長沒必要要的複雜性,但另外一方面,它能夠爲JavaScript帶來有益的學習效果。此外,經過提取函數,您能夠將其與React組件隔離開來進行測試。所以,讓咱們使用提供給內置過濾器功能的功能來展現它。
import React, { Component } from 'react'; function doFilter(user) { return this.state.query === user.name; } class App extends Component { ... render() { const users = [ { name: 'Robin' }, { name: 'Markus' }, ]; return ( <div> <ul> {users .filter(doFilter) .map(user => <li>{user.name}</li>) } </ul> <input type="text" onChange={this.onChange} /> </div> ); } } export default App;
以前的實現不起做用,由於doFilter()函數須要從狀態知道查詢屬性。所以,您能夠經過將其包含在另外一個致使更高階函數的函數中來將其傳遞給函數。
import React, { Component } from 'react'; function doFilter(query) { return function (user) { return this.state.query === user.name; } } class App extends Component { ... render() { const users = [ { name: 'Robin' }, { name: 'Markus' }, ]; return ( <div> <ul> {users .filter(doFilter(this.state.query)) .map(user => <li>{user.name}</li>) } </ul> <input type="text" onChange={this.onChange} /> </div> ); } } export default App;
基本上,高階函數是返回函數的函數。經過使用JavaScript ES6箭頭函數,您可使更高階的函數更簡潔。此外,這種速記版本使得將功能組合成功能更具吸引力。
const doFilter = query => user => this.state.query === user.name;
如今能夠從文件中導出doFilter()函數,並將其做爲純(高階)函數單獨測試。在瞭解了高階函數以後,創建了全部基礎知識,以便更多地瞭解React的高階組件。
將這些函數提取到React組件以外的(高階)函數中也能夠有利於單獨測試React的本地狀態管理。
export const doIncrement = state => ({ counter: state.counter + 1 }); export const doDecrement = state => ({ counter: state.counter - 1 }); class Counter extends Component { state = { counter: 0, }; onIncrement = () => { this.setState(doIncrement); } onDecrement = () => { this.setState(doDecrement); } render() { return ( <div> <p>{this.state.counter}</p> <button onClick={this.onIncrement} type="button">Increment</button> <button onClick={this.onDecrement} type="button">Decrement</button> </div> ); } }
圍繞代碼庫移動函數是瞭解在JavaScript中使用函數做爲拳頭類公民的好處的好方法。將代碼移向函數式編程時,它很是強大。
JavaScript中引入的另外一種語言特性稱爲解構。一般狀況下,您必須從您state或組件中的props訪問大量屬性。您能夠在JavaScript中使用解構賦值,而不是逐個將它們分配給變量。
// no destructuring const users = this.state.users; const counter = this.state.counter; // destructuring const { users, counter } = this.state;
這對功能無狀態組件特別有用,由於它們老是在函數簽名中接收props對象。一般,您不會使用道具而是使用道具,所以您能夠對功能簽名中已有的內容進行解構。
// no destructuring function Greeting(props) { return <h1>{props.greeting}</h1>; } // destructuring function Greeting({ greeting }) { return <h1>{greeting}</h1>; }
解構也適用於JavaScript數組。另外一個很棒的特徵是其他的解構。它一般用於拆分對象的一部分,但將剩餘屬性保留在另外一個對象中。
// rest destructuring const { users, ...rest } = this.state;
以後,可使用用戶進行渲染,例如在React組件中,而在其餘地方使用剩餘的狀態。這就是JavaScript擴展運算符 用於將其他對象轉發到下一個組件的位置。在下一節中,您將看到此運算符的運行狀況。
總之,有不少JavaScript能夠在React中使用。雖然React只有一個API表面區域,但開發人員必須習慣JavaScript提供的全部功能。這句話並不是沒有任何理由:「成爲React開發人員會讓你成爲更好的JavaScript開發人員」。讓咱們經過重構更高階的組件來回顧一下React中JavaScript的一些學習方面。
function withLoading(Component) { return class WithLoading extends { render() { const { isLoading, ...props } = this.props; if (isLoading) { return <p>Loading</p>; } return <Component { ...props } />; } } }; }
當isLoading prop設置爲true時,此高階組件僅用於顯示條件加載指示符。不然它呈現輸入組件。您已經能夠看到(休息)解構和傳播運算符。後者能夠在渲染的Component中看到,由於props對象的全部剩餘屬性都傳遞給Component。
使高階組件更簡潔的第一步是將返回的React類組件重構爲功能無狀態組件:
function withLoading(Component) { return function ({ isLoading, ...props }) { if (isLoading) { return <p>Loading</p>; } return <Component { ...props } />; }; }
您能夠看到其他的解構也能夠在函數的簽名中使用。接下來,使用JavaScript ES6箭頭函數使高階組件更簡潔:
const withLoading = Component => ({ isLoading, ...props }) => { if (isLoading) { return <p>Loading</p>; } return <Component { ...props } />; }
添加三元運算符可將函數體縮短爲一行代碼。所以能夠省略函數體,而且能夠省略return語句。
const withLoading = Component => ({ isLoading, ...props }) => isLoading ? <p>Loading</p> : <Component { ...props } />
如您所見,高階組件使用各類JavaScript而不是React相關技術:箭頭函數,高階函數,三元運算符,解構和擴展運算符。這就是如何在React應用程序中使用JavaScript的功能。
人們常常說學習React的學習曲線很陡峭。可是,只有將React留在等式中並將全部JavaScript排除在外。當其餘Web框架正在執行時,React不會在頂部添加任何外部抽象層。相反,你必須使用JavaScript。所以,磨練您的JavaScript技能,您將成爲一個偉大的React開發人員。