react Document 官网笔记

key 只是在兄弟节点之间必须唯一

数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值

key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key 属性的值,请用其他属性名显式传递这个值:

组件本身不能直接使用key

我们推荐使用组合而非继承来实现组件间的代码重用。

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。

错误边界

需要注意的是仅有类组件可以成为异常边界

需要注意的是仅有类组件可以成为异常边界,在真实的应与开发中我们往往会声明单个异常边界然后在所有可能抛出异常的组件中使用它。另外值得一提的是异常边界并不能捕获其本身的异常,如果异常边界组件本身抛出了异常,那么会冒泡传递到上一层最近的异常边界中。
在真实地应用开发中有的开发者也会将崩坏的界面直接展示给开发者,不过譬如在某个聊天界面中,如果在出现异常的情况下仍然直接将界面展示给用户,就有可能导致用户将信息发送给错误的接受者;或者在某些支付应用中导致用户金额显示错误。因此如果我们将应用升级到 React 16.x,我们需要将原本应用中没有被处理地异常统一包裹进异常边界中。譬如某个应用中可能会分为侧边栏、信息面板、会话界面、信息输入等几个不同的模块,我们可以将这些模块包裹进不同的错误边界中;这样如果某个组件发生崩溃,会被其直属的异常边界捕获,从而保证剩余的部分依然处于可用状态。同样的我们也可以在异常边界中添加错误反馈等服务接口以及时反馈生产环境下的异常并且修复他们。

错误在渲染阶段中被捕获,但在事件处理程序中不会被捕获

this.props.children

componentDidCatch(error, errorInfo) {}

第一个参数指的是抛出的实际错误。第二个参数是指错误信息,它返回带有“componentStack”属性的对象,“componentStack”属性包含组件的错误跟踪信息。 componentDidCatch()生命周期类似Javascript中的catch {}语句。只不过是将子组件包裹在一个大的 try/catch 语句块中.它不能捕获自身的错误,而是将错误传给离它最近的异常上。

组合和props方式结合使用 不需要使用继承进行组件复用

Thinking In React

第一步:将设计好的 UI 划分为组件层级

首先,你需要在设计稿上用方框圈出每一个组件(包括它们的子组件),并且以合适的名称命名。如果你是和设计师一起完成此任务,那么他们可能已经做过类似的工作,所以请和他们进行交流!他们的 Photoshop 的图层名称可能最终就是你编写的 React 组件的名称!

但你如何确定应该将哪些部分划分到一个组件中呢?你可以将组件当作一种函数或者是对象来考虑,根据单一功能原则来判定组件的范围。也就是说,一个组件原则上只能负责一个功能。如果它需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件

在实践中,因为你经常是在向用户展示 JSON 数据模型,所以如果你的模型设计得恰当,UI(或者说组件结构)便会与数据模型一一对应,这是因为 UI 和数据模型都会倾向于遵守相同的信息结构。将 UI 分离为组件,其中每个组件需与数据模型的某部分匹配。

[外链图片转存失败(img-0UD9RQZW-1566954382545)(E:\shixixiangmuguanli\学习笔记\react-typescript\thinking-in-react-components1.png)]

你会看到我们的应用中包含五个组件。我们已经将每个组件展示的数据标注为了斜体。

  1. FilterableProductTable (橙色): 是整个示例应用的整体
  2. SearchBar (蓝色): 接受所有的用户输入
  3. ProductTable (绿色): 展示数据内容并根据用户输入筛选结果
  4. ProductCategoryRow (天蓝色): 为每一个产品类别展示标题
  5. ProductRow (红色): 每一行展示一个产品

你可能注意到,ProductTable 的表头(包含 “Name” 和 “Price” 的那一部分)并未单独成为一个组件。这仅仅是一种偏好选择,如何处理这一问题也一直存在争论。就这个示例而言,因为表头只起到了渲染数据集合的作用——这与 ProductTable 是一致的,所以我们仍然将其保留为 ProductTable 的一部分。但是,如果表头过于复杂(例如,我们需为其添加排序功能),那么将它作为一个独立的 ProductTableHeader 组件就显得很有必要了。

现在我们已经确定了设计稿中应该包含的组件,接下来我们将把它们描述为更加清晰的层级。设计稿中被其他组件包含的子组件,在层级上应该作为其子节点。

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow
第二步:用 React 创建一个静态版本

