在開始React之前,你需要學好這些JavaScript

在開始 React 之前先學習 JavaScript

當你開始進入 React 的世界,create-react-app(https://github.com/facebook/create-react-app)通常會是你的第一個 React 項目。在搭建好項目後,你將看到下面的這個 React 類組件:

import React, { Component } from 'react';

import logo from './logo.svg';

import './App.css';

class App extends Component {

render() {

return (

logo

Welcome to React

To get started, edit src/App.js and save to reload.

);

}

}

export default App;

React 類組件可能不是最好的入手點。新手有許多東西需要消化,不一定與 React 有關:類語句、類方法和繼承。導入語句也會在學習 React 時增加額外的複雜性。儘管主要焦點應該放在 JSX(React 的語法)上,但其他的東西也需要解釋一番。這篇文章主要針對 JavaScript,所以請不用太擔心 React 相關的內容。

React 和 JavaScript 類

關於 React 類組件,需要用到有關 JavaScript 類的先驗知識。JavaScript 類的概念相對較新。之前,只有 JavaScript 的原型鏈可用於實現繼承。JavaScript 類以原型繼承為基礎,讓繼承體系變得更簡單。

定義 React 組件的一種方法是使用 JavaScript 類。

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())用於讀取(或寫入)實例的數據。類的實例在類中使用 this 對象來表示,但在外部,僅指定給 JavaScript 變量。

在面向對象編程中,類通常用來實現繼承。在 JavaScript 中也一樣,extends 語句可用於讓一個類繼承另一個類。一個子類通過 extends 語句繼承了一個父類的所有功能,還可以添加自己的功能。

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 Component 類的所有功能。

import React, { Component } from 'react';

class App extends Component {

render() {

return (

Welcome to React

);

}

}

export default App;

這就是為什麼 render() 方法在 React 類組件中是必需的:從 React 包導入的 React Component 用它在瀏覽器中顯示某些內容。此外,如果不從 React Component 繼承,將無法使用其他生命週期方法(包括 render() 方法)。例如,如果不繼承,那麼 componentDidMount() 生命週期方法就不存在,因為現在這個類只是一個普通 JavaScript 類的實例。除了生命週期方法不可用,React 的 API 方法(例如用於本地狀態管理的 this.setState())也不可用。

我們可以通過 JavaScript 類來擴展通用類的行為。因此,我們可以引入自己的類方法或屬性。

import React, { Component } from 'react';

class App extends Component {

getGreeting() {

return 'Welcome to React';

}

render() {

return (

{this.getGreeting()}

);

}

}

export default App;

現在你應該知道為什麼 React 使用 JavaScript 類來定義 React 類組件。當你需要訪問 React 的 API(生命週期方法、this.state 和 this.setState())時,可以使用它們。接下來,你將看到如何以不同的方式定義 React 組件,比如不使用 JavaScript 類,因為有時候你可能不需要使用類方法、生命週期方法或狀態。

儘管我們可以在 React 中使用 JavaScript 類繼承,但這對於 React 來說不是一個理想的結果,因為 React 更傾向於使用組合而不是繼承。因此,你的 React 組件需要擴展的唯一類應該是 React Component。

React 中的箭頭函數

在給別人培訓 React 時,我在一開始就向他們解釋 JavaScript 的箭頭函數。箭頭函數是 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';

在 React 應用程序中使用 JavaScript 箭頭函數通常是為了讓代碼保持簡潔和可讀。我很喜歡箭頭函數,總是嘗試將我的函數從 JavaScript ES5 重構成 ES6。在某些時候,當 JavaScript ES5 函數和 JavaScript ES6 函數之間的差異很明顯時,我會使用 JavaScript ES6 的箭頭函數。不過,對 React 新手來說,太多不同的語法可能會讓人不知所措。因此,在 React 中使用它們之前,我會嘗試解釋 JavaScript 函數的不同特點。在以下部分,你將瞭解到如何在 React 中使用 JavaScript 箭頭函數。

在 React 中將函數視為組件

React 使用了不同的編程範式,這要歸功於 JavaScript 是一門“多面手”編程語言。在面向對象編程方面,React 的類組件可以很好地利用 JavaScript 類(React 組件 API 的繼承、類方法和類屬性,如 this.state)。另一方面,React(及其生態系統)也使用了很多函數式編程的概念。例如,React 的函數無狀態組件是另一種定義 React 組件的方式。那麼,如果可以像函數那樣使用組件,將會怎樣?

function (props) {

return view;

}

這是一個接收輸入(例如 props)並返回 HTML 元素(視圖)的函數。它不需要管理任何狀態(無狀態),也不需要了解任何方法(類方法、生命週期方法)。這個函數只需要使用 React 組件的 render() 方法來進行渲染。

