學習 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)
模板字符串像是有超能力的普通字符串:
_11const greeting = 'Hello'_11const subject = 'World'_11console.log(`${greeting} ${subject}!`) // Hello World!_11_11// 與此相同:_11console.log(greeting + ' ' + subject + '!')_11_11// 在 React 裡:_11function Box({ className, ...props }) {_11 return <div className={`box ${className}`} {...props} />_11}
快捷屬性名(Shorthand property names)
這非常普遍和有用,我現在會不假思索地這樣做。
_13const a = 'hello'_13const b = 42_13const c = { d: [true, false] }_13console.log({ a, b, c })_13_13// 與此相同:_13console.log({ a: a, b: b, c: c })_13_13// 在 React 裡:_13function 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,你會看到並使用大量的箭
頭函數功能。
_27const getFive = () => 5_27const addFive = a => a + 5_27const divide = (a, b) => a / b_27_27// 與此相同:_27function getFive() {_27 return 5_27}_27function addFive(a) {_27 return a + 5_27}_27function divide(a, b) {_27 return a / b_27}_27_27// 在 React 裡:_27function 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 的常用方法。
解構(Destructuring)
解構可能是我最喜歡的 JavaScript 特性,經常用來解構對象和數組(如果你正在使用
useState
的話,你可能
也像這樣用)
。我很喜歡它的聲明性。
_25// const obj = {x: 3.6, y: 7.8}_25// makeCalculation(obj)_25_25function makeCalculation({ x, y: d, z = 4 }) {_25 return Math.floor((x + d + z) / 3)_25}_25_25// 與此相同_25function makeCalculation(obj) {_25 const { x, y: d, z = 4 } = obj_25 return Math.floor((x + d + z) / 3)_25}_25_25// 與此相同_25function 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 裡:_25function UserGitHubImg({ username = 'ghost', ...props }) {_25 return <img src={`https://github.com/${username}.png`} {...props} />_25}
一定要閱讀 MDN 的文章,你肯定會學到一些新東西。讀完後嘗試使用單行解構重構它:
_22function 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)_33function add(a, b = 0) {_33 return a + b_33}_33_33// 與此相同_33const add = (a, b = 0) => a + b_33_33// 與此相同_33function add(a, b) {_33 b = b === undefined ? 0 : b_33 return a + b_33}_33_33// 在 React 裡:_33function 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}
取餘值/展開(Rest/Spread)
該 ...
語法是一種“集合(collection)”語法,它對集合值進行操作。我常用它,並強
烈建議你也學習並了解如何及何時使用它。實際上在不同的上下文中具它有不同的含義,因
此學習其中的細微差別將對你有所幫助。
_44const arr = [5, 6, 8, 4, 9]_44Math.max(...arr)_44// 與此相同_44Math.max.apply(null, arr)_44_44const 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}_44const 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}_44console.log({ ...obj1, ...obj2 })_44// 與此相同_44console.log(Object.assign({}, obj1, obj2))_44_44function add(first, ...rest) {_44 return rest.reduce((sum, next) => sum + next, first)_44}_44// 與此相同_44function 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 裡:_44function 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),因此最好了 解語法的工作原理,因為即便規模很小的應用程序都可能需要使用模塊來進行代碼重用和組 織。
_46export 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_46export const foo = 'bar'_46_46/*_46 * import {foo} from './foo'_46 * console.assert(foo === 'bar')_46 */_46_46export function subtract(a, b) {_46 return a - b_46}_46_46export 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// 動態導入_46import('./some-module').then(_46 allModuleExports => {_46 // 這個 allModuleExports 和通過 import * as allModuleExports from './some-module' 導入的一樣_46 // 唯一區別是這裡是異步導入的,在某些場景下有性能優勢_46 },_46 error => {_46 // 處理錯誤_46 // 如果加載或者運行模塊出錯,就會運行這裡的代碼_46 },_46)_46_46// 在 React 裡:_46import React, { Suspense, Fragment } from 'react'_46_46// 動態導入 React 組件_46const BigComponent = React.lazy(() => import('./big-component'))_46// big-component.js 需要這樣 "export default BigComponent" 導出才行
作為輔助資料,我有一個演講是關於這種語法的,你可以點擊觀看
條件運算符(Ternaries)
我喜歡條件運算符,它們具有優雅的聲明性,特別是在 JSX 中。
_30const message = bottle.fullOfSoda_30 ? 'The bottle has soda!'_30 : 'The bottle may not have soda :-('_30_30// 與此相同_30let message_30if (bottle.fullOfSoda) {_30 message = 'The bottle has soda!'_30} else {_30 message = 'The bottle may not have soda :-('_30}_30_30// 在 React 裡:_30function 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
這裡有些例子:
_71const 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_71dogs.find(dog => dog.name === 'Bernese Mountain Dog')_71// {id: 'dog-2', name: 'Bernese Mountain Dog', ...etc}_71_71dogs.some(dog => dog.temperament.includes('Aggressive'))_71// false_71_71dogs.some(dog => dog.temperament.includes('Trusting'))_71// true_71_71dogs.every(dog => dog.temperament.includes('Trusting'))_71// false_71_71dogs.every(dog => dog.temperament.includes('Intelligent'))_71// true_71_71dogs.map(dog => dog.name)_71// ['Poodle', 'Bernese Mountain Dog', 'Labrador Retriever']_71_71dogs.filter(dog => dog.temperament.includes('Faithful'))_71// [{id: 'dog-1', ..etc}, {id: 'dog-2', ...etc}]_71_71dogs.reduce((allTemperaments, dog) => {_71 return [...allTemperaments, ...dog.temperament]_71}, [])_71// [ 'Intelligent', 'Active', 'Alert', ...etc ]_71_71// 在 React 裡:_71function 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}
空值合併運算符(Nullish coalescing operator)
如果值為null
或 undefined
時,你可能希望回退到某個默認值:
_26// 我們經常這樣寫:_26x = x || 'some default'_26_26// 但對於數字 0 或者布爾值 false 這些正常值來說,就會出問題_26_26// 所以,如果想支持這樣的代碼_26add(null, 3)_26_26// 那麼我們要做的是在計算之前:_26function add(a, b) {_26 a = a == null ? 0 : a_26 b = b == null ? 0 : b_26 return a + b_26}_26_26// 而現在我們可以這樣寫_26function add(a, b) {_26 a = a ?? 0_26 b = b ?? 0_26 return a + b_26}_26_26// 在 React 裡:_26function DisplayContactName({ contact }) {_26 return <div>{contact.name ?? 'Unknown'}</div>_26}
MDN: Nullish coalescing operator
可選鏈操作符(Optional chaining)
也稱為“Elvis Operator”,它允許你安全地訪問屬性,並調用可能存在或不存在的函數。在 可選鏈接之前,我們使用了一種依賴於真/假值的變通方法。
_32// 在可選鏈操作符發明之前,我們是這麼寫的:_32const streetName = user && user.address && user.address.street.name_32_32// 現在我們可以:_32const streetName = user?.address?.street?.name_32_32// 這裡即使 options 是 undefined (這裡 onSuccess 也將是 undefined)_32// 但是,如果 options 從未被聲明,則以下調用還是會失敗報錯_32// 可選鏈操作符無法在不存在的根 object 上調用_32// 可選鏈操作符無法代替 if (typeof options == "undefined") 這樣的類型檢查_32const onSuccess = options?.onSuccess_32_32// 即使 onSuccess 是 undefined,這裡跑起來也不會出錯,因為沒有函數會被調用_32onSuccess?.({ data: 'yay' })_32_32// 我們也可以把以上這些代碼合併成一行:_32options?.onSuccess?.({ data: 'yay' })_32_32// 還有,如果你 100% 確定 options 是存在的,而且 onSuccess 是一個函數_32// 那麼在調用前,你就不需要這個額外的 ?._32// 只在左邊的值有可能不存在的情況下才用 ?._32options?.onSuccess({ data: 'yay' })_32_32// 在 React 裡:_32function 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 的特殊語法,兩者相輔相成。
_94function 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_94function 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_94function log(...args) {_94 console.log(...args)_94}_94_94function logError(...args) {_94 console.error(...args)_94}_94_94// 這是異步代碼運行的核心_94function 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 裡:_94function 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}
結論
當然,還有很多語言特性在構建 React 應用程序時很有用,但以上這些是我最喜歡且常用 的特性,希望對你有幫助。
如果你想深入研究其中的任何一個,我確有一個 JavaScript 研討會是我在 PayPal 工作時 記錄下的,可能會對你有所幫助: ES6 and Beyond Workshop at PayPal
祝你好運!