现在我们已经确定了组件层级,可以编写对应的应用了。最容易的方式,是先用已有的数据模型渲染一个不包含交互功能的 UI。最好将渲染 UI 和添加交互这两个过程分开。这是因为,编写一个应用的静态版本时,往往要编写大量代码,而不需要考虑太多交互细节;添加交互功能时则要考虑大量细节,而不需要编写太多代码。所以,将这两个过程分开进行更为合适。我们会在接下来的代码中体会到其中的区别。

在构建应用的静态版本时,我们需要创建一些会重用其他组件的组件,然后通过 props 传入所需的数据。props 是父组件向子组件传递数据的方式。即使你已经熟悉了 state 的概念,也完全不应该使用 state 构建静态版本。state 代表了随时间会产生变化的数据,应当仅在实现交互时使用。所以构建应用的静态版本时,你不会用到它。

你可以自上而下或者自下而上构建应用:自上而下意味着首先编写层级较高的组件(比如 FilterableProductTable),自下而上意味着从最基本的组件开始编写(比如 ProductRow)。当你的应用比较简单时,使用自上而下的方式更方便;对于较为大型的项目来说,自下而上地构建,并同时为低层组件编写测试是更加简单的方式。

到此为止,你应该已经有了一个可重用的组件库来渲染你的数据模型。由于我们构建的是静态版本,所以这些组件目前只需提供 render() 方法用于渲染。最顶层的组件 FilterableProductTable 通过 props 接受你的数据模型。如果你的数据模型发生了改变,再次调用 ReactDOM.render(),UI 就会相应地被更新。数据模型变化、调用 render() 方法、UI 相应变化,这个过程并不复杂,因此很容易看清楚 UI 是如何被更新的,以及是在哪里被更新的。React 单向数据流(也叫单向绑定)的思想使得组件模块化,易于快速开发。

如果你在完成这一步骤时遇到了困难,可以参阅 React 文档

第三步:确定 UI state 的最小(且完整)表示

想要使你的 UI 具备交互功能,需要有触发基础数据模型改变的能力。React 通过实现 state 来完成这个任务。

为了正确地构建应用,你首先需要找出应用所需的 state 的最小表示,并根据需要计算出其他所有数据。其中的关键正是 DRY: Don’t Repeat Yourself。只保留应用所需的可变 state 的最小集合,其他数据均由它们计算产生。比如,你要编写一个任务清单应用,你只需要保存一个包含所有事项的数组,而无需额外保存一个单独的 state 变量(用于存储任务个数)。当你需要展示任务个数时,只需要利用该数组的 length 属性即可。

我们的示例应用拥有如下数据:

  • 包含所有产品的原始列表
  • 用户输入的搜索词
  • 复选框是否选中的值
  • 经过搜索筛选的产品列表

通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:

  1. 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
  2. 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
  3. 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。

包含所有产品的原始列表是经由 props 传入的,所以它不是 state;搜索词和复选框的值应该是 state,因为它们随时间会发生改变且无法由其他数据计算而来;经过搜索筛选的产品列表不是 state,因为它的结果可以由产品的原始列表根据搜索词和复选框的选择计算出来。

综上所述,属于 state 的有:

  • 用户输入的搜索词
  • 复选框是否选中的值
第四步:确定 state 放置的位置

我们已经确定了应用所需的 state 的最小集合。接下来,我们需要确定哪个组件能够改变这些 state,或者说拥有这些 state。

注意:React 中的数据流是单向的,并顺着组件层级从上往下传递。哪个组件应该拥有某个 state 这件事,对初学者来说往往是最难理解的部分。尽管这可能在一开始不是那么清晰,但你可以尝试通过以下步骤来判断:

对于应用中的每一个 state:

  • 找到根据这个 state 进行渲染的所有组件。
  • 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
  • 该共同所有者组件或者比它层级更高的组件应该拥有该 state。
  • 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置。

根据以上策略重新考虑我们的示例应用:

  • ProductTable 需要根据 state 筛选产品列表。SearchBar 需要展示搜索词和复选框的状态。
  • 他们的共同所有者是 FilterableProductTable
  • 因此,搜索词和复选框的值应该很自然地存放在 FilterableProductTable 组件中。

