dainiel

React.js 常见问题小结

原文链接: medium.com

1 —— 组件名没有用大写字母开头

React组件名必须大写字母开头。

如果组件名不以大写字母开头,组件会被当作是_内置_元素,像是divspan

例如:

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标签。

注意上面的“My Awesome Button”并没有渲染,React只渲染了一个空的HTML button元素。这种情况下React不会警告你。

2——使用单引号而不是反撇号

用反撇号(`...`) 创建的字符串和用单引号('...')创建的不一样。

在大部分键盘上,反撇号 (_`_)字符可以用 _tab_ 上的那个键输入。

当我们需要在字符串里面包含动态表达式的时候(这样就不需要要字符串拼接),我们会创建一个反撇号的字符串。

`这个字符串模板可以包含表达式`
'这只是字符串,这里你不能包含表达式'

一起看下如果需要一个不间断显示当前时间的字符串: “Time is ...”

// 当前时间字符串
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的形式被使用,但是再也用不了了。

相应的,你需要:

  1. 把新的prop-types包加到你的项目中:npm install prop-types
  2. 引入它: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类体里你不可以乱写。你只能使用规定的语法定义方法和属性。

这让人有点混乱因为类语法里的{}_很像常见的块级作用域,但并不是。 _ 在一个由函数构成的组件里,你就可以想怎么搞就怎么搞: // 这样完全没问题:

const Number = (props) => {
const arrayOfNumbers = _.range(1, 10);

// ...
};


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

你可以用一个字符串传递属性值

<Greeting name="World" />

如果你需要传递数字类型的值,不要使用字符串: // 不要这么做

<Greeting counter="7" /> 取而代之,使用花括号传递实际数值: // 用这个替代 <Greeting counter={7} /> 在 Greeting 组件里面使用{7}this.props.counter会被赋值7,并且对其进行数学运算也不会有问题。如果你传了“7”还把它当成数字,你可能会运行出意想不到的结果。 #### 7 ——忘记了另外一个app在用同样的端口 为了运行web服务器,你需要使用host(像是127.0.0.1)和端口(像是8080)来让服务器监听有效http地址的请求。 一旦成功运行,web服务器就占据了那个端口。你就不能让这个端口它用。端口会被占用。 如果你在另外一个终端运行同样的服务器,你会得到一个端口被占用的错误提示。 像是这样: Error: listen EADDRINUSE 127.0.0.1:8080 注意有时候web服务器会运行在 _后台_或者在分开的屏幕/tumux session。你看不见,但是它一直占据着端口。为了重启服务器,你需要“杀死”正在运行的那个。 为了鉴别正在使用特定端口的进程,你可以使用像psgrep 一些关于app的相关信息)的命令,或者如果知道端口号你也可以使用lsof命令: lsof -i :8080 #### 8 ——忘了创建环境变量 一些项目要依赖shell环境变量才能运行。如果你在没有所需环境变量的情况下运行这些项目,它们会复制undefined,这样会给你带来潜在的未知错误。 例如,如果项目连到像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 = () => {

Hello World

}; 这样用: const Greeting = () => (

Hello World

); 当你使用带有箭头函数的中括号时,你就新起了一个函数的作用域。箭头函数的缩写语法不用中括号。 #### 10——不要用圆括号包裹对象 当你想创建一个返回普通对象的短箭头函数时,上面中括号和圆括号的问题同样会让人困扰。 不要用: const myAction = () => { type: 'DO_THIS' }; 这样用: const myAction = () => ({ type: 'DO_THIS'}); 如果没有把对象包裹在圆括号里,你就不能使用缩写语法。实际上你会给字符串定义一个标签。 这在setState方法中的updater函数里很常见,因为其需要返回一个对象。如果想使用短箭头函数语法,你需要用圆括号包裹对象。 不要用: this.setState(prevState => { answer: 42 }); 这样用: this.setState(prevState => ({ answer: 42 })); #### 11 ——没有正确使用API元素和属性的大小写 是React.Component,而不是React.component。是componentDidMount,而不是ComponentDidMount。_通常_是ReactDOM,而不是ReactDom。 注意下你需要的API大写情况。如果使用了错误的大写,你得到的错误信息可能不会很明确。 当引用reactreact-dom,确保你引用了正确的名字,以及你使用和你的引用的是完全一样的。ESLint可以帮你指出没有被使用的地方。 在处理组件属性时也会遇到这种问题: <Greeting userName="Max" /> // 组件内部,你需要的话 props.userName 如果你没有使用props.userName,而是props.usernameprops.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并不会触发渲染机制。 #### 13——把<tag/>和</tag>搞混了 在闭合标签里放错/字符。不可否认,有时你可以使用<tag/>,而其他时间你需要</tag>。 在HTML里面,有个叫“自闭合标签”(非注释标签)的东西。这些标签表示没有任何子节点的元素。例如, img标签就是一个自闭合元素: <img src /> // 不要用 div 标签可以有子元素,因此你可以用开始和结束标签: <div> Children here... </div> 这点对于React组件也有效。如果组件有子内容,其可能看上去像这样: <Greeting>Hello!</Greeting> // 注意/字符的位置。 如果组件没有子元素,你可以用开始/结束标签书写,或者就用一个自闭合标签: // 2个有效的方式 ` <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(你需要了解它们,如果在前端使用它们,并且也想做SRR的话),你需要有可以编译其的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元素相关联的组件实例,而这个DOM元素也代表了组件。

React内部实现确保了类方法里的“this”指向实例。然而,当你使用 whoIsThis 方法的_引用_时, JavaScript没有自动绑定实例。

whoIsThis里的console.dir那行会正确显示组件实例,因为方法是render方法里用一个_显式_调用对象(this)来调用的。当你执行上面代码时,你可以看下console里的Greeting对象。

然而,当你在延迟执行通道,例如事件处理里执行同样方法时,调用对象将不再是显性的,console.dir那样也不会显示对象实例。 查看代码和下面的(点击后的)输出

上面的代码中,当你点击字符串时React会调用whoIsThis方法,但是在方法内部,其不会让你获取到组件实例。这就是当我们点击字符串时,你得到undefined的原因。如果你的类方法需要使用像this.propsthis.state这样的方法,这就会是个大问题。它就是不会生效。 这个问题有很多解决方案。你可以把方法放在一个内联函数里,或者使用.bind调用来强制方法记住它的调用者。这两点对于不频繁更新的组件都是可以的。你也可以通过在类的constructor里优化bind方法,而不是在render方法里。然后,对于这个方法最好的解决方案是通过Babel来使用ECMAScript类域特性(目前还是stage-3),这样对于处理程序只需使用箭头函数就可以了:

class Greeting extends React.Component {
whoIsThis = () => {
console.dir(this);
}

render() {
return (


Hello World

);
}
}

这样会和预期一样执行:

这就是本文所有内容了。感谢阅读。