深入JSX

指定 React 元素类型

从本质上讲,JSX 只是为 React.createElement(component, props, …children) 函数提供的语法糖

一个 JSX 标签的开始部分决定了 React 元素的类型

首字母大写的标签指示 JSX 标签是一个 React 组件

用户定义组件必须以大写字母开头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
对 JSX 类型使用点语法
在 JSX 中,你也可以使用点语法引用一个 React 组件。
如果你有一个单一模块(module) ,但却 导出(exports) 多个 React 组件时,这将会很方便。
*/
import React from 'react';

const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>;
}
}

function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />;
}

JSX 中的 props(属性)

JavaScript 表达式可以作为 props(属性)

props(属性) 默认为 “true”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
如果你已经有一个 object 类型的 props,并且希望在 JSX 中传入,
你可以使用扩展操作符 ... 传入整个 props 对象。
*/
/*
当你构建一个一般容器时,属性扩展非常有用。然而,这可能会使得你的代码非常混乱,
因为这非常容易使一些不相关的 props(属性) 传递给组件,而组件并不需要这些 props(属性) 。
因此我们建议谨慎使用该语法。
*/
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}

JSX 中的 Children

JavaScript 表达式可以作为 Children(子元素)
通过使用 {} 包裹,你可以将任何的 JavaScript 元素而作为 children(子元素) 传递

Functions(函数) 可以作为 Children(子元素)
通常情况下,嵌入到 JSX 中的 JavaScript 表达式会被认为是一个字符串、React元素 或者是这些内容的一个列表。然而, props.children 类似于其他的 props(属性) ,可以被传入任何数据,而不是仅仅只是 React 可以渲染的数据。例如,如果有自定义组件,其 props.children 的值可以是回调函数

Booleans, Null, 和 Undefined 被忽略


使用 PropTypes 进行类型检查

随着应用规模的提升,你可以通过类型检测捕获更多的bug。React 内置了类型检测的功能,要在组件中进行类型检测,你可以赋值 propTypes 属性。
具体用法:http://www.css88.com/react/docs/typechecking-with-proptypes.html

像 Flow 和 TypeScript 这样的静态类型检查器可以在运行代码之前识别某些类型的问题。 他们还可以通过添加自动完成功能来改善开发人员的工作流程。 出于这个原因,对于更大的代码库我们建议使用 Flow 或者 TypeScript 来替代 PropTypes。

Flow、TypeScript用法:http://www.css88.com/react/docs/static-type-checking.html

Refs 和 DOM

使用 refs 的场景:

  • 处理focus、文本选择或者媒体播放
  • 触发强制动画
  • 集成第三方DOM库

解决了一些从父组件访问子组件的DOM元素的方法(后续再仔细研究吧,云里雾里~~)


不受控组件

在受控组件中,表单数据由 React 组件负责处理。在不受控组件中,其表单数据由 DOM 元素本身处理。


优化性能

  • 在开发时使用开发版本(React包含很多在开发过程中很有帮助的警告,体积大),发布时使用生产版本
    插件的配置http://www.css88.com/react/docs/optimizing-performance.html
  • 避免重新渲染
  • 避免更新
  • 不要突变(mutate) props 或 state 的值
  • 使用 Immutable 数据结构

一致性比较

添加key属性


片段

React 中一个常见模式是为一个组件返回多个元素。 片段(fragments) 可以让你将子元素列表添加到一个分组中,并且不会在DOM中增加额外节点。

1
2
3
4
5
6
7
8
9
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}

动机

一个常见模式是为一个组件返回一个子元素列表

1
2
3
4
5
6
7
8
9
10
11
class Table extends React.Component {
render() {
return (
<table>
<tr>
<Columns />
</tr>
</table>
);
}
}

为了渲染有效的 HTML<Columns /> 需要返回多个 <td> 元素。如果 <Columns />render() 函数里面使用一个父级 div ,那么最终生成的 HTML 将是无效的。所以,这种情况就需要使用 Fragment:

1
2
3
4
5
6
7
8
9
10
class Columns extends React.Component {
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
}

简写语法
可以像使用其他元素一样使用<></>,只是它不支持 键(keys) 或 属性(attributes)。
但是目前很多工具都不支持。

1
2
3
4
5
6
7
8
9
10
class Columns extends React.Component {
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
}

