react 官网「核心、高级」总结条目

802 阅读52分钟

前言

学习 React 官网,总结文档中重要要点内容。熟悉知识清单,整理为条目。先看了下 学习入门

很多时候我认为某些新鲜事物或成熟框架,知晓全面概念比深度掌握在项目业务开发过程中要重要得多。一旦涉及,则需进行官方文档或社区资源进行深入了解。

核心

JSX

  • 是一个 JavaScript 的语法扩展。
  • 变量包裹在大括号中:const element = <h1>Hello, {name}</h1>;
  • JSX 也是一个表达式:可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX
  • JSX 特定属性:对于同一属性不能同时使用这两种符号。
    • 可以使用引号:const element = <div tabIndex="0"></div>;
    • 可以使用大括号:const element = <img src={user.avatarUrl}></img>;
  • 可以使用 /> 来闭合标签。
  • 可以安全地在 JSX 当中插入用户输入内容:const element = <h1>{response.potentiallyMaliciousInput}</h1>;
  • Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

元素渲染

  • 将一个元素渲染为 DOM:ReactDOM.render(element, document.getElementById('root'));
    • 参数1 :JSX 语法扩展
    • 参数2 :插入指定位置
  • 更新已渲染的元素:创建一个全新的元素,并将其传入 ReactDOM.render()
  • React DOM 会将元素和它的子元素与它们之前的状态进行比较,只更新它需要更新的部分。

组件

  • 定义组件
    • 函数组件
    • class 组件
  • 将函数组件转换成 class 组件,通过以下五步将 Clock 的函数组件转成 class 组件:
    1. 创建一个同名的 ES6 class,并且继承于 React.Component
    2. 添加一个空的 render() 方法。
    3. 将函数体移动到 render() 方法之中。
    4. render() 方法中使用 this.props 替换 props
    5. 删除剩余的空函数声明。
  • 组件名称必须以大写字母开头。
  • 可以对组件进行有意义的组合和提取。

props

  • Props 是只读性:组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。
  • 所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

state

  • 正确的使用 state:setState() 三件事

    1. 不要直接修改 State

      // Wrong
      this.state.comment = 'Hello';
      
      // Correct
      this.setState({comment: 'Hello'});
      
      • class 中构造函数是唯一可以给 this.state 赋值的地方
    2. State 的更新可能是异步的:出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

      • this.propsthis.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
    3. State 的更新会被合并:当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。

  • state 数据是向下流动的:通常会被叫做“自上而下”或是“单向”的数据流。

事件处理

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。并且使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

  • React 中不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault

  • 在 JavaScript 中,class 的方法默认不会绑定 this。否则 this 的值为 undefined

  • 向事件处理程序传递参数

    • 在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。

条件渲染

  • 在 React 中,可以创建不同的组件来封装各种你需要的行为。依据应用的不同状态,可以只渲染对应状态下的部分内容。
  • 通过花括号包裹代码,你可以在 JSX 中嵌入任何表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染。
  • 内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false
  • 希望能隐藏组件,即使它已经被其他组件渲染。在组件的 render 方法中返回 null 并不会影响组件的生命周期。
    • 也就是组件中 componentDidUpdate 等生命周期钩子函数依旧会执行。

key

  • 一个独一无二的确定的标识
    • 当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key
    • 默认使用元素索引 index
  • 元素的 key 只有放在就近的数组上下文中才有意义。也就是父组件声明 key 而非子组件本身声明 key。
  • 数组元素中使用的 key 在其兄弟节点之间应该是独一无二的,它们不需要是全局唯一的。
  • 在 JSX 中直接嵌入 key

表单

  • 在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。
  • React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。而 value 只是只读,则为 React 中的一个非受控组件
    • <textarea value={this.state.value} onChange={this.handleChange} />
    • 也就是 value 绑定 state,再使用 onChange 来控制表单输入元素。
    • this.state.value 初始化于构造函数中,因此文本区域默认有初值。
  • 处理多个输入:可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。
  • 如果你指定了 value,但输入仍可编辑,很大概率是意外地将value 设置为 undefinednull
  • 成熟的解决方案: Formik下一目标学一学

状态提升

  • 当多个子组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。
  • 提升 state 方式比双向绑定方式需要编写更多的“样板”代码,但好处是排查和隔离 bug 所需的工作量将会变少。由于“存在”于组件中的任何 state,仅有组件自己能够修改它,因此 bug 的排查范围被大大缩减了。

组合

  • React 有十分强大的组合模式,推荐使用组合而非继承来实现组件间的代码重用。
  • 不同组件可以通过 JSX 嵌套,将任意组件作为子组件进行传递。
  • 组件可以接受任意 props,包括基本数据类型,React 元素以及函数。
  • 如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 JavaScript 模块,组件可以直接引入(import)而无需通过 extend 继承它们。
    • 如函数、对象或者类。

官方例子

官方构建一个可搜索的产品数据表格步骤。REACT

设计稿

image-20210628111952246.png

数据

[
  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];

步骤

  • 1.将设计好的 UI 划分为组件层级

    • 一个组件原则上只能负责一个功能。如果它需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件。

    • 将 UI 分离为组件,其中每个组件需与数据模型的某部分匹配。

    • image-20210628112103733.png

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

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

      • - FilterableProductTable
        -- SearchBar
        -- ProductTable
        --- ProductCategoryRow
        --- ProductRow
        
  • 2.用 React 创建一个静态版本

    • 先用已有的数据模型渲染一个不包含交互功能的 UI。最好将渲染 UI 和添加交互这两个过程分开。
      • 编写一个应用的静态版本时,往往要编写大量代码,而不需要考虑太多交互细节。
      • 加交互功能时则要考虑大量细节,而不需要编写太多代码。
    • 构建应用的静态版本时不会用到 state
    • 当你的应用比较简单时,使用自上而下的方式更方便;对于较为大型的项目来说,自下而上地构建,并同时为低层组件编写测试是更加简单的方式。
    • 构建的是静态版本,所以这些组件目前只需提供 render() 方法用于渲染。
      • 顶层的组件通过 props 接受你的数据模型。
      • 如果你的数据模型发生了改变,再次调用 ReactDOM.render(),UI 就会相应地被更新。
      • 数据模型变化、调用 render() 方法、UI 相应变化,因此很容易看清楚 UI 是如何被更新的,以及是在哪里被更新的。
      • React 单向数据流(也叫单向绑定)的思想使得组件模块化,易于快速开发。
  • 3.确定 UI state 的最小(且完整)表示

    • React 通过实现 state,实现触发基础数据模型改变的能力,使你的 UI 具备交互功能。

    • 只保留应用所需的可变 state 的最小集合,其他数据均由它们计算产生,如上述实例:

      1. 包含所有产品的原始列表

      2. 用户输入的搜索词

      3. 复选框是否选中的值

      4. 经过搜索筛选的产品列表

      • 通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:
        1. 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
        2. 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
        3. 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
      • 故综上,属于 state 的有:
        1. 用户输入的搜索词
        2. 复选框是否选中的值
  • 4.确定 state 放置的位置

    • 确定哪个组件能够改变这些 state,或者说拥有这些 state。
    • 可以尝试有以下判断
      1. 找到根据这个 state 进行渲染的所有组件。
      2. 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
      3. 该共同所有者组件或者比它层级更高的组件应该拥有该 state。
      4. 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置。
    • 故判断过后:
      • ProductTable 需要根据 state 筛选产品列表。SearchBar 需要展示搜索词和复选框的状态。
      • 他们的共同所有者是 FilterableProductTable
      • 因此,搜索词和复选框的值应该很自然地存放在 FilterableProductTable 组件中。
  • 5.添加反向数据流

    • 借助自上而下传递的 props 和 state 渲染了一个应用。现在,需尝试让数据反向传递:处于较低层级的表单组件更新较高层级的 FilterableProductTable 中的 state。

    • 也就是采用上述的状态提升方法。AA 包含 BB 子组件

      // AA:
      delete: function(){
          //改变AA下面其它组件
          //delete something
      },
      render: function(){
          return <BB delete={this.delete}/>
      }
          
      // BB:
      render: function(){
          return <div onClick={this.props.delete}/>
      }
      

