【译】React 中你会这 10 个 JavaScript 概念吗?
公众号「前端时空」每日一题活动
回复「1」看面试题 | 回复「2」看答案
前端时空-前端老王(翻译整理)
前端时空 - React 中必会的 10 个 JavaScript 概念
原文链接:10 JavaScript Concepts You Should Learn to Master React
#react #javascript #webdev
都 2020 年了,再不掌握 ES6,说不定就被优化了。
目录
- 箭头函数
- 默认参数
- 模板字符串
- let 和 const
- 类
- 解构
- 三元运算符
- 导入/导出模块
- async / await
- 展开运算符 / 不定参数
箭头函数
您可能知道,定义React组件的最简单方法是编写 JavaScript 函数,如以下示例所示。
function MyComponent(props) {
return <h1>Hello from AlterClass.io</h1>;
} 但是还有另一种更加简洁的方法来创建 React 函数组件。
const MyComponent = (props) => <h1>Hello from AlterClass.io</h1>;
箭头函数是您在 JavaScript 和 React 应用程序中最多见的函数。
在深入探讨如何在 React 中使用它们之前,让我们看看如何使用箭头函数。有多种方式可用于编写箭头函数。我们将在这里介绍一些常见的内容,以帮助您入门。
// 多参数的语法
const add = (a, b) => { return a + b };
// 只有一个表达式时,花括号可以省略。
// return 关键字是隐式的,可以忽略。
const add = (a, b) => a + b;
// 仅存在一个参数时,括号是可选的
const getUser = data => data.user;
//但是,如果没有参数,则需要括号
const hello = () => console.log("欢迎来到前端时空!"); 介绍了基本语法,让我们了解如何将箭头函数与 React 一起使用。除了如上所述定义 React 组件之外,箭头函数在操作数组以及使用异步回调和 Promise 时也非常有用。
在 React 中,我们通常必须从服务器获取数据并将其显示给我们的用户。为了检索此数据,我们经常使用 Promise 链式调用。
// ES5
fetch(apiURL)
.then(function(res) {
return res.json();
})
.then(function(data) {
return data.products;
})
.catch(function(error) {
console.log(error);
});Promise 链式调用得到简化,易于阅读,并且使用箭头函数更加简洁:
// ES6 fetch(apiURL) .then(res => res.json()) .then(data => data.products) .catch(error => console.log(error));
最后,一旦检索到数据,就需要显示它。为了在 React 中渲染数据列表,我们必须在JSX内部循环。通常使用map / reduce / filter数组方法来实现。
const products = [
{ _id: 1234, name: "ReactJS Pro Package", price: 199 },
{ _id: 5678, name: "ReactJS Basic Package", price: 99 },
...
];
// ES5
function ProductList(props) {
return (
<ul>
{props.products
.filter(function(product) {
return product.price <= 99;
})
.map(function(product) {
return <li key={product._id}>{product.name}</li>;
})}
</ul>
);
} 现在,让我们看看如何使用 ES6 箭头函数实现相同的函数。
// ES6
const ProductList = props => (
<ul>
{props.products
.filter(product => product.price <= 99)
.map(product => (
<li key={product._id}>{product.name}</li>
))}
</ul>
); 默认参数
既然我们已经了解了箭头函数,那么让我们来谈谈默认参数。ES6 +的这一功能使它能够使用默认值初始化函数,即使函数调用不包含相应的参数也是如此。
但是首先,您还记得我们在 ES6 之前使用过的方法来检查函数中未声明的参数吗?您可能已经看过或使用过以下内容:
// ES5
function getItems(url, offset, limit, orderBy) {
offset = (typeof offset !== 'undefined') ? offset : 0;
limit = (typeof limit !== 'undefined') ? limit : 10;
orderBy = (typeof orderBy !== 'undefined') ? orderBy : 'date';
...
} 为了防止函数崩溃或计算无效 / 错误结果,我们必须编写额外的代码来测试每个可选参数和分配的默认值。确实,此技术用于避免我们的函数内部发生不良影响。没有它,任何未初始化的参数将默认为值undefined。
因此,这是我们在ES6之前如何处理默认参数的简短摘要。在ES6中定义默认参数要容易得多。
// ES6
function getItems(url, offset = 0, limit = 10, orderBy = 'date') {
...
}
// 箭头函数也支持默认参数
const getItems = (url, offset = 0, limit = 10, orderBy = 'date') => {
...
}如果将 offset,limit 和 orderBy 传递给函数调用,则它们的值将覆盖函数定义中定义为默认参数的值。无需额外的代码。
⚠️请注意,这 null 被视为有效值。这意味着,如果您 null 为其中一个参数传递值,则不会采用该函数定义的默认值。因此,请确保使用undefined而不是null当您希望使用默认值时使用。
现在,您知道如何在ES6中使用默认参数。那么默认参数和React呢?
在React中,您可以使用defaultProps属性为组件属性设置默认值。但是,这仅适用于类组件。实际上,React团队正在弃用defaultProps功能组件上的属性,并且将其删除。
别担心!我们可以利用默认参数为React函数组件的prop设置默认值。请查看以下示例。
const Button = ({ size = 'md', disabled = false, children }) => (
<button
type="button"
disabled={disabled}
className={btn-${size}}
{children}在ES5中,我们必须使用 + 运算符将多个值连接起来以连接字符串。
// ES5
console.log("Something went wrong: " + error.message); 在 ES6 中,模板字符串由反引号引起来。要在这些模板中插入表达式,我们可以使用${表达式}。
// ES6
console.log(`Something went wrong: ${error.message}`);
console.log(`Hello, ${ getUserName() }!`); 模板字符串使这种替换更具可读性。在 React 中使用它们将帮助您动态设置组件属性值或元素属性值。
const Button = (props) => (
<button
type="button"
className={`btn-${props.size}`}
>
{props.children}
</button>
); let 和 const
在 ES5 中,声明变量的唯一方法是使用 var 关键字。ES6 引入了两种使用 const 和 let。
主要区别:
var
函数作用域 在声明变量之前访问变量时 undefined
let
块作用域 在声明之前访问变量时 ReferenceError
const
块作用域 在声明之前访问变量时,ReferenceError
无法重新分配
声明时应初始化
在 React 应用程序中,const 用于声明 React 组件。最佳实践是默认使用 const,只在确实需要改变变量的值时使用 let。
const OrderDetails = (props) => {
const [totalAmount, setTotalAmount] = useState(0.0);
const { state } = useContext(Context);
useEffect(() => {
let total = state.course.price;
// substract promotional discount
total -= state.course.price * state.course.discountRate;
// add taxes
total += total * state.course.taxPercentage;
setTotalAmount(total);
},
[state]
);
const handleOnClick = () => { ... };
return (
<>
<span>Total: ${totalAmount}</span>
<button onClick={handleOnClick}>Pay</button>
</>
);
}; 类
ES6 引入了 JavaScript 类。如 MDN 网站文档所述,类主要是语法糖,而不是JavaScript 现有的基于原型的继承。有些属性值得一提,因为它们与使用常规函数编写的类不太相同。
// ES6 类的定义
class User {
constructor(name) {
this.name = name;
}
greet() {
return `${this.name} says hello!`;
}
}
// Usage
let user = new User("Greg");
user.greet(); // --> Greg says hello! 继承,这不是特定于 JavaScript 的东西,而是面向对象编程中的常见概念。
简而言之,这是将一个类创建为另一个类的子级的能力。子类将从其父类的属性继承(实际上,这比您所使用的 OOP 语言要复杂得多)。
在 ES6 中,extends 关键字继承另一个的类。
class Employee extends User {
constructor(name, salary) {
// 调用 User 类的构造函数
super(name);
// 添加一个新的属性
this.salary = salary;
}
raiseSalary() {
this.salary += 10000;
return this.salary;
}
}
// 使用
let employee = Employee("Greg", 250000);
employee.raiseSalary(); // --> 260000 在 React 应用程序中,您还可以使用 ES6 类来定义组件。要定义一个 React 组件类,您需要扩展 React.Component 基类,如下所示:
class Button extends React.Component {
render() {
return <button type="buttom">Click me</button>;
}
} 通过创建这样的组件,您将可以访问与 React 组件相关的一堆方法和属性(状态,属性,生命周期方法等)。请查看 React 文档以获取 React.Component 类的详细 API 参考。
解构
在 React 中非常经常使用解构。这是一个可以与对象以及数组一起使用的概念。分解是简化 JavaScript 代码的一种简便方法,因为它使我们可以在一行中将数据从对象或数组中拉出。
数组解构与对象解构相似,不同之处在于我们按照数据在数组中出现的顺序将数据一一拉出。
让我们直接来看看它在 React 应用程序中的用法。
// 使用对象解构获取 `useState`
import React, { useState } from 'react';
// 使用对象解构获取个别属性
const Button = ({ size = 'md', disabled = false }) => {
// 使用数组销毁获取有状态值和更新功能
const [loading, setLoading] = useState(false);
return (...);
}; 三元运算符
三元运算符用作 if 语句的简洁方式。典型 if 语句的语法如下:
if (condition) {
// value if true
}
else {
// value if false
}
condition ? valueIfTrue : valueIfFalse 条件为真,执行第一条语句(在冒号之前:)。条件为假(false,null,NaN,0,""或未定义),执行第二条语句(在冒号之后:)。
虽然有时候代码会很简洁,但是可读性会降低,所以请谨慎使用。
return condition1 ? value1
: condition2 ? value2
: condition3 ? value3
: value4;在 React 中,三元运算符使我们可以在 JSX 中编写更简洁的条件语句。通常使用它来根据条件决定显示或隐藏哪个组件。
const App = () => {
const [loading, setLoading] = useState(false);
const [showPopup, setShowPopup] = useState(false);
...
return (
<>
<Navbar />
{loading ? <Spinner /> : <Body />}
...
{showPopup && <Popup />}
</>
);
}; 导入 / 导出模块
在 ES6 之前,由于 JavaScript 不支持模块,我们使用了 RequiredJS 或 CommonJS 之类的库来导入 / 导出模块。您可能之前已经看过,特别是如果您已经使用过 Node.js。
// ES5 with CommonJS
var express = require('express');
var router = express.Router();
router.get('/', function(req, res) {
...
});
module.exports = router; 在 ES6 中,我们可以直接使用 exportand import 语句来处理应用程序中的模块。
// auth.js
export const login = (email, password) => { ... };
export const register = (name, email, password) => { ... };
// main.js
import { login, register } from './auth'; 这在 React 中非常有用,因为我们正在将应用程序 UI 划分为组件层次结构。组件在自己的文件中定义,其他组件则需要导入或者导出,例如以下示例:
// Button.js
const Button = ({ size = 'md', disabled = false, children) => (
<button
type="button"
disabled={disabled}
className={`btn-${size}`}
>
{children}
</button>
);
export default Button;
// App.js
import Button from './Button';
const App = () => (
<>
...
<Button size='lg'>Submit</Button>
</>
); async / await
您可能熟悉异步编程的概念。在 JavaScript 中,它们是使用异步代码的许多方法(回调,Promise,诸如 bluebird 和 deferred.js 等外部库)。在这里,我们只是简单的提及 async / await。
async / await 是一种特殊的语法,可以以更舒适的方式处理 Promise。
如果您需要了解 Promise,请查看 MDN 中的详细讲解。
您可能已经注意到,有两个新关键字:async 和 await。
让我们首先从 async 关键字开始。异步用于定义异步函数,该函数返回隐式 Promise 作为其结果。
async function myAsyncFunc() {
return "欢迎来到前端时空!";
}
// 调用
myAsyncFunc().then(...); 请注意,使用异步函数的代码的语法和结构看起来像常规同步函数。
关键字 awai t仅在异步函数中起作用。它使程序等待,直到 Promise 成功并返回其结果。这是一个承诺在几秒钟后解决的示例:
async function myAsyncFunc() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("Hello!"), 3000)
});
let result = await promise; // wait until the promise resolves
alert(result); // "Hello!"
} 与使用相比 Promise.then(),这是获得 Promise resolve 的一种更为优雅的方法,此外,它更易于读写。
⚠️请小心,因为 await 不能在常规函数中使用。如果这样做,则会出现语法错误。
值得一提的是 async / await 是如何处理错误。实际上,如果一个 Promise 能够正常resolve,它就会返回结果。但是,如果 reject,则会引发错误。您可以使用 Promise catch 方法或 try..catch 与常规抛出相同的方式来处理错误。
asynFunction().catch(error => console.log(error));
// or
try {
asynFunction();
}
catch(error) {
console.log(error)
} 我将 async / await 包含在此列表中是因为在每个前端项目中,我们正在做很多需要异步代码的工作。一个常见的例子是当我们想通过 API 调用获取数据时。
在 React 中,这就是我们可以使用 promises + async / await 做到的。
const App = () => {
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
// 检查用户是被验证的
const user = await getUser();
// 停下里加载 spinner
setLoading(false);
};
fetchData().catch(alert);
}, []);
if (loading) {
return <Spinner />;
}
return <>...</>;
}; 展开运算符 / 不定参数
展开运算符和不定参数由三个点表示...。在展开运算符的情况下,它将可迭代扩展为单个元素。对于不定参数,它将其余参数列表收集到一个数组中。
让我们看一些示例,以了解它们如何工作以及如何使用它们。
// 不定参数
function sum(...args) {
let sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
}
// 在函数调用中展开元素
let array = [10, 6, 4];
console.log(Math.max(...array)); // 10
// 拷贝一个数组
let items = ['item1', 'item2', 'item3'];
let newArray = [...items];
console.log(newArray); // ['item1', 'item2', 'item3']
// 合并数组
let array1 = ['1', '2', '3'];
let array2 = ['A', 'B', 'C'];
let result = [...array1, ...array2];
console.log(result); // ['1', '2', '3', 'A', 'B', 'C']
// 合并两个对象
var object1 = { _id: 123, name: 'Greg' }
var object2 = { age: 28, country: 'FR'}
const user = { ...object1, ...object2 }
console.log(user); // { "_id": 123, "name": "Greg", "age": 28, "country": "FR" } 展开运算符在 Redux 之类的库中得到了广泛使用,以不变的方式处理应用程序状态。但是,这也常与 React 一起使用,以轻松传递所有对象的数据作为单独的属性。这比逐个传递每个属性要容易。
如果您以前听说过 HOC(高阶组件),则知道您需要将所有属性传递给封装的组件。展开运算符能够为此提供帮助。
const withStorage = (WrappedComponent) => {
class WithStorageHOC extends React.Component {
...
render() {
return <WrappedComponent {...this.props} />;
}
}
}; 
