學習 React 必須知道的 JavaScript 基礎

react
javascript
Edit
學習 React 必須知道的 JavaScript 基礎

This is a translation of the original post JavaScript to Know for React by Kent C. Dodds.

與其他框架相比,我最喜歡 React 的點就是當你使用它時,你很大程度上是在使用 JavaScript。沒有模板 DSL(JSX 會編譯成清晰易懂的 JavaScript),組件 API 因 React Hooks 的加入而變得更簡單,並且該框架在解決核 心 UI 問題的同時,作了很少抽象。

因此,學習 JavaScript 特性對於你能否高效使用 React 構建應用程序非常重要。所以這 裡有一些 JavaScript 特性,我建議你花一些時間學習,這樣你就可以盡可能高效地使用 React 了。

在我們學習語法之前,另一個對 React 非常有用的理解是函數“閉包”的概念。這裡有一個 很好的關於這個概念的文章:mdn.io/closure

好的,讓我們來看看一些 React 開發有關的 JS 特性。

模板字符串 (Template Literals)

模板字符串像是有超能力的普通字符串:


_11
const greeting = 'Hello'
_11
const subject = 'World'
_11
console.log(`${greeting} ${subject}!`) // Hello World!
_11
_11
// 與此相同:
_11
console.log(greeting + ' ' + subject + '!')
_11
_11
// 在 React 裡:
_11
function Box({ className, ...props }) {
_11
return <div className={`box ${className}`} {...props} />
_11
}

MDN: Template Literals

快捷屬性名(Shorthand property names)

這非常普遍和有用,我現在會不假思索地這樣做。


_13
const a = 'hello'
_13
const b = 42
_13
const c = { d: [true, false] }
_13
console.log({ a, b, c })
_13
_13
// 與此相同:
_13
console.log({ a: a, b: b, c: c })
_13
_13
// 在 React 裡:
_13
function Counter({ initialCount, step }) {
_13
const [count, setCount] = useCounter({ initialCount, step })
_13
return <button onClick={setCount}>{count}</button>
_13
}

MDN: Object initializer New notations in ECMAScript 2015

箭頭函數

箭頭函數是在 JavaScript 中編寫函數的另一種方式,和傳統的函數相比存在一些語義差異 。幸運的是如果我們在 React 項目中使用 hooks (而不是 classes)的話,就不用再擔心 this 了。箭頭函數允許使用更簡潔的匿名函數和隱式 return,你會看到並使用大量的箭 頭函數功能。


_27
const getFive = () => 5
_27
const addFive = a => a + 5
_27
const divide = (a, b) => a / b
_27
_27
// 與此相同:
_27
function getFive() {
_27
return 5
_27
}
_27
function addFive(a) {
_27
return a + 5
_27
}
_27
function divide(a, b) {
_27
return a / b
_27
}
_27
_27
// 在 React 裡:
_27
function TeddyBearList({ teddyBears }) {
_27
return (
_27
<ul>
_27
{teddyBears.map(teddyBear => (
_27
<li key={teddyBear.id}>
_27
<span>{teddyBear.name}</span>
_27
</li>
_27
))}
_27
</ul>
_27
)
_27
}

上面的例子需要注意的一點是左括號(和右括號)。這是在使用 JSX 時利用箭頭函數的隱式 return 的常用方法。

MDN: Arrow Functions

解構(Destructuring)

解構可能是我最喜歡的 JavaScript 特性,經常用來解構對象和數組(如果你正在使用 useState 的話,你可能 也像這樣用) 。我很喜歡它的聲明性。


_25
// const obj = {x: 3.6, y: 7.8}
_25
// makeCalculation(obj)
_25
_25
function makeCalculation({ x, y: d, z = 4 }) {
_25
return Math.floor((x + d + z) / 3)
_25
}
_25
_25
// 與此相同
_25
function makeCalculation(obj) {
_25
const { x, y: d, z = 4 } = obj
_25
return Math.floor((x + d + z) / 3)
_25
}
_25
_25
// 與此相同
_25
function makeCalculation(obj) {
_25
const x = obj.x
_25
const d = obj.y
_25
const z = obj.z === undefined ? 4 : obj.z
_25
return Math.floor((x + d + z) / 3)
_25
}
_25
_25
// 在 React 裡:
_25
function UserGitHubImg({ username = 'ghost', ...props }) {
_25
return <img src={`https://github.com/${username}.png`} {...props} />
_25
}