很好,我们已经决定把这些 state 存放在 FilterableProductTable 组件中。首先,将实例属性 this.state = {filterText: '', inStockOnly: false} 添加到 FilterableProductTableconstructor 中,设置应用的初始 state;接着,将 filterTextinStockOnly 作为 props 传入 ProductTableSearchBar;最后,用这些 props 筛选 ProductTable 中的产品信息,并设置 SearchBar 的表单值。

你现在可以看到应用的变化了:将 filterText 设置为 "ball" 并刷新应用,你能发现表格中的数据已经更新了。

第五步:添加反向数据流

到目前为止,我们已经借助自上而下传递的 props 和 state 渲染了一个应用。现在,我们将尝试让数据反向传递:处于较低层级的表单组件更新较高层级的 FilterableProductTable 中的 state。

React 通过一种比传统的双向绑定略微繁琐的方法来实现反向数据传递。尽管如此,但这种需要显式声明的方法更有助于人们理解程序的运作方式。

如果你在这时尝试在搜索框输入或勾选复选框,React 不会产生任何响应。这是正常的,因为我们之前已经将 input 的值设置为了从 FilterableProductTablestate 传递而来的固定值。

让我们重新梳理一下需要实现的功能:每当用户改变表单的值,我们需要改变 state 来反映用户的当前输入。由于 state 只能由拥有它们的组件进行更改,FilterableProductTable 必须将一个能够触发 state 改变的回调函数(callback)传递给 SearchBar。我们可以使用输入框的 onChange 事件来监视用户输入的变化,并通知 FilterableProductTable 传递给 SearchBar的回调函数。然后该回调函数将调用 setState(),从而更新应用。

高阶指引

无障碍辅助功能
代码分割

WebpackRollupBrowserify 这类的构建工具 来打包文件

import()

​ 在你的应用中引入代码分割的最佳方式是通过动态 import() 语法。

import { add } from './math';
console.log(add(16, 26));
动态写法
import("./math").then(math => {
  console.log(math.add(16, 26));
});
注意:动态 import() 语法目前只是一个 ECMAScript (JavaScript) 提案, 而不是正式的语法标准。预计在不远的将来就会被正式接受。
这部分功能在webpack解析语法中可以得到支持,如果你使用 Create React App,该功能已配置好,你能立刻使用这个特性。Next.js 也已支持该特性而无需再配置
React.lazy

注意:

React.lazy 和 Suspense 技术还不支持服务端渲染。如果你想要在使用服务端渲染的应用中使用,我们推荐 Loadable Components 这个库。它有一个很棒的服务端渲染打包指南

React.lazy 函数能让你像渲染常规组件一样处理动态引入(的组件)。

import OtherComponent from './OtherComponent';

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}
之后
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}
将会在渲染组件时,自动导入包含 OtherComponent 组件的包。
React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。
Suspense 设置组件加载过程中显示内容
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

fallback 属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense 组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense 组件包裹多个懒加载组件。

异常捕获边界(Error boundaries)

错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

注意

错误边界无法捕获以下场景中产生的错误:

1.事件处理(了解更多)
2.异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
3.服务端渲染
4.它自身抛出来的错误(并非它的子组件)

如果一个 class 组件中定义了 static getDerivedStateFromError()componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。

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

如果模块加载失败(如网络问题),它会触发一个错误。你可以通过异常捕获边界(Error boundaries)技术来处理这些情况,以显示良好的用户体验并管理恢复事宜

错误边界定义
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}
使用
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载**

React 16 还提供了组件栈追踪。现在你可以准确地查看发生在组件树内的错误信息,

你没有使用 Create React App,可以手动将该插件[babel-plugin-transform-react-jsx-source]添加到你的 Babel 配置中。注意它仅用于开发环境,在生产环境必须将其禁用

try / catch 很棒但它仅能用于命令式代码(imperative code) 而react组件是声明式的 异常处理不能使用try /catch 。错误边界的方式进行错误异常处理。

事件处理器不会在渲染期间触发,。因此,如果它们抛出异常,React 仍然能够知道需要在屏幕上显示什么。

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


  handleClick() {
    try {
      // 执行操作,如有错误则会抛出
    } catch (error) {
      this.setState({ error });
    }
  }