梳理一下需要实现的功能

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

当你开始构建更大的组件库时,你会意识到这种代码模块化和清晰度的重要性。并且随着代码重用程度的加深,你的代码行数也会显著地减少。

高级

代码分割

  • 为了避免搞出大体积的代码包,在前期就思考该问题并对代码包进行分割是个不错的选择。

    • 因体积过大而导致加载时间过长。
    • 创建多个包并在运行时动态加载。
    • “懒加载”当前用户所需要的内容,能够显著地提高你的应用性能。
  • import()

    • // 使用前
      import { add } from './math';
      console.log(add(16, 26));
      
      // 使用后
      import("./math").then(math => {
        console.log(math.add(16, 26));
      });
      
    • 当使用 Babel 时,你要确保 Babel 能够解析动态 import 语法而不是将其进行转换。对于这一要求你需要 @babel/plugin-syntax-dynamic-import 插件。

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

    • // 使用前
      import OtherComponent from './OtherComponent';
      
      // 使用后
      const OtherComponent = React.lazy(() => import('./OtherComponent'));
      
    • Suspense 组件中渲染 lazy 组件,如此使得我们可以使用在等待加载 lazy 组件时做优雅降级(如 loading 指示器等)。

      • fallback 属性接受任何在组件加载过程中你想展示的 React 元素。
      • 用一个 Suspense 组件包裹多个懒加载组件。
  • 基于路由的代码分割

    • 在应用中使用 React.lazyReact Router 这类的第三方库,来配置基于路由的代码分割。
  • 命名导出(Named Exports)

    • React.lazy 目前只支持默认导出(default exports)。可以创建一个中间模块,来重新导出为默认模块。

Context

  • Context 提供了一种在组件之间共享值的方式,而不必显式地通过组件树的逐层传递 props。

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

  • 使用 Context 之前的考虑

  • Context 的 API

    • React.createContext:创建一个 Context 对象。
    • Context.Provider:每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
      • 通过新旧值检测来确定变化,使用了与 Object.is 相同的算法。
    • Class.contextType:挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。
      • 此属性能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。
    • Context.Consumer:一个 React 组件可以订阅 context 的变更,此组件可以让你在函数式组件中可以订阅 context。
    • Context.displayName:context 对象接受一个名为 displayName 的 property,类型为字符串。
      • React DevTools 使用该字符串来确定 context 要显示的内容。
  • 动态 Context:对 Context 值,可以进行动态更改。

  • 在嵌套组件中更新 Context:从一个在组件树中嵌套很深的组件中更新 context 是很有必要的。

    • 你可以通过 context 传递一个函数,使得 consumers 组件更新 context。
  • 消费多个 Context:

    • 为了确保 context 快速进行重渲染,React 需要使每一个 consumers 组件的 context 在组件树中成为一个单独的节点
  • 注意事项

    • 当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。
    • 当每一次 Provider 重渲染时,代码会重渲染所有下面的 consumers 组件,因为 value属性总是被赋值为新的对象。
    • 为了防止这种情况,将 value 状态提升到父节点的 state 里。

错误边界

