与我使用过的其他框架相比,我最喜欢React的一点是,当你使用它时,你是如何暴露于JavaScript的。没有模板DSL(JJSX编译为合理的JavaScript),组件API随着React Hooks的增加而变得更加简单,而且该框架在它要解决的核心UI问题之外为你提供了很少的抽象性。
正因为如此,要想有效地使用React构建应用程序,学习JavaScript特性是非常明智的。因此,我建议你花一些时间学习一些JavaScript特性,这样你就可以尽可能有效地使用React。
在我们讨论一些语法问题之前,另一个对React非常有用的东西是函数 "闭包 "的概念。这里有一个关于这个概念的好文章:MDN.io/closure。
好了,让我们来谈谈你想知道的React的JS特性。
模板字元
模板字元就像具有超能力的普通字符串:
const greeting = 'Hello'
const subject = 'World'
console.log(`${greeting} ${subject}!`) // Hello World!
// this is the same as:
console.log(greeting + ' ' + subject + '!')
// in React:
function Box({className, ...props}) {
return <div className={`box ${className}`} {...props} />
}
简明的属性名称
这是很常见和有用的,以至于我现在不假思索地就这么做了:
const a = 'hello'
const b = 42
const c = {d: [true, false]}
console.log({a, b, c})
// this is the same as:
console.log({a: a, b: b, c: c})
// in React:
function Counter({initialCount, step}) {
const [count, setCount] = useCounter({initialCount, step})
return <button onClick={setCount}>{count}</button>
}
箭头函数
箭头函数是另一种在JavaScript中编写函数的方式,但它们确实有一些语义上的差异。幸运的是,在React领域,如果我们在项目中使用钩子(而不是类),我们就不必担心this ,但箭头函数允许更多的匿名函数和隐式返回,所以你会看到并希望大量使用箭头函数。
const getFive = () => 5
const addFive = a => a + 5
const divide = (a, b) => a / b
// this is the same as:
function getFive() {
return 5
}
function addFive(a) {
return a + 5
}
function divide(a, b) {
return a / b
}
// in React:
function TeddyBearList({teddyBears}) {
return (
<ul>
{teddyBears.map(teddyBear => (
<li key={teddyBear.id}>
<span>{teddyBear.name}</span>
</li>
))}
</ul>
)
}
上面的例子有一点需要注意,就是开头和结尾的小括号( 。这是在使用JSX时利用箭头函数的隐式返回功能的一种常见方式。
解构
解构可能是我最喜欢的JavaScript功能。我经常对对象和数组进行重组(如果你正在使用useState ,你可能也是这样)。我喜欢它的声明性。
// const obj = {x: 3.6, y: 7.8}
// makeCalculation(obj)
function makeCalculation({x, y: d, z = 4}) {
return Math.floor((x + d + z) / 3)
}
// this is the same as
function makeCalculation(obj) {
const {x, y: d, z = 4} = obj
return Math.floor((x + d + z) / 3)
}
// which is the same as
function makeCalculation(obj) {
const x = obj.x
const d = obj.y
const z = obj.z === undefined ? 4 : obj.z
return Math.floor((x + d + z) / 3)
}
// in React:
function UserGitHubImg({username = 'ghost', ...props}) {
return <img src={`https://github.com/${username}.png`} {...props} />
}
一定要读读MDN的那篇文章。你肯定会学到一些新东西。当你完成后,试着重构这个,使用一行destructuring。
function nestedArrayAndObject() {
// refactor this to a single line of destructuring...
const info = {
title: 'Once Upon a Time',
protagonist: {
name: 'Emma Swan',
enemies: [
{name: 'Regina Mills', title: 'Evil Queen'},
{name: 'Cora Mills', title: 'Queen of Hearts'},
{name: 'Peter Pan', title: `The boy who wouldn't grow up`},
{name: 'Zelena', title: 'The Wicked Witch'},
],
},
}
// const {} = info // <-- replace the next few `const` lines with this
const title = info.title
const protagonistName = info.protagonist.name
const enemy = info.protagonist.enemies[3]
const enemyTitle = enemy.title
const enemyName = enemy.name
return `${enemyName} (${enemyTitle}) is an enemy to ${protagonistName} in "${title}"`
}
参数默认值
这是我经常使用的另一个功能。它是一种非常强大的方式,可以为你的函数声明性地表达默认值。
// add(1)
// add(1, 2)
function add(a, b = 0) {
return a + b
}
// is the same as
const add = (a, b = 0) => a + b
// is the same as
function add(a, b) {
b = b === undefined ? 0 : b
return a + b
}
// in React:
function useLocalStorageState({
key,
initialValue,
serialize = v => v,
deserialize = v => v,
}) {
const [state, setState] = React.useState(
() => deserialize(window.localStorage.getItem(key)) || initialValue,
)
const serializedState = serialize(state)
React.useEffect(() => {
window.localStorage.setItem(key, serializedState)
}, [key, serializedState])
return [state, setState]
}
休息/传播
... 语法可以被认为是一种 "集合 "语法,它对一个值的集合进行操作。我一直在使用它,并强烈建议你学习它的使用方法和使用场合。实际上,它在不同的情况下有不同的含义,所以学习其中的细微差别将有助于你。
const arr = [5, 6, 8, 4, 9]
Math.max(...arr)
// is the same as
Math.max.apply(null, arr)
const obj1 = {
a: 'a from obj1',
b: 'b from obj1',
c: 'c from obj1',
d: {
e: 'e from obj1',
f: 'f from obj1',
},
}
const obj2 = {
b: 'b from obj2',
c: 'c from obj2',
d: {
g: 'g from obj2',
h: 'g from obj2',
},
}
console.log({...obj1, ...obj2})
// is the same as
console.log(Object.assign({}, obj1, obj2))
function add(first, ...rest) {
return rest.reduce((sum, next) => sum + next, first)
}
// is the same as
function add() {
const first = arguments[0]
const rest = Array.from(arguments).slice(1)
return rest.reduce((sum, next) => sum + next, first)
}
// in React:
function Box({className, ...restOfTheProps}) {
const defaultProps = {
className: `box ${className}`,
children: 'Empty box',
}
return <div {...defaultProps} {...restOfTheProps} />
}
ESModules
如果你正在用现代工具构建一个应用程序,那么它有可能支持模块,学习该语法是一个好主意,因为任何应用程序,即使是微不足道的规模,也可能需要利用模块来进行代码重用和组织。
export default function add(a, b) {
return a + b
}
/*
* import add from './add'
* console.assert(add(3, 2) === 5)
*/
export const foo = 'bar'
/*
* import {foo} from './foo'
* console.assert(foo === 'bar')
*/
export function subtract(a, b) {
return a - b
}
export const now = new Date()
/*
* import {subtract, now} from './stuff'
* console.assert(subtract(4, 2) === 2)
* console.assert(now instanceof Date)
*/
// dynamic imports
import('./some-module').then(
allModuleExports => {
// the allModuleExports object will be the same object you get if you had
// used: import * as allModuleExports from './some-module'
// the only difference is this will be loaded asynchronously which can
// have performance benefits in some cases
},
error => {
// handle the error
// this will happen if there's an error loading or running the module
},
)
// in React:
import React, {Suspense, Fragment} from 'react'
// dynamic import of a React component
const BigComponent = React.lazy(() => import('./big-component'))
// big-component.js would need to "export default BigComponent" for this to work
作为另一个资源,我做了一个关于这种语法的讲座,你可以在这里观看该讲座。
三元组
我喜欢三元组。它们是漂亮的声明性的。特别是在JSX中。
const message = bottle.fullOfSoda
? 'The bottle has soda!'
: 'The bottle may not have soda :-('
// is the same as
let message
if (bottle.fullOfSoda) {
message = 'The bottle has soda!'
} else {
message = 'The bottle may not have soda :-('
}
// in React:
function TeddyBearList({teddyBears}) {
return (
<React.Fragment>
{teddyBears.length ? (
<ul>
{teddyBears.map(teddyBear => (
<li key={teddyBear.id}>
<span>{teddyBear.name}</span>
</li>
))}
</ul>
) : (
<div>There are no teddy bears. The sadness.</div>
)}
</React.Fragment>
)
}
我意识到,在prettier出现并清理我们的代码之前,一些人不得不忍受试图理解三元组的痛苦,所以三元组会引起他们的反感。如果你还没有使用prettier,我强烈建议你使用。Prettier将使你的三元组更容易阅读。
数组方法
数组是很奇妙的,我一直都在使用数组方法!我最常使用的是以下方法。我可能最常使用的是以下方法:
- 查找
- 一些
- 每一个
- 包括
- 地图
- 过滤
- 减少
这里有一些例子
const dogs = [
{
id: 'dog-1',
name: 'Poodle',
temperament: [
'Intelligent',
'Active',
'Alert',
'Faithful',
'Trainable',
'Instinctual',
],
},
{
id: 'dog-2',
name: 'Bernese Mountain Dog',
temperament: ['Affectionate', 'Intelligent', 'Loyal', 'Faithful'],
},
{
id: 'dog-3',
name: 'Labrador Retriever',
temperament: [
'Intelligent',
'Even Tempered',
'Kind',
'Agile',
'Outgoing',
'Trusting',
'Gentle',
],
},
]
dogs.find(dog => dog.name === 'Bernese Mountain Dog')
// {id: 'dog-2', name: 'Bernese Mountain Dog', ...etc}
dogs.some(dog => dog.temperament.includes('Aggressive'))
// false
dogs.some(dog => dog.temperament.includes('Trusting'))
// true
dogs.every(dog => dog.temperament.includes('Trusting'))
// false
dogs.every(dog => dog.temperament.includes('Intelligent'))
// true
dogs.map(dog => dog.name)
// ['Poodle', 'Bernese Mountain Dog', 'Labrador Retriever']
dogs.filter(dog => dog.temperament.includes('Faithful'))
// [{id: 'dog-1', ..etc}, {id: 'dog-2', ...etc}]
dogs.reduce((allTemperaments, dog) => {
return [...allTemperaments, ...dog.temperament]
}, [])
// [ 'Intelligent', 'Active', 'Alert', ...etc ]
// in React:
function RepositoryList({repositories, owner}) {
return (
<ul>
{repositories
.filter(repo => repo.owner === owner)
.map(repo => (
<li key={repo.id}>{repo.name}</li>
))}
</ul>
)
}
虚值凝聚运算符
如果一个值是null 或undefined ,那么你可能想回退到一些默认值。
// here's what we often did for this:
x = x || 'some default'
// but this was problematic for numbers or booleans where "0" or "false" are valid values
// So, if we wanted to support this:
add(null, 3)
// here's what we had to do before:
function add(a, b) {
a = a == null ? 0 : a
b = b == null ? 0 : b
return a + b
}
// here's what we can do now
function add(a, b) {
a = a ?? 0
b = b ?? 0
return a + b
}
// in React:
function DisplayContactName({contact}) {
return <div>{contact.name ?? 'Unknown'}</div>
}
MDN: Nullish coalescing operator
可选链式
也被称为 "猫王操作符",它允许你安全地访问属性和调用可能存在或不存在的函数。在可选链之前,我们使用的是一种依赖于falsy/truthy-ness的 hacky-workaround。
// what we did before optional chaining:
const streetName = user && user.address && user.address.street.name
// what we can do now:
const streetName = user?.address?.street?.name
// this will run even if options is undefined (in which case, onSuccess would be undefined as well)
// however, it will still fail if options was never declared,
// since optional chaining cannot be used on a non-existent root object.
// optional chaining does not replace checks like if (typeof options == "undefined")
const onSuccess = options?.onSuccess
// this will run without error even if onSuccess is undefined (in which case, no function will be called)
onSuccess?.({data: 'yay'})
// and we can combine those things into a single line:
options?.onSuccess?.({data: 'yay'})
// and if you are 100% certain that onSuccess is a function if options exists
// then you don't need the extra ?. before calling it. Only use ?. in situations
// where the thing on the left might not exist.
options?.onSuccess({data: 'yay'})
// in React:
function UserProfile({user}) {
return (
<div>
<h1>{user.name}</h1>
<strong>{user.bio?.short ?? 'No bio provided'}</strong>
</div>
)
}
围绕这一点需要注意的是,如果你发现自己在代码中经常做?. ,你可能要考虑这些值的来源地,并确保它们一致地返回它们应该的值。
许诺和async/await
这是个很大的问题,需要花点时间和实践才能熟练掌握。诺言在JavaScript生态系统中随处可见,由于React在该生态系统中的地位非常稳固,它们在那里也随处可见(事实上,React本身也在内部使用诺言)。
许诺帮助你管理异步代码,并从许多DOM API以及第三方库中返回。Async/await 语法是处理承诺的特殊语法。这两者是相辅相成的。
function promises() {
const successfulPromise = timeout(100).then(result => `success: ${result}`)
const failingPromise = timeout(200, true).then(null, error =>
Promise.reject(`failure: ${error}`),
)
const recoveredPromise = timeout(300, true).then(null, error =>
Promise.resolve(`failed and recovered: ${error}`),
)
successfulPromise.then(log, logError)
failingPromise.then(log, logError)
recoveredPromise.then(log, logError)
}
function asyncAwaits() {
async function successfulAsyncAwait() {
const result = await timeout(100)
return `success: ${result}`
}
async function failedAsyncAwait() {
const result = await timeout(200, true)
return `failed: ${result}` // this would not be executed
}
async function recoveredAsyncAwait() {
try {
const result = await timeout(300, true)
return `failed: ${result}` // this would not be executed
} catch (error) {
return `failed and recovered: ${error}`
}
}
successfulAsyncAwait().then(log, logError)
failedAsyncAwait().then(log, logError)
recoveredAsyncAwait().then(log, logError)
}
function log(...args) {
console.log(...args)
}
function logError(...args) {
console.error(...args)
}
// This is the mothership of all things asynchronous
function timeout(duration = 0, shouldReject = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldReject) {
reject(`rejected after ${duration}ms`)
} else {
resolve(`resolved after ${duration}ms`)
}
}, duration)
})
}
// in React:
function GetGreetingForSubject({subject}) {
const [isLoading, setIsLoading] = React.useState(false)
const [error, setError] = React.useState(null)
const [greeting, setGreeting] = React.useState(null)
React.useEffect(() => {
async function fetchGreeting() {
try {
const response = await window.fetch('https://example.com/api/greeting')
const data = await response.json()
setGreeting(data.greeting)
} catch (error) {
setError(error)
} finally {
setIsLoading(false)
}
}
setIsLoading(true)
fetchGreeting()
}, [])
return isLoading ? (
'loading...'
) : error ? (
'ERROR!'
) : greeting ? (
<div>
{greeting} {subject}
</div>
) : null
}
总结
当然,在构建React应用程序时,还有许多语言功能是有用的,但这些是我最喜欢的一些,我发现自己在不断地使用。我希望你觉得这很有用。
如果你想进一步了解这些特性,我有一个JavaScript研讨会,是我在PayPal工作时举办和录制的,你可能会觉得很有帮助:PayPal的ES6和超越研讨会
祝您好运!