MDN: Destructuring assignment

一定要閱讀 MDN 的文章,你肯定會學到一些新東西。讀完後嘗試使用單行解構重構它:


_22
function nestedArrayAndObject() {
_22
// 用一行解構代碼替換以下代碼
_22
const info = {
_22
title: 'Once Upon a Time',
_22
protagonist: {
_22
name: 'Emma Swan',
_22
enemies: [
_22
{ name: 'Regina Mills', title: 'Evil Queen' },
_22
{ name: 'Cora Mills', title: 'Queen of Hearts' },
_22
{ name: 'Peter Pan', title: `The boy who wouldn't grow up` },
_22
{ name: 'Zelena', title: 'The Wicked Witch' },
_22
],
_22
},
_22
}
_22
// const {} = info // <-- 用這種解構代碼形式替換下方的 `const` 開頭的代碼
_22
const title = info.title
_22
const protagonistName = info.protagonist.name
_22
const enemy = info.protagonist.enemies[3]
_22
const enemyTitle = enemy.title
_22
const enemyName = enemy.name
_22
return `${enemyName} (${enemyTitle}) is an enemy to ${protagonistName} in "${title}"`
_22
}

參數默認值(Parameter defaults)

這是另一個我常用的特性,以一種非常強大的聲明方式來表達函數的默認值。


_33
// add(1)
_33
// add(1, 2)
_33
function add(a, b = 0) {
_33
return a + b
_33
}
_33
_33
// 與此相同
_33
const add = (a, b = 0) => a + b
_33
_33
// 與此相同
_33
function add(a, b) {
_33
b = b === undefined ? 0 : b
_33
return a + b
_33
}
_33
_33
// 在 React 裡:
_33
function useLocalStorageState({
_33
key,
_33
initialValue,
_33
serialize = v => v,
_33
deserialize = v => v,
_33
}) {
_33
const [state, setState] = React.useState(
_33
() => deserialize(window.localStorage.getItem(key)) || initialValue,
_33
)
_33
_33
const serializedState = serialize(state)
_33
React.useEffect(() => {
_33
window.localStorage.setItem(key, serializedState)
_33
}, [key, serializedState])
_33
_33
return [state, setState]
_33
}

MDN: Default parameters

取餘值/展開(Rest/Spread)

... 語法是一種“集合(collection)”語法,它對集合值進行操作。我常用它,並強 烈建議你也學習並了解如何及何時使用它。實際上在不同的上下文中具它有不同的含義,因 此學習其中的細微差別將對你有所幫助。


_44
const arr = [5, 6, 8, 4, 9]
_44
Math.max(...arr)
_44
// 與此相同
_44
Math.max.apply(null, arr)
_44
_44
const obj1 = {
_44
a: 'a from obj1',
_44
b: 'b from obj1',
_44
c: 'c from obj1',
_44
d: {
_44
e: 'e from obj1',
_44
f: 'f from obj1',
_44
},
_44
}
_44
const obj2 = {
_44
b: 'b from obj2',
_44
c: 'c from obj2',
_44
d: {
_44
g: 'g from obj2',
_44
h: 'g from obj2',
_44
},
_44
}
_44
console.log({ ...obj1, ...obj2 })
_44
// 與此相同
_44
console.log(Object.assign({}, obj1, obj2))
_44
_44
function add(first, ...rest) {
_44
return rest.reduce((sum, next) => sum + next, first)
_44
}
_44
// 與此相同
_44
function add() {
_44
const first = arguments[0]
_44
const rest = Array.from(arguments).slice(1)
_44
return rest.reduce((sum, next) => sum + next, first)
_44
}
_44
_44
// 在 React 裡:
_44
function Box({ className, ...restOfTheProps }) {
_44
const defaultProps = {
_44
className: `box ${className}`,
_44
children: 'Empty box',
_44
}
_44
return <div {...defaultProps} {...restOfTheProps} />
_44
}

