本章将介绍以下配方:
- 介绍
- 在 React 中使用最新的 JS 功能
- React 有什么新功能?
- 在 Windows 上使用 React
React 是 Facebook 制作的一个 JavaScript 库(MIT 许可证),用于创建交互式 UI。它用于创建动态和可重用的组件。React 最强大的功能是可以在客户端、服务器、移动应用甚至 VR 应用中使用
在现代网络中,我们需要不断地操纵 DOM;问题是,大量这样做可能会严重影响应用的性能。React 使用虚拟 DOM,这意味着所有更新都发生在内存中(这比直接操作真实 DOM 要快)。与 Angular、Vue 或主干等其他 JavaScript 框架相比,React 的学习曲线较短,这主要是因为 React 代码大多使用现代 JavaScript(类、箭头函数、字符串模板等)编写,并且没有太多用于编写代码的模式,如依赖项注入或模板系统,就像在角上。
Airbnb、微软、Netflix、迪士尼、Dropbox、Twitter、PayPal、Salesforce、特斯拉和 Uber 等公司在其项目中广泛使用 React。在本书中,您将学习如何使用最佳实践,以 React 应用的方式开发应用。
正如我在介绍中所说,React 主要是用现代 JavaScript(ES6、ES7 和 ES8)编写的。如果您想利用 React,您应该掌握一些现代 JS 功能,以便为 React 应用获得最佳效果。在第一个菜谱中,我们将介绍基本的 JS 功能,这样您就可以准备好并开始开发第一个 React 应用了。
在本节中,我们将了解如何使用 React 中最重要的 JS 功能:
let
和const
:JavaScript 中声明变量的新方法是使用let
或const
。您可以使用let
声明可以更改其值但在块范围内的变量。let
与var
的区别在于let
是一个块范围变量,不能是全局变量,使用var
可以声明一个全局变量,例如:
var name = 'Carlos Santana';
let age = 30;
console.log(window.name); // Carlos Santana
console.log(window.age); // undefined
- 理解“block scope”的最好方法是用
var
和let
声明一个for
循环。首先,让我们使用var
并查看其行为:
for (var i = 1 ; i <= 10; i++) {
console.log(i); // 1, 2, 3, 4... 10
}
console.log(i); // Will print the last value of i: 10
- 如果我们编写相同的代码,但使用
let
,则会发生以下情况:
for (let i = 1 ; i <= 10; i++) {
console.log(i); // 1, 2, 3, 4... 10
}
console.log(i); // Uncaught ReferenceError: i is not defined
- 通过
const
,我们可以声明常量,这意味着该值不能更改(数组和对象除外):
const pi = 3.1416;
pi = 5; // Uncaught TypeError: Assignment to constant variable.
- 如果我们用
const
声明一个数组,我们可以操作数组元素(添加、删除或修改元素):
const cryptoCurrencies = ['BTC', 'ETH', 'XRP'];
// Adding ERT: ['BTC', 'ETH', 'XRP', 'ERT'];
cryptoCurrencies.push('ERT');
// Will remove the first element: ['ETH', 'XRP', 'ERT']; cryptoCurrencies.shift();
// Modifying an element
cryptoCurrencies[1] = 'LTC'; // ['ETH', 'LTC', 'ERT'];
- 此外,使用对象,我们可以添加、删除或修改节点:
const person = {
name: 'Carlos Santana',
age: 30,
email: '[email protected]'
};
// Adding a new node...
person.website = 'https://www.codejobs.com';
// Removing a node...
delete person.email;
// Updating a node...
person.age = 29;
- 扩展运算符:扩展运算符(…)将可编辑对象拆分为单个值。在 React 中,它可用于将值推送到另一个数组中,例如,当我们想利用
setState
将新项目添加到 Todo 列表中时(这将在下一章中解释):
this.setState({
items: [
...this.state.items, // Here we are spreading the current items
{
task: 'My new task', // This will be a new task in our Todo list.
}
]
});
- 此外,Spread 运算符还可用于 JSX 中的 Resact to Spread attributes(道具)中:
render() {
const props = {};
props.name = 'Carlos Santana';
props.age = 30;
props.email = '[email protected]';
return <Person {...props} />;
}
- **休息参数:**该
rest
参数也用...
表示。前缀为...
的函数中的最后一个参数称为 rest 参数。rest 参数是一个数组,当参数数超过命名参数数时,该数组将包含函数的其余参数:
function setNumbers(param1, param2, ...args) {
// param1 = 1
// param2 = 2
// args = [3, 4, 5, 6];
console.log(param1, param2, ...args); // Log: 1, 2, 3, 4, 5, 6
}
setNumbers(1, 2, 3, 4, 5, 6);
- 解构:React 中使用最多的是解构赋值功能。它是一个表达式,允许我们将 iterable 对象的值或属性指定给变量。通常,我们可以将组件道具转换为变量(或常量):
// Imagine we are on our <Person> component and we are
// receiving the props (in this.props): name, age and email. render() { // Our props are:
// { name: 'Carlos Santana', age: 30, email:
'[email protected]' } console.log(this.props);
const { name, age, email } = this.props;
// Now we can use the nodes as constants... console.log(name, age, email);
return (
<ul>
<li>Name: {name}</li>
<li>Age: {age}</li>
<li>Email: {email}</li>
</ul> ); }
// Also the destructuring can be used on function parameters
const Person = ({ name, age, email }) => (
<ul>
<li>Name: {name}</li>
<li>Age: {age}</li>
<li>Email: {email}</li>
</ul>
);
- 箭头函数:ES6 提供了一种使用
=>
操作符创建函数的新方法。这些函数称为箭头函数。此新方法具有较短的语法,并且箭头函数是匿名函数。在 React 中,箭头函数被用作在我们的方法中绑定this
对象的一种方式,而不是在构造函数中绑定它:
class Person extends Component {
showProps = () => {
console.log(this.props); // { name, age, email... }
}
render() {
return (
<div>
Consoling props: {this.showProps()}
</div>
);
}
}
- 模板文字:模板文字是一种使用反勾(``)而不是单引号(“”)或双引号(“”)创建字符串的新方法。React 使用模板文字连接类名或使用三元运算符呈现字符串:
render() {
const { theme } = this.props;
return (
<div
className={`base ${theme === 'dark' ? 'darkMode' :
'lightMode'}`} >
Some content here...
</div>
);
}
- M****ap:
map()
方法返回一个新数组,该数组包含对调用数组中每个元素调用提供函数的结果。映射在 React 中使用广泛,主要用于渲染 React 组件内的多个元素;例如,它可用于呈现任务列表:
render() {
const tasks = [
{ task: 'Task 1' },
{ task: 'Task 2' },
{ task: 'Task 3' }
];
return (
<ul>
{tasks.map((item, key) => <li key={key}>{item.task}</li>}
</ul>
);
}
- Object.assign():
Object.assign()
方法用于将所有可枚举自身属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。此方法主要用于 Redux 创建不可变对象并将新状态返回到还原器(Redux 将在第 5 章、掌握 Redux中介绍):
export default function coinsReducer(state = initialState, action) {
switch (action.type) {
case FETCH_COINS_SUCCESS: {
const { payload: coins } = action;
return Object.assign({}, state, {
coins
});
}
default:
return state;
}
};
- 类:ES6 中引入的 JavaScript 类主要是现有基于原型的继承的新语法。类是函数,不被提升。React 使用类创建类组件:
import React, { Component } from 'react';
class Home extends Component {
render() {
return <h1>I'm Home Component</h1>;
}
}
export default Home;
- 静态方法:不在类的实例上调用静态方法。相反,它们是在类本身上调用的。这些通常是实用程序函数,例如用于创建或克隆对象的函数。在 React 中,它们可用于定义组件中的
PropTypes
:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import logo from '../img/logo.svg';
class Header extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
url: PropTypes.string
};
render() {
const {
title = 'Welcome to React',
url = 'http://localhost:3000'
} = this.props;
return (
<header className="App-header">
<a href={url}>
<img src={logo} className="App-logo" alt="logo" />
</a>
<h1 className="App-title">{title}</h1>
</header>
);
}
}
export default Header;
- 承诺:对象表示异步操作的最终完成(或失败)及其结果值。我们将在 React 中使用承诺,通过使用 axios 或 fetch 处理请求;此外,我们还将使用承诺来实现服务器端渲染(这将在第 11 章【实现服务器端渲染中介绍)。
*** 异步/等待:异步函数声明定义了一个异步函数,返回一个
AsyncFunction
对象。这也可用于执行服务器请求,例如使用 axios:**
Index.getInitialProps = async () => {
const url = 'https://api.coinmarketcap.com/v1/ticker/';
const res = await axios.get(url);
return {
coins: res.data
};
};
本段于 2018 年 8 月 14 日编写,React 的最新版本为 16.4.2。React 16 版本有一个名为 Fiber 的新核心架构
在此配方中,我们将看到此版本中最重要的更新,您应该了解这些更新,以充分利用 React。
让我们看看新的更新:
- 组件现在可以从 render返回数组和字符串:之前,React 强制您返回一个用或任何其他标记包装的元素;现在可以直接返回数组或字符串:
// Example 1: Returning an array of elements.
render() {
// Now you don't need to wrap list items in an extra element
return [
<li key="1">First item</li>,
<li key="2">Second item</li>,
<li key="3">Third item</li>,
];
}
// Example 2: Returning a string
render() {
return 'Hello World!';
}
- 此外,React 现在有一个新特性,名为片段,它还可以作为元素的特殊包装器。可以使用空标签(
<></>
指定,也可以直接使用React.Fragment
指定:
// Example 1: Using empty tags <></>
render() {
return (
<>
<ComponentA />
<ComponentB />
<ComponentC />
</>
);
}
// Example 2: Using React.Fragment
render() {
return (
<React.Fragment>
<h1>An h1 heading</h1>
Some text here.
<h2>An h2 heading</h2>
More text here.
Even more text here.
</React.Fragment>
);
}
// Example 3: Importing Fragment
import React, { Fragment } from 'react';
...
render() {
return (
<Fragment>
<h1>An h1 heading</h1>
Some text here.
<h2>An h2 heading</h2>
More text here.
Even more text here.
</Fragment>
);
}
- 错误边界来自官网:
A JavaScript error in a part of the UI shouldn’t break the whole app. To solve this problem for React users, React 16 introduces a new concept of an "error boundary". Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them. A class component becomes an error boundary if it defines a new lifecycle method called componentDidCatch(error, info).
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false
};
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({
hasError: true
});
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// Then you can use it as a regular component:
render() {
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
}
- 更好的服务器端渲染来自官方网站:
React 16 includes a completely rewritten server renderer. It's really fast. It supports streaming, so you can start sending bytes to the client faster. And thanks to a new packaging strategy that compiles away process.env checks (Believe it or not, reading process.env in Node is really slow!), you no longer need to bundle React to get good server-rendering performance.
-
减少文件大小来自官方网站:**尽管添加了所有这些内容,React 16 实际上比 15.6.1 小。
- react 的大小为 5.3KB(2.2KB 压缩),低于 20.7KB(6.9KB 压缩)
- react dom 为 103.7KB(32.6KB 压缩),低于 141KB(42.9KB 压缩)
- react+react dom 的大小为 109 kb(34.8 kb 压缩),低于 161.7 kb(49.8 kb 压缩)**
**与上一版本(gzip 后 30%)相比,这意味着大小总共减少了 32%。”
If you want to check the latest updates on React, you can visit the official React blog: https://reactjs.org/blog.
我不太喜欢 Windows for development,因为有时候配置它会有点问题。我总是更喜欢 Linux 或 Mac,但我知道很多读这本书的人都会使用 Windows。在本食谱中,我将向您展示当您尝试使用 Windows 遵循本书中的食谱时可能遇到的最常见问题。
现在,我们将看到使用 Windows 进行开发时最常见的问题:
- 终端:您将面临的第一个问题是使用 Windows 终端(CMD),因为它不支持 Unix 命令(如 Linux 或 Mac)。解决方案是安装一个 Unix 终端;最强烈的建议是使用Git附带的Git Bash终端进行安装(https://git-scm.com ),第二个选项是安装Cygwin,这是 Windows(中的 Linux 终端 https://www.cygwin.com 。
- 环境变量:使用 Windows 的另一个常见问题是设置环境变量。通常,在编写 npm 脚本时,我们会设置环境变量,如
NODE_ENV=production
或BABEL_ENV=development
,但要在 Windows 中设置这些变量,您需要使用SET
命令,这意味着您需要执行SET NODE_ENV=production
或SET BABEL_ENV=development
。问题是,如果您与其他使用 Linux 或 Mac 的人一起工作,他们的SET
命令将出现问题,您可能需要忽略此文件,并仅为您的本地环境修改它。这可能很乏味。这个问题的解决方案是使用一个名为 cross-env 的包;您可以通过npm install cross-env
进行安装,这将在 Windows、Mac 和 Linux 中运行:
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --
mode development --open",
"start-production": "cross-env NODE_ENV=production webpack-dev-
server --mode production"
}
- 区分大小写的文件或目录:实际上,在 Linux 上也会发生这种情况,但有时很难确定这个问题,例如,如果您在
components/**home**/Home.jsx
目录中创建了一个组件,但在代码中您正试图像这样导入该组件:
import Home from './components/Home/Home';
Normally, this won't cause any problems on Mac but can generate an error on Linux or Windows because we are trying to import a file with a different name (because it's case-sensitive) into the directory.
- 路径:Windows 使用反斜杠(
\
)定义路径,而在 Mac 或 Linux 中使用正斜杠(/
)。这是有问题的,因为有时我们需要定义一个路径(主要在 Node.js 中),我们需要这样做:
// In Mac or Linux
app.use(
stylus.middleware({
src: __dirname + '/stylus',
dest: __dirname + '/public/css',
compile: (str, path) => {
return stylus(str)
.set('filename', path)
.set('compress', true);
}
})
);
// In Windows
app.use(
stylus.middleware({
src: __dirname + '\stylus',
dest: __dirname + '\public\css',
compile: (str, path) => {
return stylus(str)
.set('filename', path)
.set('compress', true);
}
})
);
// This can be fixed by using path
import path from 'path';
// path.join will generate a valid path for Windows or Linux and Mac
app.use(
stylus.middleware({
src: path.join(__dirname, 'stylus'),
dest: path.join(__dirname, 'public', 'css'),
compile: (str, path) => {
return stylus(str)
.set('filename', path)
.set('compress', config().html.css.compress);
}
})
);
```******