如果你需要一个带 key 的片段,你可以直接使用 <React.Fragment /> 。 一个使用场景是映射一个集合为一个片段数组 — 例如:创建一个描述列表:
key 是唯一可以传递给 Fragment 的属性。在将来,我们可能增加额外的属性支持,比如事件处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// 没有`key`,将会触发一个key警告
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}


插槽(Portals)

通常来说,当你从组件的 render 方法返回一个元素时,它将被作为子元素被装载到最近父节点 DOM 中。
然而,有时候需要将子元素插入到 DOM 节点的其他位置。
Portals 提供了一种很好的方法,将子节点渲染到父组件 DOM 层次结构之外的 DOM 节点。


错误边界(Error Boundaries)

过去,组件内的 JavaScript 错误常常会破坏 React 内部状态,并导致它在下一次渲染时产生 神秘的 错误。这些错误总会在应用代码中较早的错误引发的,但 React 并没有提供一种方式能够在组件内部优雅地来处理,也不能从错误中恢复。

错误边界是 React 组件,它可以在子组件树的任何位置捕获 JavaScript 错误,记录这些错误,并显示一个备用 UI ** ,而不是使整个组件树崩溃。 错误边界(Error Boundaries) 在渲染,生命周期方法以及整个组件树下的构造函数中捕获错误。

注意
错误边界 无法 捕获如下错误:

  • 事件处理
  • 异步代码 (例如 setTimeout 或 requestAnimationFrame 回调函数)
  • 服务端渲染
  • 错误边界自身抛出来的错误 (而不是其子组件)

如果一个类组件定义了一个名为 componentDidCatch(error, info) (info 是一个含有 componentStack 属性的对象。这一属性包含了错误期间关于组件的堆栈信息。): 的新生命周期方法,它将成为一个错误边界:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
}

而后你可以像一个普通的组件一样使用:

1
2
3
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>

componentDidCatch() 方法机制类似于 JavaScript catch {},但是针对组件。仅有类组件可以成为错误边界。实际上,大多数时间你仅想要定义一个错误边界组件并在你的整个应用中使用。

注意错误边界(Error Boundaries) 仅可以捕获其子组件的错误。错误边界无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会向上冒泡至最接近的错误边界。这也类似于 JavaScriptcatch {} 的工作机制。

为何不使用 try/catch?
try / catch 非常棒,但其仅能在命令式代码(imperative code)下可用。
然而,React 组件是声明式的并且具体指出 声明 什么需要被渲染。错误边界保留了 React 原生的声明性质,且其行为符合你的预期。例如,即使错误发生 componentDidUpdate 时期由某一个深层组件树中的 setState 调用引起,其仍然能够冒泡到最近的错误边界。

一个AI:
命令式编程 vs 声明式编程 http://www.vaikan.com/imperative-vs-declarative/

如果你需要在事件处理器内部捕获错误,使用普通的 JavaScript try / catch 语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}

handleClick = () => {
try {
// Do something that could throw
} catch (error) {
this.setState({ error });
}
}

render() {
if (this.state.error) {
return <h1>Caught an error.</h1>
}
return <div onClick={this.handleClick}>Click Me</div>
}
}


Web 组件

React 和 web组件 被用以解决不同问题。Web组件为可重用组件提供了强大的封装能力,而React则是提供了保持DOM和数据同步的声明式库。二者目标互补。作为开发者,你可以随意地在Web组件里使用React,或者在React里使用Web组件,或都有。


高阶组件

组件是React中代码重用的最小单元
高阶组件是一个函数,能够接受一个组件并返回一个新的组件
组件是将props转化成UI,然而高阶组件将一个组价转化成另外一个组件

如何创建 http://www.css88.com/react/docs/higher-order-components.html

警告:

  • 不要在render函数中使用高阶组件
  • 静态方法必须复制
  • Refs不会被传递

与其他库整合

对于 React 之外的 DOM 节点操作,React 是不会去处理的,因为 React 内部有自己的渲染逻辑。当相同的 DOM 节点被外部的代码改变时,React 就会很迷茫,并不知道发生了什么。

但这也不意味着 React 无法与其他操作 DOM 节点的库一起使用,你只要清楚他们分别在做什么就可以了。

最简单的方式就是阻止 React 更新外部在操作的节点,那么你可以通过生成一个 React 根本不会去更新的元素来实现,比如空的 <div />


代码拆分

打包(Bundling) 是一个处理过程,跟踪导入的文件并将其合并到单个文件:“包” 。然后,这个包文件可以包含到网页上,这样可以一次性加载整个应用程序。