MDN: Spread syntax MDN: Rest parameters

ESM 模塊(ESModules)

如果你正在使用現代工具構建應用程序,那麼它很可能支持模塊(Modules),因此最好了 解語法的工作原理,因為即便規模很小的應用程序都可能需要使用模塊來進行代碼重用和組 織。


_46
export default function add(a, b) {
_46
return a + b
_46
}
_46
_46
/*
_46
* import add from './add'
_46
* console.assert(add(3, 2) === 5)
_46
*/
_46
_46
export const foo = 'bar'
_46
_46
/*
_46
* import {foo} from './foo'
_46
* console.assert(foo === 'bar')
_46
*/
_46
_46
export function subtract(a, b) {
_46
return a - b
_46
}
_46
_46
export const now = new Date()
_46
_46
/*
_46
* import {subtract, now} from './stuff'
_46
* console.assert(subtract(4, 2) === 2)
_46
* console.assert(now instanceof Date)
_46
*/
_46
_46
// 動態導入
_46
import('./some-module').then(
_46
allModuleExports => {
_46
// 這個 allModuleExports 和通過 import * as allModuleExports from './some-module' 導入的一樣
_46
// 唯一區別是這裡是異步導入的,在某些場景下有性能優勢
_46
},
_46
error => {
_46
// 處理錯誤
_46
// 如果加載或者運行模塊出錯,就會運行這裡的代碼
_46
},
_46
)
_46
_46
// 在 React 裡:
_46
import React, { Suspense, Fragment } from 'react'
_46
_46
// 動態導入 React 組件
_46
const BigComponent = React.lazy(() => import('./big-component'))
_46
// big-component.js 需要這樣 "export default BigComponent" 導出才行

MDN: import MDN: export

作為輔助資料,我有一個演講是關於這種語法的,你可以點擊觀看

條件運算符(Ternaries)

我喜歡條件運算符,它們具有優雅的聲明性,特別是在 JSX 中。


_30
const message = bottle.fullOfSoda
_30
? 'The bottle has soda!'
_30
: 'The bottle may not have soda :-('
_30
_30
// 與此相同
_30
let message
_30
if (bottle.fullOfSoda) {
_30
message = 'The bottle has soda!'
_30
} else {
_30
message = 'The bottle may not have soda :-('
_30
}
_30
_30
// 在 React 裡:
_30
function TeddyBearList({ teddyBears }) {
_30
return (
_30
<React.Fragment>
_30
{teddyBears.length ? (
_30
<ul>
_30
{teddyBears.map(teddyBear => (
_30
<li key={teddyBear.id}>
_30
<span>{teddyBear.name}</span>
_30
</li>
_30
))}
_30
</ul>
_30
) : (
_30
<div>There are no teddy bears. The sadness.</div>
_30
)}
_30
</React.Fragment>
_30
)
_30
}

我意識到在 Prettier 出現之前,一些人不得不忍受理解條件運算符而引起的厭惡反應。如果你還沒有使用 Prettier,我強烈建議你試一試,它將使您的條件運算符更易於閱讀。

MDN: Conditional (ternary) operator

數組方法(Array Methods)

數組很棒,我一直使用數組內置方法!最常用以下方法:

  • find
  • some
  • every
  • includes
  • map
  • filter
  • reduce

這裡有些例子:


