React 常见的 15 个问题

DuttPandora 发布于1年前

摘要:学习 React。

Fundebug经授权转载,版权归原作者所有。

jsComplete ,我们管理一个专门用于帮助编程学习者 slack 帐户 。我们常常会收到一些有趣的问题,但大多数问题都是常见问题。 我创建这个资源为了帮助 React.js 学习者遇到这些常见的问题时提供一定帮助。在这里可以快速找到一些常见问题的解决方案,而不是一,遍又一遍去找解决方法,我会持续更新这些常见的问题。

1. 组件的名称开头要大写

React 组件名称必须具有以大写字母开头。

如果组件名称不以大写字母开头,则组件使用将被视为内置元素,例如 div 或 span 。

例如:

class greeting extends React.Component {
    // ...
}

如果尝试渲染 <greeting /> ,React 将忽略上述内容,会报以下警告:

Warning: The tag <greeting> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

这里更大的问题是把组件命名为 button 或 img 。 React 会忽略你的组件,只是渲染一个普通 HTML button 或 img 标签。

React 常见的 15 个问题

注意上面没有渲染 “My Awesome Button” 和 React 刚刚呈现的空 HTML 按钮元素。 在这种情况下,React 不会警告你。

2. 使用单引号而不是反引号

用反引号 ( … ) 创建的字符串与用单引号 ('…') 创建的字符串不同。