基于路由的代码分割
Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');

Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。

Refs 转发
在高阶组件中转发 refs
React.forwardRef()
const ref = React.createRef();

Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。但其对某些组件,尤其是可重用的组件库是很有用的。

虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。

这个问题的解决方案是通过使用 React.forwardRef API(React 16.3 中引入)。前往 ref 转发章节了解更多

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // 注意 React.forwardRef 回调的第二个参数 “ref”。
  // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
  // 然后它就可以被挂载到被 LogPros 包裹的子组件上。
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}
Fragments 返回子列表无需添加额外的DOM

React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点

解决问题

class Table extends React.Component {
  render() {
    return ( 
    <table> <tr><Columns /></tr></table>
    );
  }
}
class Columns extends React.Component {
  render() {
    return (<div><td>Hello</td><td>World</td></div> );
   // 这种方式返回 子结果,这种解决问题,两种写法
   // return ( <React.Fragment><td>Hello</td><td>World</td></React.Fragment>)
    // return ( <><td>Hello</td><td>World</td></>)
  }
}
结果:
<table><tr>
	<div>
      <td>Hello</td>
      <td>World</td>
    </div>
  </tr></table>
这种情况下返回的结果为错误的,不能够正常显示。
而Fragments 解决了这个问题,用<React.Fragment>包裹

fragments 的新的简洁的写法

<> </> 这种方式 代替 <React.Fragment></React.Fragment>

同时,fragments也可以使用带key的方式返回多个fragment

一个使用场景是将一个集合映射到一个 Fragments 数组

function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // 没有`key`,React 会发出一个关键警告
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}
key 是唯一可以传递给 Fragment 的属性。未来我们可能会添加对其他属性的支持,例如事件
高阶组件(HOC)

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式,高阶组件是参数为组件返回值为新组件的函数。

组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。

HOC 在 React 的第三方库中很常见,例如 Redux 的 connect 和 Relay 的 createFragmentContainer

使用 HOC 解决横切关注点问题

注意

我们之前建议使用 mixins 用于解决横切关注点相关的问题。但我们已经意识到 mixins 会产生更多麻烦。阅读更多 以了解我们为什么要抛弃 mixins 以及如何转换现有组件。

组件是 React 中代码复用的基本单元。但你会发现某些模式并不适合传统组件

我们需要一个抽象,允许我们在一个地方定义这个逻辑,并在许多组件之间共享它。这正是高阶组件擅长的地方

请注意,HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。

约定:将不相关的 props 传递给被包裹的组件

约定:最大化可组合性

约定:包装显示名称以便轻松调试

不要改变原始组件。使用组合。

约定:将不相关的 props 传递给被包裹的组件

不要在 render 方法中使用 HOC

HOC 为组件添加特性。自身不应该大幅改变约定。HOC 返回的组件与原组件应保持类似的接口。

许多第三方库都提供了 compose 工具函数,包括 lodash (比如 lodash.flowRight), ReduxRamda

render() {
  // 每次调用 render 函数都会创建一个新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
  return <EnhancedComponent />;
}
这不仅仅是性能问题 - 重新挂载组件会导致该组件及其所有子组件的状态丢失。

务必复制静态方法

你需要知道哪些方法应该被拷贝。你可以使用 hoist-non-react-statics 自动拷贝所有非 React 静态方法:

import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}
高阶组件的实现

如果我们学过设计模式,同时用了HOC,我们很容易将后者与装饰者模式联系起来。我们通过组合的方式到达很高灵活度的装饰搭配,我们可以将这种思维带到接下来的HOC实现。

实现的高阶组件的方法有两种:

  • 属性代理。函数通过返回包裹原组件来添加额外功能。
  • 反向继承。函数通过返回继承原组件来控制render。
  1. 属性代理

属性代理是常见高阶组件的实现方式。

//我们来写一个最简单的
const MyContainer = (WrappedComponent) =>class extends Components{
	render(){
		const newProps={
			text:'newText',
		}
		return <WrappedComponent {...this.props} {...newProps} />
	}
}

就这么简单我们实现了对原组件props的添加,我们还可以在MyContainer内添加各种生命周期和自定义方法实现对renderreturn组件的各种控制。

2.反向继承

反向继承是通过class继承特性来实现高阶组件的一种方式,我们通过简单的代码来理解一下:

const MyContainer = (WrappedComponent) = > class extends WappedComponent {
	render (){
		return super.render();
	}
}

这种方法和属性代理不太一样,它通过继承WrappedComponent来实现,方法可以通过super来顺序调用。在继承方法中,高阶组件可以使用传入组件的引用,这意味着它可以使用传入组件的state、props、生命周期和render方法。

它有两个比较大的特点

  • 渲染劫持

渲染劫持就是指高阶组件可以控制传入组件的渲染过程,并渲染各种各样的结果。在这个过程中我们可以对输出的结果进行读取、增加、修改、删除props,或读取或修改React元素树,或条件显示元素树,又或是用样式控制包裹元素树

const MyContainer = (WrappedComponent) = > class extends WrappedComponent{
	render(){
		const elementsTree = super.render();
		let newProps={};
		if(elementsTree && elementsTree.type === 'input'){
			newProps = {value:'may the force be with you'};
		}
		const props =  Object.assign({},elementsTree.props,newProps);
		const newElmentsTree = React.cloneElement(elementsTree, props, elmentsTree.props.children);
		
	}
}
  • 控制state

高阶组件可以读取、修改或删除WrappedComponent实例中的state,如果有需要,也可以增加state。但这样做,会使组件的内部状态混乱。大部分高阶组件都应该限制读取或增加state,尤其是后者,可以通过重新命名state,以防止混淆。

与第三方库协同
集成带有 DOM 操作的插件

React 不会理会 React 自身之外的 DOM 操作。它根据内部虚拟 DOM 来决定是否需要更新,而且如果同一个 DOM 节点被另一个库操作了,React 会觉得困惑而且没有办法恢复。

避免冲突的最简单方式就是防止 React 组件更新。**你可以渲染无需更新的 React 元素,比如一个空的 <div />**

深入 JSX
编译语法糖
<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>
==》
React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)
第一位 名称 第二位 实现参数 第三位 子元素
在 JSX 类型中使用点语法

你也可以使用点语法来引用一个 React 组件。当你在一个模块中导出许多 React 组件时,这会非常方便。例如,如果 MyComponents.DatePicker 是一个组件

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" />;
}
用户定义的组件必须以大写字母开头
在运行时选择类型
import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 正确!JSX 类型可以是大写字母开头的变量。
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}
Props 默认值为 “True”

如果你没给 prop 赋值,它的默认值是 true

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />
属性展开

如果你已经有了一个 props 对象,你可以使用展开运算符 ... 来在 JSX 中传递整个 props 对象

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}
等价
function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}
typescript 剩余参数
JSX 中的子元素

包含在开始和结束标签之间的 JSX 表达式内容将作为特定属性 props.children 传递给外层组件

有几种不同的方法来传递子元素

  1. 字符串字面量

    Hello world!

  2. 子元素

    子元素允许由多个 JSX 元素组成。这对于嵌套组件非常有用

<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
</MyContainer>

<MyComponent>{'foo'}</MyComponent>
还有函数作为子元素
function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

false, null, undefined, and true 是合法的子元素。但它们并不会被渲染,以下的 JSX 表达式渲染结果相同:
<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>
Optimizing Performance 性能优化
  • 使用生产版本 product

    React 默认包含了许多有用的警告信息。这些警告信息在开发过程中非常有帮助。然而这使得 React 变得更大且更慢,所以你需要确保部署时使用了生产版本。

    几种为应用构建生产版本的操作说明

    • ​ 在生产模式下,Webpack v4+ 将默认对代码进行压缩

      const TerserPlugin = require('terser-webpack-plugin');
      
      module.exports = {
        mode: 'production'
        optimization: {
          minimizer: [new TerserPlugin({ /* additional options here */ })],
        },
      };
      
    • brunch 通过安装 terser-brunch 插件,来获得最高效的 Brunch 生产构建

    • browserify

    • rollup

  • Chrome Performance 标签分析组件

    这篇文章以获取更详尽的指导。

  • 使用开发者工具中的分析器对组件进行分析

    你可以在《介绍 React 分析器》这篇博客中了解概述。 你也可以在 YouTube 上观看分析器的视频指导

  • 虚拟化长列表

    如果你的应用渲染了长列表(上百甚至上千的数据),我们推荐使用“虚拟滚动”技术。这项技术会在有限的时间内仅渲染有限的内容,并奇迹般地降低重新渲染组件消耗的时间,以及创建 DOM 节点的数量。

    react-windowreact-virtualized 是热门的虚拟滚动库。 它们提供了多种可复用的组件,用于展示列表、网格和表格数据。 如果你想要一些针对你的应用做定制优化,你也可以创建你自己的虚拟滚动组件,就像 Twitter 所做的

  • 避免调停

    在大部分情况下它并不是问题,不过如果它已经慢到让人注意了,你可以通过覆盖生命周期方法 shouldComponentUpdate 来进行提速。该方法会在重新渲染前被触发。其默认实现总是返回 true,让 React 执行更新:

    shouldComponentUpdate(nextProps, nextState) {
      return true;
    }
    

    如果你知道在什么情况下你的组件不需要更新,你可以在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及之后的操作。

    在大部分情况下,你可以继承 React.PureComponent 以代替手写 shouldComponentUpdate()。它用当前与之前 props 和 state 的浅比较覆写了 shouldComponentUpdate() 的实现。

  • 不可变数据的力量

    ​ 字符连接 剩余参数

    
    handleClick() {
      this.setState(state => ({
        words: [...state.words, 'marklar'],
      }));
    };
    
    function updateColorMap(colormap) {
      return {...colormap, right: 'blue'};
    }
    
Portals

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

ReactDOM.createPortal(child, container)
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。
通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点

一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框

模态框示例https://codepen.io/gaearon/pen/jGBWpE	
Profiler API 分析器api

用于分析组件加载消耗的时间及组价做什么

在react 树中, profiler 可以添加在任何地方去分析测量树的某部分渲染的消耗,它要求两个参数 id 和 提交更新时的onRender 回调函数

A Profiler can be added anywhere in a React tree to measure the cost of rendering that part of the tree. It requires two props: an id (string) and an onRender callback (function) which React calls any time a component within the tree “commits” an update.

可以多个嵌套使用

render(
  <App>
    <Profiler id="Panel" onRender={callback}>
      <Panel {...props}>
        <Profiler id="Content" onRender={callback}>
          <Content {...props} />
        </Profiler>
        <Profiler id="PreviewPane" onRender={callback}>
          <PreviewPane {...props} />
        </Profiler>
      </Panel>
    </Profiler>
  </App>
);
不使用 ES6
  • 自动绑定

    对于使用 ES6 的 class 关键字创建的 React 组件,组件中的方法遵循与常规 ES6 class 相同的语法规则。这意味着这些方法不会自动绑定 this 到这个组件实例。 你需要在 constructor 中显式地调用 .bind(this)

    为了保险起见,以下三种做法都是可以的:

    • 在 constructor 中绑定方法。

    • 使用箭头函数,比如:onClick={(e) => this.handleClick(e)}

    • 继续使用 createReactClass

  • Mixins

    注意:**

    ES6 本身是不包含任何 mixin 支持。因此,当你在 React 中使用 ES6 class 时,将不支持 mixins 。

    我们也发现了很多使用 mixins 然后出现了问题的代码库。并且不建议在新代码中使用它们。

使用hoc 代替

Reconciliation
Diffing 算法

如果简单实现的话,那么在列表头部插入会很影响性能,那么更变开销会比较大。比如:

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>
React 会针对每个子元素 mutate 而不是保持相同的 <li>Duke</li> 和 <li>Villanova</li> 子树完成。这种情况下的低效可能会带来性能问题。


React 支持 key 属性。当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。以下例子在新增 key 之后使得之前的低效转换变得高效:

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>
现在 React 知道只有带着 '2014' key 的元素是新元素,带着 '2015' 以及 '2016' key 的元素仅仅移动了。

这就是为什在list遍历输出时必须要有key属性 ,由于算法性能优化的原因,这样更高效,这个 key 不需要全局唯一,但在列表中需要保持唯一。

Key 应该具有稳定,可预测,以及列表内唯一的特质。不稳定的 key(比如通过 Math.random() 生成的)会导致许多组件实例和 DOM 节点被不必要地重新创建,这可能导致性能下降和子组件中的状态丢失

Refs and the DOM

在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法

几个适合使用 refs 的情况:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

避免使用 refs 来做任何可以通过声明式实现来完成的事情。

勿过度使用 Refs

如果是这种情况,请花一点时间,认真再考虑一下 state 属性应该被安排在哪个组件层中。通常你会想明白,让更高的组件层级拥有这个 state,是更恰当的

ref 的值根据节点的类型而有所不同:

  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
  • ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
  • 你不能在函数组件上使用 ref 属性,因为他们没有实例。
Render Props
使用 Render Props 来解决横切关注点

render prop 是一个用于告知组件需要渲染什么内容的函数 prop。

重要的是要记住,render prop 是因为模式才被称为 render prop ,你不一定要用名为 render的 prop 来使用这种模式。事实上, 任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 “render prop”.

注意事项

将 Render Props 与 React.PureComponent 一起使用时要小心

如果你在 render 方法里创建函数,那么使用 render prop 会抵消使用 React.PureComponent 带来的优势。因为浅比较 props 的时候总会得到 false,并且在这种情况下每一个 render 对于 render prop 将会生成一个新的值。

静态类型检查

支持两种静态类型检查

  • typescript
  • flow

Flow 是一个针对 JavaScript 代码的静态类型检测器。Flow 由 Facebook 开发,经常与 React 一起使用。Flow 通过特殊的类型语法为变量,函数,以及 React 组件提供注解,帮助你尽早地发现错误

TypeScript 是一种由微软开发的编程语言。它是 JavaScript 的一个类型超集,包含独立的编译器。作为一种类型语言,TypeScript 可以在构建时发现 bug 和错误,这样程序运行时就可以避免此类错误

npm install --save-dev typescript
npx tsc --init 生成tsconfig.json 文件

tsconfig.json 文件中,有许多配置项用于配置编译器。查看所有配置项的的详细说明,请参考此文档

文件扩展名

在 React 中,你的组件文件大多数使用 .js 作为扩展名。在 TypeScript 中,提供两种文件扩展名:

.ts 是默认的文件扩展名,而 .tsx 是一个用于包含 JSX 代码的特殊扩展名。

严格模式

StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。

StrictMode 目前有助于:

检测意外的副作用

渲染阶段的生命周期包括以下 class 组件方法:

  • constructor
  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState 更新函数(第一个参数)

因为上述方法可能会被多次调用,所以不要在它们内部编写副作用相关的代码,这点非常重要。忽略此规则可能会导致各种问题的产生,包括内存泄漏和或出现无效的应用程序状态。不幸的是,这些问题很难被发现,因为它们通常具有非确定性。严格模式采用故意重复调用方法(如组件的构造函数)的方式,使得这种 bug 更容易被发现。

使用 PropTypes 进行类型检查

这个是默认的检查字段信息的校验器 可以使用flow或者typescript 进行替换

// 限制单个元素

import PropTypes from 'prop-types';

class MyComponent extends React.Component {
  render() {
    // 这必须只有一个元素,否则控制台会打印警告。
    const children = this.props.children;
    return (
      <div>
        {children}
      </div>
    );
  }
}

MyComponent.propTypes = {
  children: PropTypes.element.isRequired
};

API reference

ps://zh-hans.reactjs.org/docs/strict-mode.html#detecting-legacy-context-api)

检测意外的副作用

渲染阶段的生命周期包括以下 class 组件方法:

  • constructor
  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState 更新函数(第一个参数)

因为上述方法可能会被多次调用,所以不要在它们内部编写副作用相关的代码,这点非常重要。忽略此规则可能会导致各种问题的产生,包括内存泄漏和或出现无效的应用程序状态。不幸的是,这些问题很难被发现,因为它们通常具有非确定性。严格模式采用故意重复调用方法(如组件的构造函数)的方式,使得这种 bug 更容易被发现。

使用 PropTypes 进行类型检查

这个是默认的检查字段信息的校验器 可以使用flow或者typescript 进行替换

// 限制单个元素

import PropTypes from 'prop-types';

class MyComponent extends React.Component {
  render() {
    // 这必须只有一个元素,否则控制台会打印警告。
    const children = this.props.children;
    return (
      <div>
        {children}
      </div>
    );
  }
}

MyComponent.propTypes = {
  children: PropTypes.element.isRequired
};

API reference

React.Component
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值