_71
const dogs = [
_71
{
_71
id: 'dog-1',
_71
name: 'Poodle',
_71
temperament: [
_71
'Intelligent',
_71
'Active',
_71
'Alert',
_71
'Faithful',
_71
'Trainable',
_71
'Instinctual',
_71
],
_71
},
_71
{
_71
id: 'dog-2',
_71
name: 'Bernese Mountain Dog',
_71
temperament: ['Affectionate', 'Intelligent', 'Loyal', 'Faithful'],
_71
},
_71
{
_71
id: 'dog-3',
_71
name: 'Labrador Retriever',
_71
temperament: [
_71
'Intelligent',
_71
'Even Tempered',
_71
'Kind',
_71
'Agile',
_71
'Outgoing',
_71
'Trusting',
_71
'Gentle',
_71
],
_71
},
_71
]
_71
_71
dogs.find(dog => dog.name === 'Bernese Mountain Dog')
_71
// {id: 'dog-2', name: 'Bernese Mountain Dog', ...etc}
_71
_71
dogs.some(dog => dog.temperament.includes('Aggressive'))
_71
// false
_71
_71
dogs.some(dog => dog.temperament.includes('Trusting'))
_71
// true
_71
_71
dogs.every(dog => dog.temperament.includes('Trusting'))
_71
// false
_71
_71
dogs.every(dog => dog.temperament.includes('Intelligent'))
_71
// true
_71
_71
dogs.map(dog => dog.name)
_71
// ['Poodle', 'Bernese Mountain Dog', 'Labrador Retriever']
_71
_71
dogs.filter(dog => dog.temperament.includes('Faithful'))
_71
// [{id: 'dog-1', ..etc}, {id: 'dog-2', ...etc}]
_71
_71
dogs.reduce((allTemperaments, dog) => {
_71
return [...allTemperaments, ...dog.temperament]
_71
}, [])
_71
// [ 'Intelligent', 'Active', 'Alert', ...etc ]
_71
_71
// 在 React 裡:
_71
function RepositoryList({ repositories, owner }) {
_71
return (
_71
<ul>
_71
{repositories
_71
.filter(repo => repo.owner === owner)
_71
.map(repo => (
_71
<li key={repo.id}>{repo.name}</li>
_71
))}
_71
</ul>
_71
)
_71
}

MDN: Array

空值合併運算符(Nullish coalescing operator)

如果值為nullundefined時,你可能希望回退到某個默認值:


_26
// 我們經常這樣寫:
_26
x = x || 'some default'
_26
_26
// 但對於數字 0 或者布爾值 false 這些正常值來說,就會出問題
_26
_26
// 所以,如果想支持這樣的代碼
_26
add(null, 3)
_26
_26
// 那麼我們要做的是在計算之前:
_26
function add(a, b) {
_26
a = a == null ? 0 : a
_26
b = b == null ? 0 : b
_26
return a + b
_26
}
_26
_26
// 而現在我們可以這樣寫
_26
function add(a, b) {
_26
a = a ?? 0
_26
b = b ?? 0
_26
return a + b
_26
}
_26
_26
// 在 React 裡:
_26
function DisplayContactName({ contact }) {
_26
return <div>{contact.name ?? 'Unknown'}</div>
_26
}

MDN: Nullish coalescing operator

可選鏈操作符(Optional chaining)

也稱為“Elvis Operator”,它允許你安全地訪問屬性,並調用可能存在或不存在的函數。在 可選鏈接之前,我們使用了一種依賴於真/假值的變通方法。


_32
// 在可選鏈操作符發明之前,我們是這麼寫的:
_32
const streetName = user && user.address && user.address.street.name
_32
_32
// 現在我們可以:
_32
const streetName = user?.address?.street?.name
_32
_32
// 這裡即使 options 是 undefined (這裡 onSuccess 也將是 undefined)
_32
// 但是,如果 options 從未被聲明,則以下調用還是會失敗報錯
_32
// 可選鏈操作符無法在不存在的根 object 上調用
_32
// 可選鏈操作符無法代替 if (typeof options == "undefined") 這樣的類型檢查
_32
const onSuccess = options?.onSuccess
_32
_32
// 即使 onSuccess 是 undefined,這裡跑起來也不會出錯,因為沒有函數會被調用
_32
onSuccess?.({ data: 'yay' })
_32
_32
// 我們也可以把以上這些代碼合併成一行:
_32
options?.onSuccess?.({ data: 'yay' })
_32
_32
// 還有,如果你 100% 確定 options 是存在的,而且 onSuccess 是一個函數
_32
// 那麼在調用前,你就不需要這個額外的 ?.
_32
// 只在左邊的值有可能不存在的情況下才用 ?.
_32
options?.onSuccess({ data: 'yay' })
_32
_32
// 在 React 裡:
_32
function UserProfile({ user }) {
_32
return (
_32
<div>
_32
<h1>{user.name}</h1>
_32
<strong>{user.bio?.short ?? 'No bio provided'}</strong>
_32
</div>
_32
)
_32
}