function Greeting(props) {

return

{props.greeting}

;

}

功能無狀態組件是在 React 中定義組件的首選方法。它們的樣板代碼較少,複雜性較低,並且比 React 類組件更易於維護。不過,這兩者都有自己存在的理由。

之前提到了 JavaScript 箭頭函數以及它們可以提升代碼的可讀性,現在讓我們將這些函數應用無狀態組件中。之前的 Greeting 組件在 JavaScript ES5 和 ES6 中的寫法有點不一樣:

// JavaScript ES5 function

function Greeting(props) {

return

{props.greeting}

;

}

// JavaScript ES6 arrow function

const Greeting = (props) => {

return

{props.greeting}

;

}

// JavaScript ES6 arrow function without body and implicit return

const Greeting = (props) =>

{props.greeting}

JavaScript 箭頭函數是讓 React 無狀態組件保持簡潔的一個不錯的方法。

React 類組件語法

React 定義組件的方式一直在演化。在早期階段,React.createClass() 方法是創建 React 類組件的默認方式。現在不再使用這個方法,因為隨著 JavaScript 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 (

{this.state.counter}

);

}

}

不過,在實現大量的 React 類組件時,構造函數中的類方法綁定和構造函數本身就變成了繁瑣的實現細節。所運的是,有一個簡短的語法可用來擺脫這兩個煩惱:

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 (

{this.state.counter}

);

}

}

通過使用 JavaScript 箭頭函數,可以自動綁定類方法,不需要在構造函數中綁定它們。通過將狀態直接定義為類屬性,在不使用 props 時就可以省略構造函數。(注意:請注意,JavaScript 還不支持類屬性。)因此,你可以說這種定義 React 類組件的方式比其他版本更簡潔。

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 原語。字符串字面量不僅可用於字符串插值,還可用於多行字符串:

function getGreeting(what) {

return `

Welcome

to

${what}

`;

}

這樣就可以格式化多行文本塊。

React 中 Map、Reduce 和 Filter

在向 React 新手教授 JSX 語法時,我通常會先在 render() 方法中定義一個變量,然後將其用在返回代碼塊中。

import React, { Component } from 'react';

class App extends Component {

render() {

var greeting = 'Welcome to React';

return (

{greeting}

);

}

}

export default App;

你只需使用花括號來操作 HTML 中的 JavaScript。不管是渲染字符串還是渲染一個複雜的對象,並沒有太大不同。

import React, { Component } from 'react';

class App extends Component {

render() {

var user = { name: 'Robin' };

return (

{user.name}

);

}

}

export default App;

接下來的問題是:如何渲染項目列表?React 沒有提供特定的 API(例如 HTML 標記的自定義屬性)用於渲染項目列表。我們可以使用純 JavaScript 代碼來迭代項目列表,並返回每個項目的 HTML。

import React, { Component } from 'react';

class App extends Component {

render() {

var users = [

{ name: 'Robin' },

{ name: 'Markus' },

];

return (

    {users.map(function (user) {

    return

  • {user.name}
  • ;

    })}

);

}

}

export default App;

通過使用 JavaScript 箭頭函數,你可以擺脫箭頭函數體和 return 語句,讓渲染輸出更加簡潔。

import React, { Component } from 'react';

class App extends Component {

render() {

var users = [

{ name: 'Robin' },

{ name: 'Markus' },

];

return (

    {users.map(user =>

  • {user.name}
  • )}

);

}

}

export default App;

很快,每個 React 開發人員都習慣了 JavaScript 內置的 map() 方法。對數組進行 map 並返回每個項的渲染輸出,這樣做非常有用。在某系情況下,結合使用 filter() 或 reduce() 會更有用,而不只是為每個被 map 的項渲染輸出。

import React, { Component } from 'react';

class App extends Component {

render() {

var users = [

{ name: 'Robin', isDeveloper: true },

{ name: 'Markus', isDeveloper: false },

];

return (

    {users

    .filter(user => user.isDeveloper)

    .map(user =>

  • {user.name}
  • )

    }

);

}

}

export default App;

通常,React 開發人員習慣於使用 JavaScript 的這些內置函數,而不必使用 React 特定的 API。它只是 HTML 中的 JavaScript。

React 中的 var、let 和 const

對於 React 的新手來說,使用 var、let 和 const 來聲明變量可能也會給他們造成混淆,雖然它們不是 React 相關的。我會嘗試在教學中儘早介紹 let 和 const,並從在 React 組件中交替使用 const 和 var 開始:

import React, { Component } from 'react';

