React.js 常见问题
在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 将忽略你的组件并仅渲染原始 HTMLbutton
或img
标签。
请注意,上面的“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();
// When using regular strings (single or double quotes),
// you need to use string concatenation:
'Time is ' + time
// When using back-ticks,
// you can inject the time in the string using ${}
`Time is ${time}`
此外,当使用字符串文字(带有反引号)时,您可以创建跨越多行的字符串:
const template = `I
CAN
SPAN
Multiple Lines`;
您不能使用常规字符串来做到这一点。
3—使用 React.PropTypes
该PropTypes
对象已从 React 中移除。它之前可用,React.PropTypes
但现在无法再使用。
相反,您需要:
- 将新的prop-types包添加到您的项目中:
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" />
如果需要传递数值,请不要使用字符串:
// Don't do this
<Greeting counter="7" />
相反,使用花括号传递实际的数值:
// Do this instead
<Greeting counter={7} />
{7}
在组件内部使用Greeting
,this.props.counter
将获得实际的数值7
,并且可以安全地对其进行数学运算。如果您将其作为参数传递“7”
,然后将其视为数字,则可能会遇到意外的结果。
7 — 忘记另一个应用程序实例仍在使用同一端口
要运行 Web 服务器,您需要使用主机(如 127.0.0.1)和端口(如 8080)来使服务器监听有效的 http 地址上的请求。
一旦 Web 服务器成功运行,它就拥有了该端口的控制权。您不能将同一端口用于其他用途。该端口将处于繁忙状态。
如果你尝试在另一个终端运行同一个服务器,你会收到一个错误,提示端口“正在使用”。例如:
Error: listen EADDRINUSE 127.0.0.1:8080
请注意,有时 Web 服务器可能正在后台运行,或者在分离的 screen/tmux 会话中运行。您看不到它,但它仍然占用着端口。要重启服务器,您需要“终止”仍在运行的服务器。
要识别使用特定端口的进程,您可以使用类似命令ps
(以及grep
有关您的应用程序的某些内容),或者如果您知道端口号,则可以使用以下lsof
命令:
lsof -i :8080
8 — 忘记创建环境变量
有些项目依赖于 shell 环境变量的存在才能启动。如果您在缺少所需环境变量的情况下运行这些项目,它们将尝试使用未定义的值,并可能引发一些难以理解的错误。
例如,如果一个项目连接到 MongoDB 之类的数据库,它很可能使用类似的环境变量process.env.MONGO_URI
来连接它。这使得该项目可以在不同的环境中使用不同的 MongoDB 实例。
要在本地运行连接到 MongoDB 的项目,您必须MONGO_URI
先导出环境变量。例如,如果您在端口 上运行本地 MongoDB 27017
,则需要在运行项目之前执行此操作:
export MONGO_URI="mongodb://localhost:27017/mydb"
您可以grep项目源代码来process.env
找出它需要哪些环境变量才能正常工作。
9——混淆花括号 {} 和圆括号 ()
而不是:
return {
something();
};
你需要:
return (
something();
);
第一个将尝试(并失败)返回一个对象,而第二个将正确调用该something()
函数并返回该函数返回的内容。
由于<tag>
JSX 中的任何内容都会转换为函数调用,因此在返回任何 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
,因为它需要返回一个对象。如果要使用短箭头函数语法,则需要用括号将该对象括起来。
而不是:
this.setState(prevState => { answer: 42 });
你需要:
this.setState(prevState => ({ answer: 42 }));
11 — API 元素和属性的大小写不正确
不是React.Component
,不是React.component
。不是componentDidMount
,不是ComponentDidMount
。通常 ReactDOM
不是ReactDom
。
请注意您需要的 API 大小写。如果使用不正确的大小写,您收到的错误可能无法清楚地说明问题所在。
从react
和导入时react-dom
,请确保导入的名称正确,并且所使用的内容与导入的内容完全相同。ESLint 可以帮助您指出未使用的内容。
在访问组件 props 时,这个问题也很常见:
<Greeting userName="Max" />
// Inside the component, you need
props.userName
如果props.userName
您错误地使用了props.username
或 而不是props.UserName
,那么您将使用未定义的值。请注意这一点,或者更好的是,让您的 ESLint 配置也指出这些问题。
12. 混淆状态对象和实例属性
在类组件中,您可以定义本地state
对象,然后使用以下命令访问它this
:
class Greeting extends React.Component {
state = {
name: "World",
};
render() {
return `Hello ${this.state.name}`;
}
}
以上将输出“Hello World”。
您还可以除了状态之外定义其他本地即时属性:
class Greeting extends React.Component {
user = {
name: "World",
};
render() {
return `Hello ${this.user.name}`;
}
}
以上也会输出“Hello World”。
实例state
属性比较特殊,因为 React 会管理它。你只能通过修改它来更改setState
,React 会在你修改时做出反应。但是,你定义的所有其他实例属性都不会影响渲染算法。你可以随意更改this.user
上面示例中的实例属性,React 不会触发渲染循环。
13 — 将 <tag/> 与 </tag> 混淆
不要把/
结束标签中的字符放错位置。诚然,有时你可以使用<tag/>
,有时则需要</tag>
。
HTML 中有一种叫做“自闭合标签”(又称空标签)的东西。这些标签表示没有任何子节点的元素。例如,这个img
标签就是一个自闭合标签:
<img src="..." />
// You don't use <img></img>
标签div
可以有子标签,因此您可以使用开始和结束标签:
<div>
Children here...
</div>
这同样适用于 React 组件。如果组件有子内容,它可能看起来像这样:
<Greeting>Hello!</Greeting>
// Notice the position of the / character.
如果组件没有子组件,则可以使用打开/关闭标签或仅使用自关闭标签来编写它:
// 2 valid ways
<Greeting></Greeting>
<Greeting />
// Notice how the / character moves based on whether the element
// is self-closing or not
以下使用无效:
// Wrong
<Greeting><Greeting />
如果你放错了/
字符,你将会得到如下错误:
Syntax error: Unterminated JSX contents
14 — 假设导入/导出功能正常
导入/导出功能是 JavaScript 的官方特性(自 2015 年起)。然而,它是唯一一个尚未在现代浏览器和最新 Node.js 中完全支持的 ES2015 特性。
React 项目的常用配置是 Webpack 和 Babel。两者都支持此功能,并将其编译为所有浏览器都能理解的内容。只有当你的流程中包含 Webpack 或 Babel 之类的工具时,才能使用 import/export。
然而,在 React 捆绑应用中启用 import/export 并不意味着你可以在任何你想要的地方使用它们!例如,如果你还通过最新的 Node 进行服务器端渲染,那么一切就无法正常工作。你很可能会收到“ unexpected token ”错误。
为了让 Node 也能理解 import/export(如果你在前端使用 import/export 并且想实现服务端渲染,这是必须的),你需要在 Node 上运行一个 Babel 预设(例如env预设),以便能够转译它们。你可以在开发环境中使用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);
我在方法whoIsThis
中使用了方法,因为在方法内部,关键字指的是与代表组件的 DOM 元素关联的组件实例。render
this.whoIsThis
render
this
React 内部确保其类方法中的 “ ” 指向实例。但是,当你使用对该方法的引用this
时,JavaScript 不会自动绑定实例。whoIsThis
console.dir
中的代码行将whoIsThis
正确报告组件实例,因为该方法是直接从render
带有显式调用者( )的方法中调用的。执行上述代码时,您应该在控制台中this
看到该对象:Greeting
但是,当您在延迟执行通道(例如事件处理程序)中使用相同方法时,调用者将不再显式,并且该console.dir
行将不会报告组件实例。
请参阅下面的代码和输出(单击后)。
在上面的代码中,whoIsThis
当你点击字符串时,React 会调用该方法,但它不会允许你访问其中的组件实例。这就是为什么我们点击字符串时会得到 的原因。如果你的类方法需要访问诸如和 之undefined
类的内容,那么就会出现问题。它根本无法工作。this.props
this.state
这个问题有很多解决方案。你可以将方法包装成内联函数,或者使用 .bind
call 强制方法记住其调用者。对于不经常更新的组件,这两种方法都可以。你还可以优化 bind 方法,将其放在类的构造函数中而不是 render 方法中执行。然而,解决这个问题的最佳方案是通过 Babel 启用 ECMAScript 类字段功能(目前仍处于 stage-3),并使用箭头函数作为处理程序:
class Greeting extends React.Component {
whoIsThis = () => {
console.dir(this);
}
render() {
return (
<div onClick={this.whoIsThis}>
Hello World
</div>
);
}
}
这将按预期工作:
就到这里吧。感谢阅读。
看看我的《通过构建游戏来学习 React.js》这本书:
我还有一些其他您可能感兴趣的书:
本文最初发表于此处
文章来源:https://dev.to/pluralsight/reactjs-frequently-facedproblems--l5g