要注意的是,如果你發現自己在代碼中過多使用了?.,你可能需要考慮這些值的來源,並 確保它們始終如一地返回應有的值。 MDN: Optional chaining

Promises 和 async/await

這是一個很大的主題,可能需要一些時間去練習和使用才能真正掌握他們。 Promise 在 JavaScript 生態系統中無處不在,並且由於 React 也植根於該生態系統中,因此 Promise 在 React 中也無處不在(事實上,React 在內部使用了 Promise)。

Promise 可幫助你管理異步代碼,並從許多 DOM API 以及第三方庫獲取返回值。 Async/await 是處理 Promise 的特殊語法,兩者相輔相成。


_94
function promises() {
_94
const successfulPromise = timeout(100).then(result => `success: ${result}`)
_94
_94
const failingPromise = timeout(200, true).then(null, error =>
_94
Promise.reject(`failure: ${error}`),
_94
)
_94
_94
const recoveredPromise = timeout(300, true).then(null, error =>
_94
Promise.resolve(`failed and recovered: ${error}`),
_94
)
_94
_94
successfulPromise.then(log, logError)
_94
failingPromise.then(log, logError)
_94
recoveredPromise.then(log, logError)
_94
}
_94
_94
function asyncAwaits() {
_94
async function successfulAsyncAwait() {
_94
const result = await timeout(100)
_94
return `success: ${result}`
_94
}
_94
_94
async function failedAsyncAwait() {
_94
const result = await timeout(200, true)
_94
return `failed: ${result}` // 這裡不會運行
_94
}
_94
_94
async function recoveredAsyncAwait() {
_94
try {
_94
const result = await timeout(300, true)
_94
return `failed: ${result}` // 這裡不會運行
_94
} catch (error) {
_94
return `failed and recovered: ${error}`
_94
}
_94
}
_94
_94
successfulAsyncAwait().then(log, logError)
_94
failedAsyncAwait().then(log, logError)
_94
recoveredAsyncAwait().then(log, logError)
_94
}
_94
_94
function log(...args) {
_94
console.log(...args)
_94
}
_94
_94
function logError(...args) {
_94
console.error(...args)
_94
}
_94
_94
// 這是異步代碼運行的核心
_94
function timeout(duration = 0, shouldReject = false) {
_94
return new Promise((resolve, reject) => {
_94
setTimeout(() => {
_94
if (shouldReject) {
_94
reject(`rejected after ${duration}ms`)
_94
} else {
_94
resolve(`resolved after ${duration}ms`)
_94
}
_94
}, duration)
_94
})
_94
}
_94
_94
// 在 React 裡:
_94
function GetGreetingForSubject({ subject }) {
_94
const [isLoading, setIsLoading] = React.useState(false)
_94
const [error, setError] = React.useState(null)
_94
const [greeting, setGreeting] = React.useState(null)
_94
_94
React.useEffect(() => {
_94
async function fetchGreeting() {
_94
try {
_94
const response = await window.fetch('https://example.com/api/greeting')
_94
const data = await response.json()
_94
setGreeting(data.greeting)
_94
} catch (error) {
_94
setError(error)
_94
} finally {
_94
setIsLoading(false)
_94
}
_94
}
_94
setIsLoading(true)
_94
fetchGreeting()
_94
}, [])
_94
_94
return isLoading ? (
_94
'loading...'
_94
) : error ? (
_94
'ERROR!'
_94
) : greeting ? (
_94
<div>
_94
{greeting} {subject}
_94
</div>
_94
) : null
_94
}

MDN: Promise

MDN: async function

MDN: await

結論

當然,還有很多語言特性在構建 React 應用程序時很有用,但以上這些是我最喜歡且常用 的特性,希望對你有幫助。

如果你想深入研究其中的任何一個,我確有一個 JavaScript 研討會是我在 PayPal 工作時 記錄下的,可能會對你有所幫助: ES6 and Beyond Workshop at PayPal

祝你好運!