class App extends Component {

render() {

const users = [

{ name: 'Robin' },

{ name: 'Markus' },

];

return (

    {users.map(user =>

  • {user.name}
  • )}

);

}

}

export default App;

然後我給出了一些使用這些變量聲明的經驗法則:

(1)不要使用 var,因為 let 和 const 更具體

(2)默認使用 const,因為它不能被重新分配或重新聲明

(3)如果要重新賦值變量則使用 let

let 通常用於 for 循環中,const 通常用於保持 JavaScript 變量不變。儘管在使用 const 時可以修改對象和數組的內部屬性,但變量聲明表達了保持變量不變的意圖。

React 中的三元運算符

如果要通過 if-else 語句進行條件渲染該怎麼辦?我們不能直接在 JSX 中使用 if-else 語句,但可以從渲染函數中提前返回。如果不需要顯示內容,返回 null 在 React 中是合法的。

import React, { Component } from 'react';

class App extends Component {

render() {

const users = [

{ name: 'Robin' },

{ name: 'Markus' },

];

const showUsers = false;

if (!showUsers) {

return null;

}

return (

    {users.map(user =>

  • {user.name}
  • )}

);

}

}

export default App;

不過,如果要在返回的 JSX 中使用 if-else 語句,可以使用 JavaScript 的三元運算符:

import React, { Component } from 'react';

class App extends Component {

render() {

const users = [

{ name: 'Robin' },

{ name: 'Markus' },

];

const showUsers = false;

return (

{

showUsers ? (

    {users.map(user =>

  • {user.name}
  • )}

) : (

null

)

}

);

}

}

export default App;

如果你只返回條件渲染的一個方面,可以使用 && 運算符:

import React, { Component } from 'react';

class App extends Component {

render() {

const users = [

{ name: 'Robin' },

{ name: 'Markus' },

];

const showUsers = false;

return (

{

showUsers && (

    {users.map(user =>

  • {user.name}
  • )}

)

}

);

}

}

export default App;

詳細的原理我就不說了,但如果你感興趣,可以在這篇文章(https://www.robinwieruch.de/conditional-rendering-react/)裡瞭解到更詳細的信息以及與條件渲染相關的其他技術。React 中的條件渲染告訴我們,大多數 React 都是與 JavaScript 有關,而不是 React 特定的內容。

React 中的導入和導出語句

在 JavaScript 中,我們可以通過 import 和 export 語句來導入和導出在 JavaScript ES6 文件中定義的功能。

在開始你的第一個 React 應用程序之前,這些 import 和 export 語句是另一個需要了解的話題。create-react-app 項目已經在使用 import 語句:

import React, { Component } from 'react';

import logo from './logo.svg';

import './App.css';

class App extends Component {

render() {

return (

logo

Welcome to React

To get started, edit src/App.js and save to reload.

);

}

}

export default App;

這對初始項目來說非常棒,因為它為你提供了一個全面的體驗,可以導入和導出其他文件。不過,在剛開始接觸 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

因此,它不一定只是與導入或導出組件或函數有關,它可以是共享可分配給變量的所有東西(我們只談 JS)。你還可以將另一個文件導出的所有變量作為一個對象導入:

import * as person from './file1.js';

console.log(person.firstname);

// output: Robin

導入可以有別名。當從多個文件導入具有相同導出名稱的功能時,就需要用到別名。

import { firstname as username } from './file1.js';

console.log(username);

// output: Robin

之前所有的例子都是命名的導入和導出。除此之外,還有默認的導入和導出。它可以用於以下一些場景:

  • 導出和導入單個功能;
  • 強調一個模塊導出 API 的主要功能;
  • 作為導入功能的後備。

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 只是應用程序的視圖層。React 提供了一些內部狀態管理,但除此之外,它只是一個為瀏覽器渲染 HTML 的組件庫。API(例如瀏覽器 API、DOM API)、JavaScript 或外部庫可以為 React 添加額外的東西。為 React 應用程序選擇合適的庫並不是件容易的事,但一旦你對不同的庫有了很好的瞭解,就可以選擇最適合你的技術棧的庫。

例如,我們可以使用 React 原生的獲取數據的 API 來獲取數據:

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;

但你也可以使用另一個庫來獲取數據,Axios 就是這樣的一個流行庫:

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 本身無關,而是有關了解如何選擇可用於彌補 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 (

    {users

    .filter(user => this.state.query === user.name)

    .map(user =>

  • {user.name}
  • )

    }

type="text"

onChange={this.onChange}

/>

);

}

}

export default App;

我們並不總是希望通過提取函數的方式來實現,因為這樣會增加不必要的複雜性。但是,通過提取函數,我們可以對其進行單獨的測試。因此,讓我們使用內置的 filter 函數來實現這個例子。