在大多数键盘上,可以使用 tab 键上方的键来输入用反引号( ` )字符。

当需要在字符串中包含动态表达式时,使用反引号创建一个字符串(不需要使用字符串连接)。

`This is a string template literal that can include expressions`;
("This is just a string, you cannot include expressions here");

假设你想要一个始终报告当前时间的字符串:

“Time is …”

// Current time string
const time = new Date().toLocaleTimeString();
// 使用普通字符串(单引号或双引号)时,需要使用字符串连接:
"Time is " +
    time`Time is ${
        time // 在使用反引号时,可以使用 ${} 在字符串中注入时间
    }`;

此外,反引号还声明一个字符串时,可以创建一个跨多行的字符串:

const template = `I
CAN
SPAN
Multiple Lines`;

常规字符串不能这样做。

3. 使用 React.PropTypes

PropTypes 对象已从 React 中删除。 其过去是以 React.PropTypes 的形式被使用,但不能再使用它了。

相应的,你需要:

npm install prop-types
import PropTypes from 'prop-types'

然后就可以使用它啦,如: PropTypes.string 。

如果你错误地使用了 React.PropTypes ,会得到这样的错误提示:

TypeError: Cannot read property 'string' of undefined

4. 没有使用指南里指定的版本

在看或阅读有关代码的内容以及使用指南里的例子时,确保你使用的库版本与例子的里的版本是一致的。一般使用最新版本是没有问题,但是如果内容过时,你可能会遇到一些弃用问题。

为了安全起见,请使用主干版本。 例如,如果教程里使用的是 React 16,自己就不要使用 React 15 进行开发了。

这对 Node.js 也很重要。如果使用旧版本的 Node,会遇到一系列问题。 例如,如果你正在看一些教程,这些教程使用了 Object.values ,而你现在用 Node 6.x,那个版本此方法是不存在的。 你需要 Node 7.x 或更高版本。

5. 令人困惑的函数与类

你能看出下面的代码有什么问题吗?

class Numbers extends React.Component {
  const arrayOfNumbers = _.range(1, 10);
  // ...
}

上面的代码是无效的,因为在 JavaScript 类的内部,不能随意定义变量,只能使用规定的语法定义方法和属性。

这有点令人困惑,因为类语法中使用的 {} 看起来像块级作用域,但它并不是。

在一个由函数构成的组件里,你就可以想怎么搞就怎么搞:

// Totally Okay:
const Number = props => {
    const arrayOfNumbers = _.range(1, 10);
    // ...
};

6. 将数字作为字符串传递

你可以通过 prop 属性传递一个字符串:

<Greeting name="World" />

如果需要传递一个数值,不要使用字符串:

// 不要这样做 <Greeting counter="7" />

相反,使用花括号传递一个实际的数值:

<Greeting counter="{7}" />

在 Greeting 组件中使用 {7} , this.props.counter 就被赋值为数字 7 ,并且可以对其进行数学运算。 如果将其作为“7”传递,然后将其视为数字,则可能会遇到意外结果。

7. 忘记了另外一个 app 在用同样的端口

要运行 web 服务器,需要使用主机(如 127.0.0.1 )和端口(如 8080)使服务器侦听有效 http 地址上的请求。

一旦成功运行,web 服务器就占据了那个端口,你就不能让这个端口它用,端口会被占用。

如果你尝试在另一个终端上运行相同的服务器,将会得到端口被占用的错误提示,如下:

Error: listen EADDRINUSE 127.0.0.1:8080

请注意,有时 Web 服务器可能在后台运行或在分离的屏幕/tmux 会话中运行。你看不到,但它仍然占据了端口。 要重新启动服务器,需要“杀死”仍在运行的服务器。

要识别使用某个端口的进程,可以使用 ps 之类的命令(以及关于应用程序的 grep),或者如果你知道端口号,则可以使用 lsof 命令:

lsof -i :8080

8. 忘记创建环境变量

有些项目依赖于 shell 环境变量的存在才能启动。 如果在没有所需环境变量的情况下运行这些项目,它们将尝试为它们使用未定义的值,并可能会给你一些神秘的错误。

例如,如果项目连接到像 MongoDB 这样的数据库,则可能会使用像 process.env.MONGO_URI 这样的环境变量来连接它。 这允许项目与不同环境中的不同 MongoDB 实例一起使用。

要在本地运行连接到 MongoDB 的项目,首先必须导出 MONGO_URI 环境变量。 例如,如果你在端口 27017 上运行本地 MongoDB,则需要在运行项目之前执行此操作:

export MONGO_URI="mongodb://localhost:27017/mydb"

你可以 grep 项目源代码,找到 process.env 来查清楚其运行正常所需要的环境变量。

9. 把花括号{}和圆括号()搞混

不要用:

return {
  something();
};

这样用:

return (
  something();
);

第一个将尝试(并且会失败)返回一个对象,而第二个将正确调用 something() 函数并返回该函数返回的内容。

因为 JSX 中的任何 <tag> 都将转换为函数调用,所以在返回任何 JSX 时都会出现这个问题。

这个问题在箭头函数的缩写语法中也很常见。

不要用:

const Greeting = () => {
    <div>Hello World</div>;
};

这样用:

const Greeting = () => <div>Hello World</div>;

当你使用带有箭头函数的中括号时,你就新起了一个函数的作用域。箭头函数的缩写语法不用中括号。

10. 不使用圆括号包装对象

当你想要创建一个返回普通对象的箭头函数时,上面的花括号和圆括号问题也会让你感到困惑。

不要用:

const myAction = () => {
    type: "DO_THIS";
};

这样用:

const myAction = () => ({ type: "DO_THIS" });

如果没有把对象包裹在圆括号里,你就不能使用缩写语法。实际上你会给字符串定义一个标签。

这在 setState 方法的 updater 函数中很常见,因为它需要返回一个对象。 如果要使用箭头函数语法,则需要用括号包装该对象。

不要用:

this.setState(prevState => {
    answer: 42;
});

这样用:

this.setState(prevState => ({ answer: 42 }));

代码部署后可能存在的 BUG 没法实时知道,事后为了解决这些 BUG,花了大量的时间进行 log 调试,这边顺便给大家推荐一个好用的 BUG 监控工具Fundebug。

11. 没有正确使用 API 元素和属性的大小写

使用 React.Component ,而不是 React.component 。 使用 componentDidMount ,而不是 ComponentDidMount 。使用 ReactDOM ,而不是 ReactDom 。

请注意需要的 API 大小写。 如果使用不正确的大小写,得到的错误信息可能不会很明确。

从 react 和 react-dom 导入时,确保引入正确的名称,并且使用的内容与引入完全相同,ESLint 可以帮助你指出未使用的内容。

在处理组件属性时也会遇到这种问题:

<Greeting userName="Max" /> // 在组件内部,你需要使用 props.userName
来获取传入的值

如果你没有使用 props.userName ,而是 props.username 或 props.UserName ,你会相当于用了一个 undefined 的值。需要特别留意下这点,当然更好的是配置 ESLint ,它也能指出这些问题。

12. 将 state 对象与实例属性搞混

在类组件中,可以定义本地 state 对象,然后使用 this 访问它:

class Greeting extends React.Component {
    state = {
        name: "World"
    };
    render() {
        return `Hello ${this.state.name}`;
    }
}

以上代码会输出 “Hello World” 。

除 state 之外,你还可以定义其它本地实例属性。

class Greeting extends React.Component {
    user = {
        name: "World"
    };
    render() {
        return `Hello ${this.user.name}`;
    }
}

以上代码也会输出 “Hello World” 。

state 实例属性是一个特殊属性,因为 React 会管理它。 你只能通过 setState 更改它,当你这样做时 React 会做出响应。

然而,定义的所有其他实例属性对渲染算法没有影响。 你可以根据需要在上面的示例中更改 this.user ,并且 React 不会在 React 中触发渲染机制。

13. 将 与</ tag> 搞混

在闭合标签里放错 / 字符。不可否认,有时你可以使用 <tag/> ,而其他时间你需要 </tag> 。

在 HTML 中,有一种称为“自闭合标签”(AKA void tag)。这些是表示没有任何子节点的元素的标记。例如,img 标签是一个自闭合标签:

<img src="..." />
// 不必使用 <img></img>

div 标签可以包含子项,因此你可以使用开始和结束标记:

<div>
    Children here...
</div>

这同样适用于 Reac t 组件,如果组件具有子元素,如下所示:

<Greeting>Hello!</Greeting> // 注意/字符的位置

如果组件没有子元素,可以用开始/结束标签书写,或者用自闭合标签:

// 两种方式
<Greeting></Greeting>
<Greeting />

以下方式是非法的:

// 错误 <Greeting><Greeting /> </Greeting>

如果放错放了 / 字符的位置,将收到如下错误:

Syntax error: Unterminated JSX contents

14. 假设 import/export 起作用

import/export 特性是 JavaScript 中的官方功能(自 2015 年起)。 其只是 ES2015 的特性,并且还没有在流行浏览器和最新版 Node 里面被完整支持。

React 项目的流行配置使用 Webpack 和 Babel。 两者都允许使用此特性并将其编译为所有浏览器都能理解的内容。 只有工作流中有 Webpack 或 Babel 之类的转译工具,才能使用 import/export 。

但是,在 React 打包应用程序中 import/export 不意味着可以随意使用它们! 例如,如果你还通过最新的 Node 进行服务器端渲染,会行不通,你很可能会收到 “unexpected token” 错误。

要让 Node 理解 import/export (如果你在前端使用它们就是你需要了解它们,并且也想要做 SSR 渲染的话),你需要有可以编译其的 Babel preset(像是 env preset),才能在 Node 端运行。 你可以在开发时使用像 pm2\ 、 _nodemon_ 和 babel-watch 的工具做到这点,,并在每次更改内容时重新启动 Node。

15. 不绑定处理程序方法

我把这个留到最后,因为这是一个大问题,一个很常见的问题。

你可以在 React 组件中定义类方法,然后在组件的 render 方法中使用它们。 例如:

class Greeting extends React.Component {
    whoIsThis() {
        console.dir(this); // "this" is the caller of whoIsThis
        return "World";
    }
    render() {
        return `Hello ${this.whoIsThis()}`;
    }
}
ReactDOM.render(<Greeting />, mountNode);

我在 render 里以 this.whoIsThis 的方式调用 whoIsThis 方法,因为在 render 中, this 关键字指的是与表示组件的 DOM 元素关联的组件实例。

内部 React 确保其类方法中的 “this” 指向实例。 但是,当你使用 whoIsThis 方法的引用时,JavaScript 不会自动绑定实例。

whoIsThis 方法中的 console.dir 可以正确告诉我们当前组件实例,因为该方法是使用显式调用方( this )直接从 render 方法调用的。 执行上面的代码时,在控制台中看到 Greeting 对象:

React 常见的 15 个问题

然而,当在延迟执行通道,例如事件处理里执行同样方法时,调用对象将不再是显性的, console.dir 也不会打印当前组件实例。

React 常见的 15 个问题

在上面的代码中,当单击字符串时,React 会调用 whoIsThis 方法,但它不会让你访问组件实例。 这就是我们点击字符串时得到 undefined 的原因。 如果类方法需要访问像 this.props 和 this.state 这样的属性,这会是一个问题,因为它根本行不通。

对于这个问题有很多解决方案。可以将方法包装在内联函数中,或使用 .bind 来改变 this 指向。 对于不经常更新的组件,两者都可以。

还可以通过在类的构造函数中而不是在 render 方法中执行来优化 bind 方法。 但是,此方法的最佳解决方案是通过 Babel 来使用 ECMAScript 类字段我(目前还是 stage-3),这样对于处理程序只需使用箭头函数就可以了:

class Greeting extends React.Component {
    whoIsThis = () => {
        console.dir(this);
    };
    render() {
        return <div onClick={this.whoIsThis}>Hello World</div>;
    }
}

这样会和预期一样执行:

React 常见的 15 个问题

原文: React.js Commonly Faced Problems

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎大家免费试用!

React 常见的 15 个问题

查看原文: React 常见的 15 个问题

  • bluemeercat
  • tinypanda
  • brownfrog
  • tinyswan
  • redbird
  • orangekoala
  • smallbutterfly
  • brownladybug