组件内的 JavaScript 错误会导致 React 的内部状态被破坏,并且在下一次渲染时 产生 可能无法追踪的 错误

  • 部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。
  • 错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
  • 错误边界无法捕获以下场景中产生的错误:
    • 事件处理(了解更多
    • 异步代码(例如 setTimeoutrequestAnimationFrame 回调函数)
    • 服务端渲染
    • 它自身抛出来的错误(并非它的子组件)
  • 错误边界可以放置包装在最顶层的路由组件,也可以将单独的部件包装在错误边界以保护应用其他部分不崩溃。
  • 自 React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。
  • 在开发环境下,React 16 会把渲染期间发生的所有错误打印到控制台,即使该应用意外的将这些错误掩盖。除了错误信息和 JavaScript 栈外,React 16 还提供了组件栈追踪。
  • try/catch 区别在于:
    • try / catch 仅能用于命令式代码(imperative code)
    • React 组件是声明式的并且具体指出什么需要被渲染,错误边界保留了 React 的声明性质,其行为符合你的预期。
  • React 15 中有一个支持有限的错误边界方法 unstable_handleError
    • 此方法不再起作用,同时自 React 16 beta 发布起你需要在代码中将其修改为 componentDidCatch
    • 提供了一个 codemod 来帮助你自动迁移你的代码。

Refs 转发

  • Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。

  • 倾向于在整个应用中以一种类似常规 DOM buttoninput 的方式被使用,并且访问其 DOM 节点对管理焦点,选中或动画来说是不可避免的。

  • 官方例子步骤:

    • const FancyButton = React.forwardRef((props, ref) => (
        <button ref={ref} className="FancyButton">
          {props.children}
        </button>
      ));
      
      // 你可以直接获取 DOM button 的 ref:
      const ref = React.createRef();
      <FancyButton ref={ref}>Click me!</FancyButton>;
      
    1. 通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。

    2. 通过指定 ref 为 JSX 属性,将其向下传递给 <FancyButton ref={ref}>

    3. React 传递 refforwardRef 内函数 (props, ref) => ...,作为其第二个参数。

    4. 向下转发该 ref 参数到 <button ref={ref}>,将其指定为 JSX 属性。

    5. 当 ref 挂载完成,ref.current 将指向 <button> DOM 节点。

    • 第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。
  • React.forwardRef 接受一个渲染函数。React DevTools 使用该函数来决定为 ref 转发组件显示的内容。

    • 组件默认在 DevTools 中显示为 “ForwardRef
    • 如果你命名了渲染函数,DevTools 也将包含其名称(例如 “ForwardRef(myFunction)”)
    • 甚至可以设置函数的 displayName 属性来包含被包裹组件的名称

Fragments

Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。

  • 代替 <div> 作为一个根节点,但是为空。
  • 你可以使用一种新的,且更简短的语法来声明 Fragments。它看起来像空标签:<>...</>
    • 不支持 key 或属性。
  • 带 key 的 Fragments
    • 使用显式 <React.Fragment> 语法声明的片段可能具有 key。并且将一个集合映射到一个 Fragments 数组

高级组件

  • 自身不是 React API 的一部分,是一种基于 React 的组合特性而形成的设计模式。
  • 具体而言,高阶组件是参数为组件,返回值为新组件的函数。
    • 组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
  • 使用 HOC 解决横切关注点问题
  • HOC 不会修改传入的组件,也不会使用继承来复制其行为。
  • HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。
  • 不要试图在 HOC 中修改组件原型(或以其他方式改变它),而应该使用组合组件
    • 输入组件再也无法像 HOC 增强之前那样使用。
    • 再用另一个同样会修改 componentDidUpdate 的 HOC 增强它,那么前面的 HOC 就会失效!
    • 这个 HOC 也无法应用于没有生命周期的函数组件。
  • HOC 与容器组件模式
    • 容器组件担任将高级和低级关注点分离的责任,由容器管理订阅和状态,并将 prop 传递给处理 UI 的组件。
    • HOC 使用容器作为其实现的一部分,你可以将 HOC 视为参数化容器组件。
  • 将不相关的 props 传递给被包裹的组件
    • HOC 为组件添加特性。自身不应该大幅改变约定。HOC 返回的组件与原组件应保持类似的接口。
    • HOC 应该透传与自身无关的 props。
  • 最大化可组合性
    • 并不是所有的 HOC 都一样。有时候它仅接受一个参数,也就是被包裹的组件。
    • connect 是一个返回高阶组件的高阶函数。
      • connect 函数返回的单参数 HOC 具有签名 Component => Component。 输出类型与输入类型相同的函数很容易组合在一起。
  • 包装显示名称以便轻松调试:getDisplayName()
    • 最常见的方式是用 HOC 包住被包装组件的显示名称。比如高阶组件名为 withSubscription,并且被包装组件的显示名称为 CommentList,显示名称应该为 WithSubscription(CommentList)
  • 注意事项
    • 不要在 render 方法中使用 HOC:会严重影响 React 的 diff 算法来更新子树。
    • 务必复制静态方法:当你将 HOC 应用于组件时,原始组件将使用容器组件进行包装。这意味着新组件没有原始组件的任何静态方法。
    • Refs 不会被传递:如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。
      • ref 实际上并不是一个 prop。
      • key 一样,它是由 React 专门处理的。
      • 这个问题的解决方案是通过使用 React.forwardRef API(React 16.3 中引入)

与第三方库协同

React 可以被用于任何 web 应用中。它可以被嵌入到其他应用,且需要注意,其他的应用也可以被嵌入到 React。

  • 集成带有 DOM 操作的插件
    • 防止 React 组件更新。你可以渲染无需更新的 React 元素,比如一个空的 <div />
    • 我们会添加一个 ref 到这个根 DOM 元素。 在 componentDidMount 中,我们能够获取它的引用这样我们就可以把它传递给 jQuery 插件了。
    • 防止 React 在挂载之后去触碰这个 DOM,我们会从 render() 函数返回一个空的 <div />。使得 jQuery 插件可以自由的管理这部分的 DOM
  • 和其他视图库集成
    • 利用 ReactDOM.render() 的灵活性 React 可以被嵌入到其他的应用中。
      • ReactDOM.render() 可以在 UI 的独立部分上多次调用。
    • 利用 React 替换基于字符串的渲染
      • 使用一个字符串描述 DOM 块并且通过类似 $el.html(htmlString) 这样的方式插入到 DOM 中。
    • 把 React 嵌入到 Backbone 视图
      • 通过渲染一个 React 组件来替换掉,同样的,我们将会使用 ReactDOM.render()
  • 和 Model 层集成:React 组件也可以使用一个其他框架和库的 Model 层
    • 使用 Model
      • 监听多种变化事件并且手动强制触发一个更新:调用 this.forceUpdate() 来使用新的数据重新渲染组件。
    • 提取 Model 数据
      • 用高阶组件封装:每当 model 中的属性变化时都把它提取成简单数据,并且把这个逻辑放在一个独立的地方。

深入 JSX

  • 实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。

指定 React 元素类型

  • JSX 标签的第一部分指定了 React 元素的类型。
    • 大写字母开头的 JSX 标签意味着它们是 React 组件。
    • 这些标签会被编译为对命名变量的直接引用,所以,当你使用 JSX <Foo /> 表达式时,Foo 必须包含在作用域内。
  • 由于 JSX 会编译为 React.createElement 调用形式,所以 React 库也必须包含在 JSX 代码作用域内。也就是必须 import
  • 在 JSX 中,你也可以使用点语法来引用一个 React 组件。
    • 例如在高阶组件应用中,或是一个组件的对象中。
  • 用户定义的组件必须以大写字母开头,避免与 HTML 内置组件冲突。
  • 不能用通用表达式来动态来渲染不同组件。
    • 可以使用大写字母开头的变量作为 JSX 类型,来渲染不同组件。

JSX 中的 Props

  • 可以把包裹在 {} 中的 JavaScript 表达式作为一个 prop 传递给 JSX 元素。
  • if 语句以及 for 循环不是 JavaScript 表达式,所以不能在 JSX 中直接使用。
    • 但是,你可以用在 JSX 以外的代码中,用变量的形式使用。
  • 可以将字符串字面量赋值给 prop
    • 字符串字面量的值可以是未转义的
  • 如果你没给 prop 赋值,它的默认值是 true
  • 可以使用展开运算符 ... 来在 JSX 中传递整个 props 对象
    • 可以选择只保留当前组件需要接收的 props,并使用展开运算符将其他 props 传递下去。
    • 需要避免将不必要的 props 传递给不相关的组件,或者将无效的 HTML 属性传递给 DOM。

JSX 中的子元素

  • 可以将字符串放在开始和结束标签之间。
    • JSX 会移除行首尾的空格以及空行。
    • 与标签相邻的空行均会被删除。
    • 文本字符串之间的新行会被压缩为一个空格。
  • 子元素允许由多个 JSX 元素组成:这对于嵌套组件非常有用。
  • JavaScript 表达式可以被包裹在 {} 中作为子元素。
  • 可以把回调函数作为 props.children 进行传递
    • props.children 和其他 prop 一样,它可以传递任意类型的数据,而不仅仅是 React 已知的可渲染类型。
  • false, null, undefined, and true 是合法的子元素。但它们并不会被渲染。
    • 这有助于依据特定条件来渲染其他的 React 元素。
      • 有一些 “falsy” 值,如数字 0,仍然会被 React 渲染。
      • 要解决这个问题,确保 && 之前的表达式总是布尔值
    • 如果你想渲染 falsetruenullundefined 等值,你需要先将它们转换为字符串

性能优化

  • 开发应用时使用开发模式,而在为用户部署应用时使用生产模式。
    • 因为开发时默认包含了警告信息,在开发过程中非常有帮助。然而这使得 React 变得更大且更慢。
  • 通过安装 Chrome 的 React 开发者工具 来检查。如果你浏览一个基于 React 生产版本的网站,图标背景会变成深色,反之则为红色。
  • 打包整个 React App 项目,使用 npm run build
  • 可以在生产环境使用的单文件版 React 和 React DOM
    • 注意只有以 .production.min.js 为结尾的 React 文件适用于生产。
  • 通过安装 terser-brunch 插件,来获得最高效的 Brunch 生产构建。
    • build 命令后添加 -p 参数,以创建生产构建
    • 只需要在生产构建时使用,否则将性能更为低效。
  • 为了最高效的生产构建,需要安装一些插件(顺序很重要)
    • envify 转换器用于设置正确的环境变量。设置为全局 (-g)。
    • uglifyify 转换器移除开发相关的引用代码。同样设置为全局 (-g)。
    • 最后,将产物传给 terser 并进行压缩。
    • 只需要在生产构建时使用,否则将性能更为低效。
  • 为了最高效的 Rollup 生产构建,需要安装一些插件(顺序很重要)
    • replace 插件确保环境被正确设置。
    • commonjs 插件用于支持 CommonJS。
    • terser 插件用于压缩并生成最终的产物。
  • 当直接配置了 webpack 时,使用 'terser-webpack-plugin' 插件。
  • 使用开发者工具中的分析器对组件进行分析
  • 如果你的应用渲染了长列表(上百甚至上千的数据),推荐使用“虚拟滚动”技术。
  • 避免调停:React 避免创建 DOM 节点以及没有必要的节点访问,因为 DOM 操作相对于 JavaScript 对象操作更慢。因此采用了 “虚拟 DOM”,但还是需要花费大量效率。
    • 如果你知道在什么情况下你的组件不需要更新,你可以在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程。
      • 其包括该组件的 render 调用以及之后的操作。
      • 可以利用 propsstate 值来进行是否需调用 shouldComponentUpdate更新。
    • 可以继承 React.PureComponent 用当前与之前 props 和 state 的浅比较覆写了 shouldComponentUpdate() 的实现。
      • 只进行了浅比较,但数据结构很复杂时,便无法得到正确结果。
  • 避免更改正用于 propsstate 的值,也就是进行值的克隆拷贝
    • 可以利用 concat 重写
    • 支持扩展运算符
    • 使用 Object.assign 方法
    • 等等

Portals

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

    • ReactDOM.createPortal(child, container)
      
      • 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。
      • 第二个参数(container)是一个 DOM 元素。
  • 通过 Portal 进行事件冒泡:一个从 portal 内部触发的事件会一直冒泡至包含 React 树的祖先,即便这些元素并不是 DOM 树 中的祖先。

Profiler API

  • Profiler 测量渲染一个 React 应用多久渲染一次以及渲染一次的“代价”。 它的目的是识别出应用中渲染较慢的部分,或是可以使用类似 memoization 优化的部分。
  • Profiling 增加了额外的开支,所以它在生产构建中会被禁用
  • 用法,它需要两个 prop:
    • 一个是 id(string)
    • 一个是当组件树中的组件“提交”更新的时候被React调用的回调函数 onRender(function)。
  • onRender 回调参数:
    • id: string - 发生提交的 Profiler 树的 id。 如果有多个 profiler,它能用来分辨树的哪一部分发生了“提交”。
    • phase: "mount" | "update" - 判断是组件树的第一次装载引起的重渲染,还是由 props、state 或是 hooks 改变引起的重渲染。
    • actualDuration: number - 本次更新在渲染 Profiler 和它的子代上花费的时间。 这个数值表明使用 memoization 之后能表现得多好。。
    • baseDuration: number - 在 Profiler 树中最近一次每一个组件 render 的持续时间。 这个值估计了最差的渲染时间。
    • startTime: number - 本次更新中 React 开始渲染的时间戳。
    • commitTime: number - 本次更新中 React commit 阶段结束的时间戳。 在一次 commit 中这个值在所有的 profiler 之间是共享的,可以将它们按需分组。
    • interactions: Set - 当更新被制定时,“interactions” 的集合会被追踪。(例如当 render 或者 setState 被调用时)。
  • 对一个应用来说,每添加一些 Profiler 都会给 CPU 和内存带来一些负担。

Diffing 算法

  • 实现 React 的 “diffing” 算法过程中所作出的设计决策,以保证组件更新可预测,且在繁杂业务场景下依然保持应用的高性能。
  • 在某一时间节点调用 React 的 render() 方法,会创建一棵由 React 元素组成的树。在下一次 state 或 props 更新时,相同的 render() 方法会返回一棵不同的树。React 需要基于这两棵树之间的差别来判断如何高效的更新 UI,以保证当前 UI 与最新的树保持同步。
    • 即使使用最优的算法,该算法的复杂程度仍为 O(n 3 ),其中 n 是树中元素的数量。
    • 是 React 在以下两个假设的基础之上提出了一套 O(n) 的启发式算法:
      1. 两个不同类型的元素会产生出不同的树;
      2. 开发者可以通过设置 key 属性,来告知渲染哪些子元素在不同的渲染下可以保存不变;

算法

  • 当对比两棵树时,React 首先比较两棵树的根节点。不同类型的根节点元素会有不同的形态。

  • 对比不同类型的元素

    • 当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树。
    • 当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树。
  • 对比同一类型的元素

    • 当对比两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。
  • 对比同类型的组件元素

    • 当一个组件更新时,组件实例会保持不变,因此可以在不同的渲染时保持 state 一致。React 将更新该组件实例的 props 以保证与最新的元素保持一致,并且调用该实例的 UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate() 以及 componentDidUpdate() 方法。
    • 下一步,调用 render() 方法,diff 算法将在之前的结果以及新的结果中进行递归。
  • 对子节点进行递归

    • 默认情况下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;
    • 在子元素列表末尾新增元素时,更新开销比较小。
    • 简单的将新增元素插入到表头,那么更新开销会比较大,后续的元素将都需要比较。
  • Keys:当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素,来避免上述新增元素情况。

    • 这个 key 不需要全局唯一,但在列表中需要保持唯一。
  • 重新渲染表示在所有组件内调用 render 方法,这不代表 React 会卸载或装载它们。React 只会基于以上提到的规则来决定如何进行差异的合并。

  • 由于 React 依赖启发式算法,因此当以下假设没有得到满足,性能会大大损耗。

    1. 该算法不会尝试匹配不同组件类型的子树。如果你发现你在两种不同类型的组件中切换,但输出非常相似的内容,建议把它们改成同一类型。在实践中,我们没有遇到这类问题。
    2. Key 应该具有稳定,可预测,以及列表内唯一的特质。不稳定的 key(比如通过 Math.random() 生成的)会导致许多组件实例和 DOM 节点被不必要地重新创建,这可能导致性能下降和子组件中的状态丢失。

Refs and the DOM

  • Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

  • 下面是几个适合使用 refs 的情况:

    • 管理焦点,文本选择或媒体播放。
    • 触发强制动画。
    • 集成第三方 DOM 库。
  • 避免使用 refs 来做任何可以通过声明式实现来完成的事情,让更高的组件层级拥有这个 state,是更恰当的。如 状态提升

  • 创建 Refs:使用 React.createRef() 创建,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。

  • 访问 Refs:当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。

  • ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。

    • React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMountcomponentDidUpdate 生命周期钩子触发前更新。
  • ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。

  • 你不能在函数组件上使用 ref 属性,因为他们没有实例。

    • 如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle 结合使用),或者可以将该组件转化为 class 组件。
  • 在父组件中引用子节点的 DOM 节点。通常不建议这样做,因为它会打破组件的封装。

    • 如果你使用 16.3 或更高版本的 React, 这种情况下我们推荐使用 ref 转发Ref 转发使组件可以像暴露自己的 ref 一样暴露子组件的 ref
    • 如果你使用 16.2 或更低版本的 React,或者你需要比 ref 转发更高的灵活性,你可以使用这个替代方案将 ref 作为特殊名字的 prop 直接传递。
  • React 也支持另一种设置 refs 的方式,称为“回调 refs”。它能助你更精细地控制何时 refs 被设置和解除。

    • React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMountcomponentDidUpdate 触发前,React 会保证 refs 一定是最新的。
    • 可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。

Render props

  • 具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑。
    • 为组件提供一个函数 prop 来动态的确定要渲染什么
  • render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
  • 可以使用带有 render prop 的常规组件来实现大多数高阶组件 (HOC)。
  • props 的变量名可以自定义命名,而非 render
  • 将 Render Props 与 React.PureComponent 一起使用时要小心,每个渲染的 render prop 的值将会是不同的。
    • 可以定义一个 prop 作为实例方法,绕过这个问题

TypeScript

  • TypeScript 是一种由微软开发的编程语言。它是 JavaScript 的一个类型超集,包含独立的编译器。
  • 作为一种类型语言,TypeScript 可以在构建时发现 bug 和错误,这样程序运行时就可以避免此类错误。
  • 在项目中使用 TypeScript:
    • 将 TypeScript 添加到你的项目依赖中。
    • 配置 TypeScript 编译选项
      • tsconfig.json 文件中,有许多配置项用于配置编译器。查看所有配置项的的详细说明,请参考此文档
      • 通常情况下,不希望将编译后生成的 JavaScript 文件保留在版本控制内。因此,应该把构建文件夹添加到 .gitignore 中。
    • 使用正确的文件扩展名
      • .ts 是默认的文件扩展名,而 .tsx 是一个用于包含 JSX 代码的特殊扩展名。
    • 为你使用的库添加定义

严格模式

非受控组件

  • 在大多数情况下,我们推荐使用 受控组件 来处理表单数据。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理
  • 可以 使用 ref 来从 DOM 节点中获取表单数据。
  • 因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。
    • 如果表单在UI反馈方面非常简单,则使用refs不受控制完全是很好的。
    • 如果不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。
  • 在非受控组件中,指定一个 defaultValue 赋予组件一个初始值,但是不去控制后续的更新。
    • 同样,<input type="checkbox"><input type="radio"> 支持 defaultChecked<select><textarea> 支持 defaultValue
  • 在 React 中,<input type="file" /> 始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制,通过使用 File API 进行操作

API 参考

React 顶层 API

  • 使用 React.API
    • 单文件加载时,可以通过 React 全局变量对象来获得 React 的顶层 API。
    • 使用 ES6 与 npm 时,可以通过编写 import React from 'react'
    • 使用 ES5 与 npm 时,可以通过编写 var React = require('react')

组件

你可以通过子类 React.ComponentReact.PureComponent 来定义 React 组件。

  • React.Component:是使用 ES6 classes 方式定义 React 组件的基类

  • React.PureComponent:浅层对比 prop 和 state 的方式来实现shouldComponentUpdate()

    • 根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
    • 赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能。
    • React.PureComponent 中的 shouldComponentUpdate() 将跳过所有子组件树的 prop 更新。因此,请确保所有子组件也都是“纯”的组件(无复杂的数据结构)。
  • React 组件也可以被定义为可被包装的函数:React.memo

    • React.memo高阶组件
    • 组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。React 将跳过渲染组件的操作并直接复用最近一次渲染的结果
    • 默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
    • 此方法仅作为**性能优化**的方式而存在,不要依赖它来“阻止”渲染。

创建 React 元素

我们建议使用 JSX 来编写你的 UI 组件。每个 JSX 元素都是调用 React.createElement() 的语法糖。

操作元素

  • cloneElement():以 element 元素为样板克隆并返回新的 React 元素。

    • 返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。
    • 新的子元素将取代现有的子元素。
    • 原始元素的 keyref 将被保留。
    • 引入此 API 是为了替换已弃用的 React.addons.cloneWithProps()
  • isValidElement():验证对象是否为 React 元素,返回值为 truefalse

  • React.Children:提供了用于处理 this.props.children 不透明数据结构的实用方法。

    • React.Children.map

      • 如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。
      • 如果 children 是一个 Fragment 对象,它将被视为单一子节点的情况处理,而不会被遍历。
    • React.Children.forEach

    • React.Children.only

      • 验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。
      • 不接受数组
    • React.Children.toArray

      • children 这个复杂的数据结构以数组的方式扁平展开并返回,并为每个子节点分配一个 key。
        • toArray 会为返回数组中的每个 key 添加前缀,以使得每个元素 key 的范围都限定在此函数入参数组的对象内。
      • 当你想要在渲染函数中操作子节点的集合时。
      • 想要在向下传递 this.props.children 之前对内容重新排序或获取子集时。

Fragments

React 还提供了用于减少不必要嵌套的组件。

  • React.Fragment:能够在不额外创建 DOM 元素的情况下,让 render() 方法中返回多个元素。
    • 简写语法 <>...</>

Refs

Suspense

Suspense 使得组件可以“等待”某些操作结束后,再进行渲染。目前,Suspense 仅支持的使用场景是:通过 React.lazy 动态加载组件。它将在未来支持其它使用场景,如数据获取等。

  • React.lazy:允许你定义一个动态加载的组件。
    • 这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。代码分割文档
    • 渲染 lazy 组件依赖该组件渲染树上层的 <React.Suspense> 组件。
    • 使用 React.lazy 的动态引入特性需要 JS 环境支持 Promise。在 IE11 及以下版本的浏览器中需要通过引入 polyfill 来使用该特性。
  • React.Suspense:懒加载组件,可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。

最佳实践是将 <Suspense> 置于你想展示加载指示器(loading indicator)的位置

lazy() 则可被放置于任何你想要做代码分割的地方。

ReactDom

  • 使用 ReactDOM API

    • 使用 <script> 标签引入 React,所有的顶层 API 都能在全局 ReactDOM 上调用。
    • 使用 npm 和 ES6,你可以用 import ReactDOM from 'react-dom'
    • 使用 npm 和 ES5,你可以用 var ReactDOM = require('react-dom')
  • React 支持所有的现代浏览器,包括 IE9 及以上版本,但是需要为旧版浏览器比如 IE9 和 IE10 引入相关的 polyfills 依赖

  • ReactDOM.render(element, container[, callback])
    
    • 在提供的 container 里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回 null)。
      • 该回调函数将在组件被渲染或更新之后被执行。
    • 如果 React 元素之前已经在 container 里渲染过,这将会对其执行更新操作,并仅会在必要时改变 DOM 以映射最新的 React 元素。
      • ReactDOM.render() 目前会返回对根组件 ReactComponent 实例的引用。
  • ReactDOM.hydrate(element, container[, callback])
    
    • render() 相同,但它用于在 ReactDOMServer 渲染的容器中对 HTML 的内容进行 hydrate 操作。React 会尝试在已有标记上绑定事件监听器。并且 React 希望服务端与客户端渲染的内容完全一致。
    • 为元素添加 suppressHydrationWarning={true} 来消除服务端和客户端之间差异的警告,只在一级深度上有效。
    • 采用双重(two-pass)渲染实现在服务端与客户端渲染不同内容。
      • 在客户端渲染不同内容的组件可以读取类似于 this.state.isClient 的 state 变量,你可以在 componentDidMount() 里将它设置为 true
      • 在初始渲染过程中会与服务端渲染相同的内容,从而避免不匹配的情况出现,但在 hydration 操作之后,会同步进行额外的渲染操作。
      • 注意,因为进行了两次渲染,这种方式会使得组件渲染变慢,请小心使用。
  • ReactDOM.unmountComponentAtNode(container)
    
    • 从 DOM 中卸载组件,会将其事件处理器和 state 一并清除。
    • 如果指定容器上没有对应已挂载的组件,这个函数什么也不会做。
    • 如果组件被移除将会返回 true,如果没有组件可被移除将会返回 false
  • ReactDOM.createPortal(child, container)
    
    • 创建 portal。Portal 将提供一种将子节点渲染到 DOM 节点中的方式,该节点存在于 DOM 组件的层次结构之外。

Readctdomserver

  • ReactDOMServer 对象允许你将组件渲染成静态标记。通常,它被使用在 Node服务端上
    • 使用 npm 和 ES6,你可以用 import ReactDOMServer from 'react-dom/server';
    • 使用 npm 和 ES5,你可以用 var ReactDOMServer = require('react-dom/server');
  • 下述方法可以被使用在服务端和浏览器环境。
    • renderToString():将 React 元素渲染为初始 HTML,返回一个 HTML 字符串。
      • 你可以使用此方法在服务端生成 HTML,并在首次请求时将标记下发,以加快页面加载速度,并允许搜索引擎爬取你的页面以达到 SEO 优化的目的。
      • 在已有服务端渲染标记的节点上调用 ReactDOM.hydrate() 方法,React 将会保留该节点且只进行事件处理绑定,从而让你有一个非常高性能的首次加载体验。
    • renderToStaticMarkup():此方法与 renderToString 相似,但此方法不会在 React 内部创建的额外 DOM 属性。
      • 用于把 React 当作静态页面生成器来使用
      • 若在前端使用 React 以使得标记可交互,请不要使用此方法。
  • 下述附加方法依赖一个只能在服务端使用的 package(stream)。它们在浏览器中不起作用。
    • renderToNodeStream():将一个 React 元素渲染成其初始 HTML,返回一个可输出 HTML 字符串的可读流
      • 通过可读流输出的 HTML 完全等同于 ReactDOMServer.renderToString 返回的 HTML。
      • 你可以使用本方法在服务器上生成 HTML,并在初始请求时将标记下发,以加快页面加载速度,并允许搜索引擎抓取你的页面以达到 SEO 优化的目的。
    • renderToStaticNodeStream():此方法与 renderToNodeStream 相似,但此方法不会在 React 内部创建的额外 DOM 属性。
    • 本方法返回的流会返回一个由 utf-8 编码的字节流。如果你需要另一种编码的流,请查看像 iconv-lite 这样的项目,它为转换文本提供了转换流。

DOM元素

  • React 实现了一套独立于浏览器的 DOM 系统,兼顾了性能和跨浏览器的兼容性。并完善了浏览器 DOM 实现的一些特殊情况。
  • 所有的 DOM 特性和属性(包括事件处理)都应该是小驼峰命名的方式。
    • 例外的情况是 aria-* 以及 data-* 属性,一律使用小写字母命名。比如, 你依然可以用 aria-label 作为 aria-label

属性差异

  • checked:当 <input> 组件的 type 类型为 checkboxradio 时,组件支持 checked 属性。

    • 用来设置构建受控组件是否被选中
    • defaultChecked 则是非受控组件的属性,用于设置组件首次挂载时是否被选中。
  • className:用于指定 CSS 的 class,此特性适用于所有常规 DOM 节点和 SVG 元素。

    • 如果你在 React 中使用 Web Components,请使用 class 属性代替。
  • dangerouslySetInnerHTML:是 React 为浏览器 DOM 提供 innerHTML 的替换方案。

    • 使用代码直接设置 HTML 存在风险,因为很容易无意中使用户暴露于跨站脚本(XSS)的攻击。
    • 可以直接在 React 中设置 HTML,但当你想设置 dangerouslySetInnerHTML 时,需要向其传递包含 key 为 __html 的对象。
  • htmlFor:由于 for 在 JavaScript 中是保留字,所以 React 元素中使用了 htmlFor 来代替。

  • onChange:预期行为一致,每当表单字段变化时,该事件都会被触发。

    • onChange 在浏览器中的行为和名称不对应,并且 React 依靠了该事件实时处理用户输入。故没有使用浏览器已有的默认行为。
  • selected:在 selectvalue 中引用 selected<option> 标记为已选中状态。

  • style:接受一个采用小驼峰命名属性的 JavaScript 对象,而不是 CSS 字符串

    • 这与 DOM 中 style 的 JavaScript 属性是一致的,同时会更高效的,且能预防跨站脚本(XSS)的安全漏洞。
    • 通常不推荐将 style 属性作为设置元素样式的主要方式,应使用 className 属性来引用外部 CSS 样式表中定义的 class。
    • style 在 React 应用中多用于在渲染过程中添加动态计算的样式。
    • 浏览器引擎前缀都应以大写字母开头,除了 ms。因此,WebkitTransition 首字母为 ”W”。
    • React 会自动添加 ”px” 后缀到内联样式为数字的属性后。使用其他单位则需使用组合字符串。
  • suppressContentEditableWarning:当拥有子节点的元素被标记为 contentEditable 时,React 会发出一个警告,因为这不会生效。该属性将禁止此警告。

    • 尽量不要使用该属性。
  • suppressHydrationWarning:使用 React 服务端渲染,通常会在当服务端与客户端渲染不同的内容时发出警告。设置 suppressHydrationWarningtrue,React 将不会警告你属性与元素内容不一致。

  • 只会对元素一级深度有效,并且打算作为应急方案。

  • value<input><select><textarea> 组件支持 value 属性。你可以使用它为组件设置 value。这对于构建受控组件是非常有帮助。

    • defaultValue 属性对应的是非受控组件的属性,用于设置组件第一次挂载时的 value。

所有支持的HTML属性

  • 在 React 16 中,任何标准的或自定义的 DOM 属性都是完全支持的。

  • 因以 JavaScript 为中心的 API,React 采用了小驼峰命名的方式。

  • 支持的 DOM 属性(以首字母顺序)有:

      accept | acceptCharset | accessKey | action | allowFullScreen | alt | async | autoComplete
      autoFocus | autoPlay | capture | cellPadding | cellSpacing | challenge | charSet | checked
      cite | classID | className | colSpan | cols | content | contentEditable | contextMenu | controls
      controlsList | coords | crossOrigin | data | dateTime | default | defer | dir | disabled
      download | draggable | encType | form | formAction | formEncType | formMethod | formNoValidate
      formTarget | frameBorder | headers | height | hidden | high | href | hrefLang | htmlFor
      httpEquiv | icon | id | inputMode | integrity | is | keyParams | keyType | kind | label | lang | list
      loop | low | manifest | marginHeight | marginWidth | max | maxLength | media | mediaGroup | method
      min | minLength | multiple | muted | name | noValidate | nonce | open | optimum | pattern
      placeholder | poster | preload | profile | radioGroup | readOnly | rel | required | reversed
      role | rowSpan | rows | sandbox | scope | scoped | scrolling | seamless | selected | shape | size
      sizes | span | spellCheck | src | srcDoc | srcLang | srcSet | start | step | style | summary
      tabIndex | target | title | type | useMap | value | width | wmode | wrap
    
  • 所有的 SVG 属性也完全得到了支持(以首字母顺序):

      accentHeight | accumulate | additive | alignmentBaseline | allowReorder | alphabetic
      amplitude | arabicForm | ascent | attributeName | attributeType | autoReverse | azimuth
      baseFrequency | baseProfile | baselineShift | bbox | begin | bias | by | calcMode | capHeight
      clip | clipPath | clipPathUnits | clipRule | colorInterpolation
      colorInterpolationFilters | colorProfile | colorRendering | contentScriptType
      contentStyleType | cursor | cx | cy | d | decelerate | descent | diffuseConstant | direction
      display | divisor | dominantBaseline | dur | dx | dy | edgeMode | elevation | enableBackground
      end | exponent | externalResourcesRequired | fill | fillOpacity | fillRule | filter
      filterRes | filterUnits | floodColor | floodOpacity | focusable | fontFamily | fontSize
      fontSizeAdjust | fontStretch | fontStyle | fontVariant | fontWeight | format | from | fx | fy
      g1 | g2 | glyphName | glyphOrientationHorizontal | glyphOrientationVertical | glyphRef
      gradientTransform | gradientUnits | hanging | horizAdvX | horizOriginX | ideographic
      imageRendering | in | in2 | intercept | k | k1 | k2 | k3 | k4 | kernelMatrix | kernelUnitLength
      kerning | keyPoints | keySplines | keyTimes | lengthAdjust | letterSpacing | lightingColor
      limitingConeAngle | local | markerEnd | markerHeight | markerMid | markerStart
      markerUnits | markerWidth | mask | maskContentUnits | maskUnits | mathematical | mode
      numOctaves | offset | opacity | operator | order | orient | orientation | origin | overflow
      overlinePosition | overlineThickness | paintOrder | panose1 | pathLength
      patternContentUnits | patternTransform | patternUnits | pointerEvents | points
      pointsAtX | pointsAtY | pointsAtZ | preserveAlpha | preserveAspectRatio | primitiveUnits
      r | radius | refX | refY | renderingIntent | repeatCount | repeatDur | requiredExtensions
      requiredFeatures | restart | result | rotate | rx | ry | scale | seed | shapeRendering | slope
      spacing | specularConstant | specularExponent | speed | spreadMethod | startOffset
      stdDeviation | stemh | stemv | stitchTiles | stopColor | stopOpacity
      strikethroughPosition | strikethroughThickness | string | stroke | strokeDasharray
      strokeDashoffset | strokeLinecap | strokeLinejoin | strokeMiterlimit | strokeOpacity
      strokeWidth | surfaceScale | systemLanguage | tableValues | targetX | targetY | textAnchor
      textDecoration | textLength | textRendering | to | transform | u1 | u2 | underlinePosition
      underlineThickness | unicode | unicodeBidi | unicodeRange | unitsPerEm | vAlphabetic
      vHanging | vIdeographic | vMathematical | values | vectorEffect | version | vertAdvY
      vertOriginX | vertOriginY | viewBox | viewTarget | visibility | widths | wordSpacing
      writingMode | x | x1 | x2 | xChannelSelector | xHeight | xlinkActuate | xlinkArcrole
      xlinkHref | xlinkRole | xlinkShow | xlinkTitle | xlinkType | xmlns | xmlnsXlink | xmlBase
      xmlLang | xmlSpace | y | y1 | y2 | yChannelSelector | z | zoomAndPan
    
  • 可以使用自定义属性,但要注意属性名全都为小写。

合成事件

  • 是浏览器的原生事件的跨浏览器包装器,除兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口。
  • 当你需要使用浏览器的底层事件时,只需要使用 nativeEvent 属性来获取即可。
    • 合成事件与浏览器的原生事件不同,也不会直接映射到原生事件。
    • 例如,在 onMouseLeave 事件中 event.nativeEvent 将指向 mouseout 事件。
  • 每个 SyntheticEvent 对象都包含以下属性:
boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
void persist()
DOMEventTarget target
number timeStamp
string type
  • 从 v17 开始,e.persist() 将不再生效,因为 SyntheticEvent 不再放入事件池中。
  • 从 v0.14 开始,事件处理器返回 false 时,不再阻止事件传递。
    • 调用 e.stopPropagation()e.preventDefault() 作为替代方案。

事件

  • 以下的事件处理函数在冒泡阶段被触发。
    • 如需注册捕获阶段的事件处理函数,则应为事件名添加 Capture
    • 例如,处理捕获阶段的点击事件请使用 onClickCapture,而不是 onClick

剪贴板事件

事件名:

onCopy onCut onPaste

属性:

DOMDataTransfer clipboardData

复合事件

事件名:

onCompositionEnd onCompositionStart onCompositionUpdate

属性:

string data

键盘事件

事件名:

onKeyDown onKeyPress onKeyUp

属性:

boolean altKey
number charCode
boolean ctrlKey
boolean getModifierState(key)
string key
number keyCode
string locale
number location
boolean metaKey
boolean repeat
boolean shiftKey
number which

焦点事件

事件名:

onFocus onBlur
  • 这些焦点事件在 React DOM 上的所有元素都有效,不只是表单元素。

属性:

DOMEventTarget relatedTarget

onFocus

  • onFocus 事件在元素(或其内部某些元素)聚焦时被调用。

onBlur

  • onBlur 事件处理程序在元素(或元素内某些元素)失去焦点时被调用。

监听焦点的进入与离开

  • 你可以使用 currentTargetrelatedTarget 来区分聚焦和失去焦点是否来自父元素外部

表单事件

事件名:

onChange onInput onInvalid onReset onSubmit 
  • 想了解 onChange 事件的更多信息,查看 Forms

通用事件

事件名:

onError onLoad

鼠标事件

事件名:

onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit
onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave
onMouseMove onMouseOut onMouseOver onMouseUp
  • onMouseEnteronMouseLeave 事件从离开的元素向进入的元素传播,不是正常的冒泡,也没有捕获阶段。

属性:

boolean altKey
number button
number buttons
number clientX
number clientY
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
number pageX
number pageY
DOMEventTarget relatedTarget
number screenX
number screenY
boolean shiftKey

指针事件

事件名:

onPointerDown onPointerMove onPointerUp onPointerCancel onGotPointerCapture
onLostPointerCapture onPointerEnter onPointerLeave onPointerOver onPointerOut
  • onPointerEnteronPointerLeave 事件从离开的元素向进入的元素传播,不是正常的冒泡,也没有捕获阶段。

属性:

number pointerId
number width
number height
number pressure
number tangentialPressure
number tiltX
number tiltY
number twist
string pointerType
boolean isPrimary
  • 并非每个浏览器都支持指针事件,如果你的应用要求指针事件,我们推荐添加第三方的指针事件 polyfill。

选择事件

事件名:

onSelect

触摸事件

事件名:

onTouchCancel onTouchEnd onTouchMove onTouchStart

属性:

boolean altKey
DOMTouchList changedTouches
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
boolean shiftKey
DOMTouchList targetTouches
DOMTouchList touches

UI 事件

事件名:

onScroll
  • 从 React 17 开始,onScroll 事件在 React 中不再冒泡。这与浏览器的行为一致,并且避免了当一个嵌套且可滚动的元素在其父元素触发事件时造成混乱。

属性:

number detail
DOMAbstractView view

滚轮事件

事件名:

onWheel

属性:

number deltaMode
number deltaX
number deltaY
number deltaZ

媒体事件

事件名:

onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted
onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay
onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend
onTimeUpdate onVolumeChange onWaiting

图像事件

事件名:

onLoad onError

动画事件

事件名:

onAnimationStart onAnimationEnd onAnimationIteration

属性:

string animationName
string pseudoElement
float elapsedTime

过渡事件

事件名:

onTransitionEnd

属性:

string propertyName
string pseudoElement
float elapsedTime

其他事件

事件名:

onToggle

Javascript 环境要求

  • React 16 依赖集合类型 MapSet
  • 支持无法原生提供这些能力(例如 IE < 11)或实现不规范(例如 IE 11)的旧浏览器与设备,考虑在你的应用库中包含一个全局的 polyfill ,例如 core-js
  • React 同时还依赖于 requestAnimationFrame(甚至包括测试环境)。
    • 可以使用 raf 的 package 增添 requestAnimationFrame 的 shim:

React 术语词汇表

单页面应用

  • 单页面应用是一个应用程序,它可以加载单个 HTML 页面,以及运行应用程序所需的所有必要资源(例如 JavaScript 和 CSS)。
  • 与页面或后续页面的任何交互,都不再需要往返 server 加载资源,即页面不会重新加载。

ES6, ES2015, ES2016 等

  • 这些首字母缩写都是指 ECMAScript 语言规范标准的最新版本。
  • 其中 ES6 版本(也称为 ES2015)包括对前面版本的许多补充。
  • 可以在这里了解此规范特定版本的详细信息。

Compiler(编译器)

  • JavaScript compiler 接收 JavaScript 代码,然后对其进行转换,最终返回不同格式的 JavaScript 代码。
  • 最为常见的使用示例是,接收 ES6 语法,然后将其转换为旧版本浏览器能够解释执行的语法。Babel 是 React 最常用的 compiler。

Bundler(打包工具)

  • bundler 会接收写成单独模块(通常有数百个)的 JavaScript 和 CSS 代码,然后将它们组合在一起,最终生成出一些为浏览器优化的文件。
  • 常用的打包 React 应用的工具有 webpackBrowserify

Package 管理工具

  • package 管理工具,是帮助你管理项目依赖的工具。
  • npmYarn 是两个常用的管理 React 应用依赖的 package 管理工具。

CDN

  • CDN 代表内容分发网络(Content Delivery Network)。CDN 会通过一个遍布全球的服务器网络来分发缓存的静态内容。

JSX

  • JSX 是一个 JavaScript 语法扩展。它类似于模板语言,但它具有 JavaScript 的全部能力。
  • JSX 最终会被编译为 React.createElement() 函数调用,返回称为 “React 元素” 的普通 JavaScript 对象。

元素

  • React 元素是构成 React 应用的基础砖块。
  • 人们可能会把元素与广为人知的“组件”概念相互混淆。
    • 元素描述了你在屏幕上想看到的内容。
    • React 元素是不可变对象。

组件

  • React 组件是可复用的小的代码片段,它们返回要在页面中渲染的 React 元素。

  • React 组件的最简版本是,一个返回 React 元素的普通 JavaScript 函数。

  • 组件也可以使用 ES6 的 class 编写。

  • 组件名称应该始终以大写字母开头(<Wrapper/> 而不是 <wrapper/>)。

props

  • props 是 React 组件的输入。它们是从父组件向下传递给子组件的数据。

  • props 是只读的。不应以任何方式修改它们

  • 若想要响应式数据,请使用 state 来作为替代。

props.children

  • 每个组件都可以获取到 props.children。它包含组件的开始标签和结束标签之间的内容。

  • 对于 class 组件,使用 this.props.children

state

  • 使用 state 来跟踪组件中的状态。

  • 不要试图同步来自于两个不同组件的 state。

    • 应将其提升到最近的共同祖先组件中,并将这个 state 作为 props 传递到两个子组件。
  • stateprops 之间最重要的区别是:

    • props 由父组件传入,而 state 由组件本身管理。
    • 组件不能修改 props,但它可以修改 state

生命周期方法

受控组件 vs 非受控组件

  • React 有两种不同的方式来处理表单输入:

    • input 表单元素的值是由 React 控制,就其称为受控组件
    • 一个非受控组件,就像是运行在 React 体系之外的表单元素。
  • 在大多数情况下,你应该使用受控组件。

key

  • “key” 是在创建元素数组时,需要用到的一个特殊字符串属性。

  • key 帮助 React 识别出被修改、添加或删除的 item。应当给数组内的每个元素都设定 key,以使元素具有固定身份标识。

  • 只需要保证,在同一个数组中的兄弟元素之间的 key 是唯一的。而不需要在整个应用程序甚至单个组件中保持唯一。

Ref

  • React 支持一个特殊的、可以附加到任何组件上的 ref 属性。

    • React.createRef() 函数创建的对象
    • 一个回调函数
      • ref 属性是一个回调函数时,此函数会(根据元素的类型)接收底层 DOM 元素或 class 实例作为其参数。
      • 这能够让你直接访问 DOM 元素或组件实例。
    • 一个字符串(遗留 API)
  • 谨慎使用 ref。如果你发现自己经常使用 ref 来在应用中“实现想要的功能”,你可以考虑去了解一下自上而下的数据流

事件

  • React 事件处理器使用 camelCase(驼峰式命名)而不使用小写命名。
  • 通过 JSX,你可以直接传入一个函数,而不是传入一个字符串,来作为事件处理器。

协调

  • 当组件的 props 或 state 发生变化时,React 通过将最新返回的元素与原先渲染的元素进行比较,来决定是否有必要进行一次实际的 DOM 更新。当它们不相等时,React 才会更新 DOM。这个过程被称为“协调”。

后言

因字数限制下文为 react 官网「Hooks」总结条目