import React, { Component } from 'react';

function doFilter(user) {

return query === user.name;

}

class App extends Component {

...

render() {

const users = [

{ name: 'Robin' },

{ name: 'Markus' },

];

return (

    {users

    .filter(doFilter)

    .map(user =>

  • {user.name}
  • )

    }

type="text"

onChange={this.onChange}

/>

);

}

}

export default App;

這個實現還起不到作用,因為 doFilter() 函數需要知道 state 的 query 屬性。我們可以通過另一個包裝函數來傳遞它,也就是高階函數。

import React, { Component } from 'react';

function doFilter(query) {

return function (user) {

return query === user.name;

}

}

class App extends Component {

...

render() {

const users = [

{ name: 'Robin' },

{ name: 'Markus' },

];

return (

    {users

    .filter(doFilter(this.state.query))

    .map(user =>

  • {user.name}
  • )

    }

type="text"

onChange={this.onChange}

/>

);

}

}

export default App;

基本上,高階函數是可以返回函數的函數。通過使用 JavaScript ES6 的箭頭函數,你可以讓高階函數變得更簡潔。此外,這種簡化的方式讓將函數組合成函數變得更吸引人。

const doFilter = query => user =>

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 (

{this.state.counter}

);

}

}

函數式編程非常強大,轉向函數式編程有助於瞭解 JavaScript 將函數作為一等公民所帶來的好處。

React 中的解構和展開運算符

JavaScript 中引入的另一種語言特性稱為解構。通常情況下,你需要在組件的 state 或 props 中訪問大量的屬性。你可以在 JavaScript 中使用解構賦值,而不是逐個將它們分配給變量。

// no destructuring

const users = this.state.users;

const counter = this.state.counter;

// destructuring

const { users, counter } = this.state;

這對函數式無狀態組件來說特別有用,因為它們可以在函數簽名中收到 props 對象。通常,你用到的不是 props,而是 props 裡的內容,因此你可以對函數簽名中已有的內容進行解構。

// no destructuring

function Greeting(props) {

return

{props.greeting}

;

}

// destructuring

function Greeting({ greeting }) {

return

{greeting}

;

}

解構也適用於 JavaScript 數組。另一個很棒的特性是剩餘解構。它通常用於拆分對象的一部分屬性,並將剩餘屬性保留在另一個對象中。

// rest destructuring

const { users, ...rest } = this.state;

uesrs 可以在 React 組件中渲染,而剩餘狀態可以用在其他地方。這就是 JavaScript 展開(spread)運算髮揮作用的地方,它可以將對象的剩餘部分轉到下一個組件。

JavaScript 多過 React

React 只提供了一個細小的 API 表面區域,因此開發人員必須習慣於 JavaScript 提供的所有功能。這句話並非沒有任何理由:“成為 React 開發者也會讓你成為更好的 JavaScript 開發者”。讓我們通過重構一個高階組件來回顧一下學到的 JavaScript 的一些方面。

function withLoading(Component) {

return class WithLoading extends {

render() {

const { isLoading, ...props } = this.props;

if (isLoading) {

return

Loading

;

}

return ;

}

}

};

}

這個高階組件用於顯示條件加載進度條,當 isLoading 被設為 true 時,就可以顯示加載進度條,否則就渲染輸入組件。在這裡可以看到(剩餘)解構和展開運算符的實際應用。後者可以在渲染的 Component 中看到,因為 props 對象的剩餘屬性被傳給了那個 Component。

讓高階組件變得更簡潔的第一步是將返回的 React 類組件重構為函數式無狀態組件:

function withLoading(Component) {

return function ({ isLoading, ...props }) {

if (isLoading) {

return

Loading

;

}

return ;

};

}

可以看到,剩餘解構也可以被用在函數的簽名中。接下來,使用 JavaScript ES6 箭頭函數讓高階組件變得更加簡潔:

const withLoading = Component => ({ isLoading, ...props }) => {

if (isLoading) {

return

Loading

;

}

return ;

}

通過使用三元運算符可以將函數體縮短為一行代碼。因此可以省略函數體和 return 語句。

const withLoading = Component => ({ isLoading, ...props }) =>

isLoading

?

Loading

:

如你所見,高階組件使用的是各種 JavaScript 而不是 React 相關技術:箭頭函數、高階函數、三元運算符、解構和展開運算符。

為了幫助小夥伴們更好的學習Python,技術學派整理了Python的相關學習視頻及學習路線圖。

領取方式

關注“技術學派”後,評論轉發文章,私信回覆:Python學習


分享到:


相關文章: