react学习笔记(下)

这里有一份简洁的前端知识体系等待你查收,看看吧,会有惊喜哦~若是以为不错,麻烦star哈~前端


设计维度


目标

为了解决用户的问题,技术自己要达成什么目标。这层定义“作到什么”。react


命令式编程VS声明式编程

命令式编程

命令“机器”如何去作事情(how),这样无论你想要的是什么(what),它都会按照你的命令实现。git

声明式编程

告诉“机器”你想要的是什么(what),让机器想出如何去作(how)。github

在React中,每一个组件经过render函数返回“这个组件应该长得什么样”,而不去描述“怎么样去让这个组件长成这个样子”。算法

声明式编程的好处

  • 让开发者的工做简化了
  • 减小了重复工做
  • 留下了改进的空间:好比React Fiber,虽然算法改头换面,可是组件却几乎不用改,由于组件只操心“显示什么”而不操心“如何显示”啊,固然不受影响了。
  • 提供了全局协调能力:在React的将来,每一个组件仍是只声明“想要画成什么样子”,但React却能够改进协调算法,让React组件根据不一样优先级来渲染,提升用户感知性能,可是React组件的代码不须要改变

react的核心理念之一就是函数式编程。编程


JSX

在JS中写HTML标记,这体现了高内聚。要达到这种效果,就必须依赖JSX。redux

JSX 的本质不是模板引擎,而是动态建立组件的语法糖,它容许咱们在JS代码中直接写HTML标记。最终生成的代码就是React.CreateElement。后端

若是在 JSX 中往 DOM 元素中传入自定义属性,React 是不会渲染的。若是要使用 HTML 自定义属性,要使用 data- 前缀,这与 HTML 标准也是一致的。然而,在自定义标签中任意的属性都是被支持的,以 aria- 开头的网络无障碍属性一样能够正常使用。设计模式

JSX的优势数组

  1. 直观:声明式建立界面
  2. 灵活:代码动态建立界面
  3. 易上手:无需学习新的模板语言

约定

  1. 自定义组件以大写字母开头
  2. react 认为小写的 tag 是原生 DOM 节点,如 div
  3. JSX标记能够直接使用属性语法,例如<menu.Item />

实现原理

为了达到设计目标,该技术采用了什么原理和机制。实现原理层回答“怎么作到”的问题。把实现原理弄懂,而且讲清楚,是技术人员的基本功。


生命周期函数

生命周期函数指的是在某一个时刻组件会自动调用执行的函数。



也能够参考网上的这张图

一些注意点:

无论是挂载阶段仍是更新阶段,都要到render时才能获取到更新后的this.state。在componentWillMount、 componentWillReceiveProps、 shouldComponentUpdate 和 componentWillUpdate 中也仍是没法获取到更新后的 this.state。

mountComponent 本质上是经过递归渲染内容的,因为递归的特性,父组件的 componentWillMount 在其子组件的 componentWillMount 以前调用,而父组件的 componentDidMount 在其子组件的 componentDidMount 以后调用。updateComponent同理。

updateComponent 负责管理生命周期中的 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。在 componentWillReceiveProps 中调用 setState,是不会触发 re-render 的,而是会进行 state 合并。禁止在 shouldComponentUpdate 和 componentWillUpdate 中调用 setState,这会形成循环调用,直至耗光浏览器内存后崩溃。

在 componentWillUnmount 中调用 setState,是不会触发 re-render 的。

无状态组件只是一个 render 方法,并无组件类的实例化过程,也没有实例返回。无状态组件没有状态,没有生命周期,只是简单地接受 props 渲染生成 DOM 结构,是一个纯粹为渲染而生的组件。


这里简单介绍下各个生命周期函数:

constructor

  1. 用于初始化内部状态,不多使用
  2. 惟一能够直接修改 state 的地方

getDerivedStateFromProps

  1. 当 state 须要从 props 初始化时使用
  2. 尽可能不要使用:维护二者状态一致性会增长复杂度
  3. 每次 render 都会调用
  4. 典型场景:表单控件获取默认值

componentDidMount

  1. UI 渲染完成后调用
  2. 只执行一次
  3. 典型场景:获取外部资源

componentWillUnmount

  1. 组件移除时被调用
  2. 典型场景:资源释放

getSnapshotBeforeUpdate

  1. 在元素被渲染并写入 DOM 以前调用,这样,你在 DOM 更新前捕获 DOM 信息(例如:滚动位置)。
  2. 在页面 render t前调用,state 已更新
  3. 典型场景:获取 render 以前的 DOM 状态

componentDididUpdate

  1. 每次 UI 更新时被调用
  2. 典型场景:页面须要根据 props 变化从新获取数据

shouldComponentUpdate

  1. 决定 VDOM 是否要重绘
  2. 通常能够由 PureComponent 自动实现
  3. 典型场景:性能优化

componentWillReceiveProps

注意下update阶段,触发组件update有两种状况,props或者state的修改。

能够看到,这两种状况生命周期函数是有重合的。惟一的不一样就是props改变时,会先调用 componentWillReceiveProps

  1. 一个组件要从父组件接受参数
  2. 若是这个组件第一次存在于父组件中,不会执行
  3. 若是这个组件以前已经存在于父组件中,才会执行

MV* 与 Flux

MVC/MVVM

MVC/MVVM 简称 MC* 模式,其中 MVVM 是从 MVC 演进而来的。

MVC 是一种架构设计模式,它经过关注数据界面分离,来鼓励改进应用程序结构。具体地 说,MVC 强制将业务数据(Model)与用户界面(View)隔离,用控制器(Controller)管理逻 辑和用户输入。

Model 负责保存应用数据,和后端交互同步应用数据,或校验数据。

View 是 Model 的可视化表示,表示当前状态的视图。

Controller负责链接 View 和 Model,Model 的任何改变会应用到 View 中,View 的操做会经过 Controller应用到 Model 中。

Controller 管理了应用程序中 Model 和 View 之间的逻辑和协调。

MVC 的致命缺点:混乱的数据流动方式,此外,前端 MVC 模式的实现各有各的理解,千奇百怪。



MVVM 出现于 2005 年,最大变化在于 VM(ViewModel)代替了 C(Controller)。其关键“改 进”是数据绑定(DataBinding),也就是说,View 的数据状态发生变化能够直接影响 VM,反之 亦然。



Flux 的解决方案

Flux 的核心思想就是数据和逻辑永远单向流动



Flux 核心思想,也就是中心化控制。中心化控制让全部的请求与改变都只能经过 action 发出,统一 由 dispatcher 来分配。这样View就能够保持高度简洁,发生问题时也便于定位。比起 MVC 架构下数据或逻 辑的改动可能来自多个彻底不一样的源头,Flux 架构追查问题的复杂度和困难度显然要小得多。

Flux 的不足:冗余代码过多,每一个应用中都须要手动建立一个 dispatcher 的示例,这仍是让不少开发者以为烦恼

若是非要把Flux 和MVC 作一个结构对比,那么, Flux 的Dispatcher 至关于MVC 的Controller, Flux 的Store 至关于MVC 的Model, Flux 的View 固然就对应MVC 的View了,至于多出来的这个Action ,能够理解为对应给MVC 框架的用户请求

Redux

Redux 是一个可预测的状态容器。简单地说,在摒弃了传统 MVC 的发布/订阅模式并经过 Redux 三大原则强化对状态 的修改后,使用 Redux 可让你的应用状态管理变得可预测、可追溯。

redux的相关知识繁多,还包含了Mobx、dva,为此我将他抽离出来,请看这里


render的执行

  1. 当组件的state和props发生改变时,render函数就会从新执行
  2. 父组件render函数被执行时,它的子组件的render函数都将被从新运行一次

applyMiddleWare

applyMiddleWare 的实现:

  1. 拿到原生的store跟dispatch
  2. 对dispatch作了一层扩展
  3. 将原生store中的dispatch覆盖掉

为何须要VDOM?

若是没有VDOM,state改变,如何渲染页面?

最原始的作法:

  1. state 数据
  2. JSX 模板
  3. 数据 + 模板 结合,生成真实DOM,来显示
  4. state发生改变
  5. 数据 + 模板 结合,生成真实DOM,替换原来的DOM

这样作的缺陷:

  1. 第一次生成了完整的DOM片断
  2. 第二次生成了完整的DOM片断
  3. 第二次的DOM替换第一次的DOM,很是耗性能

改进的作法:

  1. state 数据
  2. JSX 模板
  3. 数据 + 模板 结合,生成真实DOM,来显示
  4. state发生改变
  5. 数据 + 模板 结合,生成真实DOM,不直接替换原来的DOM
  6. 新的DOM(DocumentFragment) 和 原始的DOM 作对比,找差别
  7. 只替换有变更的DOM元素

这样作的缺陷:性能提高不明显,由于对比DOM也消耗了性能

react的作法

  1. state 数据
  2. JSX 模板
  3. 数据 + 模板结合,生成VDOM(VDOM就是一个JS对象,用他来描述真实DOM)
  4. 用VDOM,生成真实DOM,来显示
  5. state发生改变
  6. 生成新的VDOM (极大提高性能)
  7. 比较原始VDOM和新的VDOM的区别 (极大提高性能)
  8. 只替换有变更的DOM元素

优势:

  1. 性能提高了
  2. 方便与其余平台集成,跨端应用得以实现

VDOM原理

JSX的运行基础就是VDOM。

VDOM的运行机制是广度优先分层比较。

VDOM 的两个假设:

  1. 组件的DOM结构是相对稳定的(不多发生跨层移动的场景)
  2. 类型相同的兄弟节点能够被惟一标识

事件系统

VDOM 在内存中是以对象的形式存在的,若是想要在这些对象上添加事件,就会很是简单。

React 基于 VDOM 实现了一个 SyntheticEvent (合成事件)层,咱们所定义的事件处理器会接收到一个 SyntheticEvent 对象的实例,它彻底符合 W3C 标准,不会存在任何 IE 标准的兼容性问题。而且与原生的浏览器事件同样拥有一样的接口,一样支持事件的冒泡机制,咱们可使用 stopPropagation() 和 preventDefault() 来中断它。全部事件都自动绑定到最外层上。若是须要访问原生事件对象,可使用 nativeEvent 属性。

合成事件的实现机制

在 React 底层,主要对合成事件作了两件事:事件委派和自动绑定

事件委派

react 并不会把事件处理函数直接绑定到真实的节点上,而是把全部事件绑定到结构的最外层,使用一个统一的事件监听器。

事件监听器上维持了一个映射来保存全部组件内部的事件监听和处理函数。

当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象

当事件发生时,首先被这个统一的事件监听器处理,而后在映射里找到真正的事件处理函数并调用。

这样作简化了事件处理和回收机制,效率也有很大提高。

自动绑定

在 React 组件中,每一个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件。 并且 React 还会对这种引用进行缓存,以达到 CPU 和内存的最优化。在使用 ES6 classes 或者纯函数时,这种自动绑定就不复存在了,咱们须要手动实现 this 的绑定。

常见的绑定方法有:

  • 双冒号语法:<button onClick={::this.handleClick}>Test</button>
  • 构造器内使用bind绑定
  • 箭头函数

合成事件与原生事件对比

事件对象

原生 DOM 事件对象在 W3C 标准和 IE 标准下存在着差别。在低版本的 IE 浏览器中,只能使用 window.event 来获取事件对象。

而在 React 合成事件系统中,不存在这种兼容性问题,在事件处理函数中能够获得一个合成事件对象。

事件类型

React 合成事件的事件类型是 JS 原生事件类型的一个子集

事件传播与阻止事件传播

事件传播分为捕获阶段、目标阶段、冒泡阶段。

事件捕获在程序开发中的意义不大,还有兼容性问题。因此,React 的合成事件则并无实现事件捕获,仅仅支持了事件冒泡机制。

阻止事件传播:阻止原生事件传播须要使用 e.preventDefault(),不过对于不支持该方法的浏览器(IE9 以 下),只能使用 e.cancelBubble = true 来阻止。而在 React 合成事件中,只须要使用 e.preventDefault() 便可。

事件绑定方式

原生事件有三种方式:

  • 直接在DOM元素中绑定: <button onclick="alert(1)">Test</button>
  • 在JS中,经过为元素的事件属性赋值的方式实现绑定:el.onclick = e => {console.log(e)}
  • 经过事件监听函数来实现绑定:el.addEventListener("click", ()=>{},false); el.attachEvent("onclick", ()=>{})

React 合成事件的绑定方式则简单得多:<button onClick={this.handleClick}>Test</button>


获取真实DOM的方式

要获取真实的DOM节点有两种方式,一种是经过e.target,一种是ref。

但能不使用ref尽可能不用


注意事项

1、原生事件

componentDidMount 会在组件已经完成安装而且在浏览器中存在真实的 DOM 后调用,此时咱们就能够完成原生事件的绑定。

在 React 中使用 DOM 原生事件时,必定要在组件卸载时手动移除,不然很 可能出现内存泄漏的问题。而使用合成事件系统时则不须要,由于 React 内部已经帮你妥善地处理了。

2、合成事件与原生事件混用

尽可能避免在 React 中混用合成事件和原生 DOM 事件。

阻止 React 事件冒泡的行为只能用于 React 合成事件系统 中,且没办法阻止原生事件的冒泡。反之,在原生事件中的阻止冒泡行为,却能够阻止 React 合成事件的传播。

React 的合成事件系统只是原生 DOM 事件系统的一个子集。它仅仅实现了 DOM Level 3 的事件接口,而且统一了浏览器间的兼容问题。有些事件 React 并无实现,或者受某些限制没办法去实现,好比 window 的 resize 事件。


优劣局限

每种技术实现,都有其局限性,在某些条件下能最大化的发挥效能,缺乏了某些条件则暴露出其缺陷。优劣局限层回答“作得怎么样”的问题。对技术优劣局限的把握,更有利于应用时总结最佳实践,是分析各类“坑”的基础。


演进趋势

技术是在迭代改进和不断淘汰的。了解技术的前生后世,分清技术不变的本质,和变化的脉络,以及与其余技术的共生关系,能体现你对技术发展趋势的关注和思考。这层体现“将来如何”。


拥抱异步渲染

react v16.0.0 引入了叫 Fiber 这个全新的架构。这个架构使得 React 用异步渲染成为可能,但要注意,这个改变只是让异步渲染(async rendering)成为“可能”,React 却并无在 v16 发布的时候马上开启这种“可能”,也就是说,React 在 v16 发布以后依然使用的是同步渲染。

不过,虽然异步渲染没有马上采用,Fiber 架构仍是打开了通向新世界的大门,React v16 一系列新功能几乎都是基于 Fiber 架构。

要面向 React 将来,咱们首先要理解这个异步渲染的概念。

同步渲染的问题

长期以来,React 一直用的是同步渲染,这样对 React 实现很是直观方便,可是会带来性能问题。

当要渲染的组件树很是庞大,JS的单线程遇到react的同步渲染,结果就是同步渲染霸占 JS 惟一的线程,其余的操做什么都作不了,在这 1 秒钟内,若是用户要点击什么按钮,或者在某个输入框里面按键,都不会看到当即的界面反应,这也就是俗话说的“卡顿”。

在同步渲染下,要解决“卡顿”的问题,只能是尽可能缩小组件树的大小,以此缩短渲染时间,可是,应用的规模老是在增大的,不是说缩小就能缩小的,虽然咱们利用定义 shouldComponentUpdate 的方法能够减小没必要要的渲染,可是这也没法从根本上解决大量同步渲染带来的“卡顿”问题。

异步渲染:两阶段渲染

React Fiber 引入了异步渲染,有了异步渲染以后,React 组件的渲染过程是分时间片的,不是一口气从头至尾把子组件所有渲染完,而是每一个时间片渲染一点,而后每一个时间片的间隔均可去看看有没有更紧急的任务(好比用户按键),若是有,就去处理紧急任务,若是没有那就继续照常渲染。

根据 React Fiber 的设计,一个组件的渲染被分为两个阶段:第一个阶段(也叫作 render 阶段)是能够被 React 打断的,一旦被打断,这阶段所作的全部事情都被废弃,当 React 处理完紧急的事情回来,依然会从新渲染这个组件,这时候第一阶段的工做会重作一遍;第二个阶段叫作 commit 阶段,一旦开始就不能中断,也就是说第二个阶段的工做会稳妥当当地作到这个组件的渲染结束。

两个阶段的分界点,就是 render 函数。render 函数以前的全部生命周期函数(包括 render)都属于第一阶段,以后的都属于第二阶段。

开启异步渲染,虽然咱们得到了更好的感知性能,可是考虑到第一阶段的的生命周期函数可能会被重复调用,不得不对历史代码作一些调整。

在 React v16.3 以前,render 以前的生命周期函数(也就是第一阶段生命周期函数)包括这些:

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • componentWillMount
  • render

React 官方告诫开发者,虽然目前全部的代码均可以照常使用,可是将来版本中会废弃掉,为了未来,使用 React 的程序应该快点去掉这些在第一阶段生命函数中有反作用的功能。

一个典型的错误用例,也是我被问到作多的问题之一:为何不在 componentWillMount 里去作AJAX?componentWillMount 但是比 componentDidMount 更早调用啊,更早调用意味着更早返回结果,那样性能不是更高吗?

首先,一个组件的 componentWillMount 比 componentDidMount 也早调用不了几微秒,性能没啥提升;并且,等到异步渲染开启的时候,componentWillMount 就可能被中途打断,中断以后渲染又要重作一遍,想想,在 componentWillMount 中作 AJAX 调用,代码里看到只有调用一次,可是实际上可能调用 N 屡次,这明显不合适。相反,若把 AJAX 放在 componentDidMount,由于 componentDidMount 在第二阶段,因此绝对不会屡次重复调用,这才是 AJAX 合适的位置

getDerivedStateFromProps

到了 React v16.3,React 干脆引入了一个新的生命周期函数 getDerivedStateFromProps,这个生命周期函数是一个 static 函数,在里面根本不能经过 this 访问到当前组件,输入只能经过参数,对组件渲染的影响只能经过返回值。没错,getDerivedStateFromProps 应该是一个纯函数,React 就是经过要求这种纯函数,强制开发者们必须适应异步渲染。

static getDerivedStateFromProps(nextProps, prevState) {
  //根据nextProps和prevState计算出预期的状态改变,返回结果会被送给setState
}
复制代码

React v16发布时,还增长了异常处理的生命周期函数。

若是异常发生在第一阶段(render阶段),React就会调用getDerivedStateFromError,若是异常发生在第二阶段(commit阶段),React会调用componentDidCatch。这个区别也体现出两个阶段的区分对待。

适应异步渲染的组件原则

当 React 开启异步渲染的时候,你的代码应该作到在 render 以前最多只能这些函数被调用:

  • 构造函数
  • getDerivedStateFromProps
  • shouldComponentUpdate

幸存的这些第一阶段函数,除了构造函数,其他两个全都必须是纯函数,也就是不该该作任何有反作用的操做。


Suspense带来的异步操做革命

Suspense 应用的场合就是异步数据处理,最多见的例子,就是经过 AJAX 从服务器获取数据,每个 React 开发者都曾为这个问题纠结。

若是用一句话归纳 Suspense 的功用,那就是:用同步的代码来实现异步操做。

React 同步操做的不足

React 最初的设计,整个渲染过程都是同步的。同步的意思是,当一个组件开始渲染以后,就必须一口气渲染完,不能中断,对于特别庞大的组件树,这个渲染过程会很耗时,并且,这种同步处理,也会致使咱们的代码比较麻烦。

当咱们开始渲染某个组件的时候,假设这个组件须要从服务器获取数据,那么,要么由这个组件的父组件想办法拿到服务器的数据,而后经过 props 传递进来,要么就要靠这个组件自力更生来获取数据,可是,没有办法经过一次渲染完成这个过程,由于渲染过程是同步的,不可能让 React 等待这个组件调用 AJAX 获取数据以后再继续渲染。

经常使用的作法,须要组件的 render 和 componentDidMount 函数配合。

  1. 在 componentDidMount 中使用 AJAX,在 AJAX 成功以后,经过 setState 修改自身状态,这会引起一次新的渲染过程。
  2. 在 render 函数中,若是 state 中没有须要的数据,就什么都不渲染或者渲染一个“正在装载”之类提示;若是 state 中已经有须要的数据,就能够正常渲染了,但这也一定是在 componentDidMount 修改了 state 以后,也就是只有在第二次渲染过程当中才能够。

下面是代码实例:

class Foo extends React.Component {
  state = {
    data: null,
  };

  render () {
    if (!this.state.data) {
      return null;
    } else {
      return <div>this.state.data</div>;
    }
  }

  componentDidMount () {
    callAPI ().then (result => {
      this.setState ({data: result});
    });
  }
}
复制代码

这种方式虽然可行,咱们也照这种套路写过很多代码,但它的缺点也是很明显的。

  • 组件必需要有本身的 state 和 componentDidMount 函数实现,也就不可能作成纯函数形式的组件。
  • 须要两次渲染过程,第一次是 mount 引起的渲染,由 componentDidMount 触发 AJAX 而后修改 state,而后第二次渲染才真的渲染出内容。
  • 代码啰嗦,十分啰嗦。

理想中的代码形式

而 Suspense 就是为了克服上述 React 的缺点。

在了解 Suspense 怎么解决这些问题以前,咱们不妨本身想象一下,若是要利用 AJAX 获取数据,代码怎样写最简洁高效?

我先来讲一说本身设想的最佳代码形式。首先,我不想写一个有状态的组件,由于经过 AJAX 获取的数据每每也就在渲染用一次,不必存在 state 里;其次,想要使数据拿来就用,不须要通过 componentDidMount 走一圈。因此,代码最好是下面这样:

const Foo = () => {
  const data = callAPI ();
  return <div>{data}</div>;
};
复制代码

够简洁吧,但是目前的 React 版本作不到啊!

由于 callAPI 确定是一个异步操做,不可能得到同步数据,没法在同步的 React 渲染过程当中立足。

不过,如今作不到,不表明未来作不到,未来 React 会支持这样的代码形式,这也就是 Suspense。

有了Suspense,咱们能够这样写代码:

const Foo = () => {
  const data = createFetcher (callAJAX).read ();
  return <div>{data}</div>;
};
复制代码

接下来,咱们就介绍一下 Suspense 的原理。

在 React 推出 v16 的时候,就增长了一个新生命周期函数 componentDidCatch。若是某个组件定义了 componentDidCatch,那么这个组件中全部的子组件在渲染过程当中抛出异常时,这个 componentDidCatch 函数就会被调用。

能够这么设想,componentDidCatch 就是 JS 语法中的 catch,而对应的 try 覆盖全部的子组件,就像下面这样:

try {
  //渲染子组件
} catch (error) {
  // componentDidCatch被调用
}
复制代码

Suspense 就是巧妙利用 componentDidCatch 来实现同步形式的异步处理。

Suspense 提供的 createFetcher 函数会封装异步操做,当尝试从 createFetcher 返回的结果读取数据时,有两种可能:一种是数据已经就绪,那就直接返回结果;还有一种多是异步操做尚未结束,数据没有就绪,这时候 createFetcher 会抛出一个“异常”。

你可能会说,抛出异常,渲染过程不就中断了吗?

的确会中断,不过,createFetcher 抛出的这个“异常”比较特殊,这个“异常”其实是一个 Promise 对象,这个 Promise 对象表明的就是异步操做,操做结束时,也是数据准备好的时候。当 componentDidCatch 捕获这个 Promise 类型的“异常”时,就能够根据这个 Promise 对象的状态改变来从新渲染对应组件,第二次渲染,确定就可以成功。

下面是 createFetcher 的一个简单实现方式:

var NO_RESULT = {};

export const createFetcher = task => {
  let result = NO_RESULT;

  return () => {
    const p = task ();

    p.then (res => {
      result = res;
    });

    if (result === NO_RESULT) {
      throw p;
    }

    return result;
  };
};
复制代码

在上面的代码中,createFetcher 的参数 task 被调用应该返回一个 Promise 对象,这个对象在第一次调用时会被 throw 出去,可是,只要这个对象完结,那么 result 就有实际的值,不会再被 throw。

还须要一个和 createFetcher 配合的 Suspense,代码以下:

class Suspense extends React.Component {
  state = {
    pending: false,
  };

  componentDidCatch (error) {
    // easy way to detect Promise type
    if (typeof error.then === 'function') {
      this.setState ({pending: true});

      error.then (() =>
        this.setState ({
          pending: false,
        })
      );
    }
  }

  render () {
    return this.state.pending ? null : this.props.children;
  }
}
复制代码

上面的 Suspense 组件实现了 componentDidCatch,若是捕获的 error 是 Promise 类型,那就说明子组件用 createFetcher 获取异步数据了,就会等到它完结以后重设 state,引起一次新的渲染过程,由于 createFetcher 中会记录异步返回的结果,新的渲染就不会抛出异常了。

使用 createFetcher 和 Suspense 的示例代码以下:

const getName = () =>
  new Promise (resolve => {
    setTimeout (() => {
      resolve ('Morgan');
    }, 1000);
  });

const fetcher = createFetcher (getName);

const Greeting = () => {
  return <div>Hello {fetcher ()}</div>;
};

const SuspenseDemo = () => {
  return (
    <Suspense> <Greeting /> </Suspense>
  );
};
复制代码

上面的 getName 利用 setTimeout 模拟了异步 AJAX 获取数据,第一次渲染 Greeting 组件时,会有 Promise 类型的异常抛出,被 Suspense 捕获。1 秒钟以后,当 getName 返回实际结果的时候,Suspense 会引起从新渲染,这一次 Greeting 会显示出 hello Morgan。

上面的 createFetcher 和 Suspense 是一个很是简陋的实现,主要用来让读者了解 Suspense 的工做原理,正式发布的 Suspense 确定会具有更强大的功能。

Suspense 带来的 React 使用模式改变

Suspense 被推出以后,能够极大地减小异步操做代码的复杂度。

以前,只要有 AJAX 这样的异步操做,就必需要用两次渲染来显示 AJAX 结果,这就须要用组件的 state 来存储 AJAX 的结果,用 state 又意味着要把组件实现为一个 class。总之,咱们须要作这些:

  • 实现一个 class;
  • class 中须要有 state;
  • 须要实现 componentDidMount 函数;
  • render 必需要根据 this.state 来渲染不一样内容。

有了 Suspense 以后,不须要作上面这些琐事,只要一个函数形式组件就足够了。

在介绍 Redux 时,咱们提到过在 Suspense 面前,Redux 的一切异步操做方案都显得繁琐,读者如今应该可以经过代码理解这一点了。

很惋惜,目前 Suspense 还不支持服务器端渲染,当 Suspense 支持服务器端渲染的时候,那就真的会对 React 社区带来革命性影响。


函数化的 Hooks

Hooks 的目的,简而言之就是让开发者不须要再用 class 来实现组件。

useState

Hooks 会提供一个叫 useState 的方法,它开启了一扇新的定义 state 的门,对应 Counter 的代码能够这么写:

import {useState} from 'react';

const Counter = () => {
  const [count, setCount] = useState (0);

  return (
    <div> <div>{count}</div> <button onClick={() => setCount (count + 1)}>+</button> <button onClick={() => setCount (count - 1)}>-</button> </div>
  );
};
复制代码

注意看,Counter 拥有本身的“状态”,但它只是一个函数,不是 class。

useState 只接受一个参数,也就是 state 的初始值,它返回一个只有两个元素的数组,第一个元素就是 state 的值,第二个元素是更新 state 的函数。

这个例子中,咱们能够利用 count 能够读取到这个 state,利用 setCount 能够更新这个 state。

由于 useState 在 Counter 这个函数体中,每次 Counter 被渲染的时候,这个 useState 调用都会被执行,useState 本身确定不是一个纯函数,由于它要区分第一次调用(组件被 mount 时)和后续调用(重复渲染时),只有第一次才用得上参数的初始值,然后续的调用就返回“记住”的 state 值。

读者看到这里,内心可能会有这样的疑问:若是组件中屡次使用 useState 怎么办?React 如何“记住”哪一个状态对应哪一个变量?

React 是彻底根据 useState 的调用顺序来“记住”状态归属的,假设组件代码以下:

const Counter = () => {
  const [count, setCount] = useState (0);
  const [foo, updateFoo] = useState ('foo');

  // ...
};
复制代码

每一次 Counter 被渲染,都是第一次 useState 调用得到 count 和 setCount,第二次 useState 调用得到 foo 和 updateFoo。

React 不知道你把 useState 等 Hooks API 返回的结果赋值给什么变量,可是它也不须要知道,它只须要按照 useState 调用顺序记录就行了。

正由于这个缘由,Hooks,千万不要在 if 语句或者 for 循环语句中使用!

像下面的代码,确定会出乱子的:

const Counter = () => {
  const [count, setCount] = useState (0);
  if (count % 2 === 0) {
    const [foo, updateFoo] = useState ('foo');
  }
  const [bar, updateBar] = useState ('bar');
};
复制代码

由于条件判断,让每次渲染中 useState 的调用次序不一致了,因而 React 就错乱了。

useEffect

除了 useState,React 还提供 useEffect,用于支持组件中增长反作用的支持。

在 React 组件生命周期中若是要作有反作用的操做,代码放在哪里?

固然是放在 componentDidMount 或者 componentDidUpdate 里,可是这意味着组件必须是一个 class。

在 Counter 组件,若是咱们想要在用户点击“+”或者“-”按钮以后把计数值体如今网页标题上,这就是一个修改 DOM 的反作用操做,因此必须把 Counter 写成 class,并且添加下面的代码:

componentDidMount () {
    document.title = `Count: ${this.state.count}`;
  }

  componentDidUpdate () {
    document.title = `Count: ${this.state.count}`;
  }
复制代码

而有了 useEffect,咱们就不用写一个 class 了,对应代码以下:

import {useState, useEffect} from 'react';

const Counter = () => {
  const [count, setCount] = useState (0);

  useEffect (() => {
    document.title = `Count: ${count}`;
  });

  return (
    <div> <div>{count}</div> <button onClick={() => setCount (count + 1)}>+</button> <button onClick={() => setCount (count - 1)}>-</button> </div>
  );
};
复制代码

useEffect 的参数是一个函数,组件每次渲染以后,都会调用这个函数参数,这样就达到了 componentDidMount 和 componentDidUpdate 同样的效果。

虽然本质上,依然是 componentDidMount 和 componentDidUpdate 两个生命周期被调用,可是如今咱们关心的不是 mount 或者 update 过程,而是“after render”事件,useEffect 就是告诉组件在“渲染完”以后作点什么事。

读者可能会问,如今把 componentDidMount 和 componentDidUpdate 混在了一块儿,那假如某个场景下我只在 mount 时作事但 update 不作事,用 useEffect 不就不行了吗?

其实,用一点小技巧就能够解决。useEffect 还支持第二个可选参数,只有同一 useEffect 的两次调用第二个参数不一样时,第一个函数参数才会被调用,因此,若是想模拟 componentDidMount,只须要这样写:

useEffect(() => {
  // 这里只有mount时才被调用,至关于componentDidMount
}, [123]);
复制代码

在上面的代码中,useEffect 的第二个参数是 [123],其实也能够是任何一个常数,由于它永远不变,因此 useEffect 只在 mount 时调用第一个函数参数一次,达到了 componentDidMount 同样的效果。

useContext

Context API的设计不完美,在多个 Context 嵌套的时候尤为麻烦。

好比,一段 JSX 若是既依赖于 ThemeContext 又依赖于 LanguageContext,那么按照 React Context API 应该这么写:

<ThemeContext.Consumer>
  {theme => (
    <LanguageContext.Cosumer> language => { //可使用theme和lanugage了 } </LanguageContext.Cosumer> )} </ThemeContext.Consumer>; 复制代码

由于 Context API 要用 render props,因此用两个 Context 就要用两次 render props,也就用了两个函数嵌套,这样的缩格看起来也的确过度了一点点。

使用 Hooks 的 useContext,上面的代码能够缩略为下面这样:

const theme = useContext(ThemeContext);
const language = useContext(LanguageContext);
// 这里就能够用theme和language了
复制代码

这个useContext把一个须要很费劲才能理解的 Context API 使用大大简化,不须要理解render props,直接一个函数调用就搞定。

可是,useContext也并非完美的,它会形成意想不到的从新渲染,咱们看一个完整的使用useContext的组件。

const ThemedPage = () => {
  const theme = useContext (ThemeContext);

  return (
    <div>
      <Header color={theme.color} />
      <Content color={theme.color} />
      <Footer color={theme.color} />
    </div>
  );
};
复制代码

由于这个组件ThemedPage使用了useContext,它很天然成为了Context的一个消费者,因此,只要Context的值发生了变化,ThemedPage就会被从新渲染,这很天然,由于不从新渲染也就没办法从新得到theme值,但如今有一个大问题,对于ThemedPage来讲,实际上只依赖于theme中的color属性,若是只是theme中的size发生了变化可是color属性没有变化,ThemedPage依然会被从新渲染,固然,咱们经过给Header、Content和Footer这些组件添加shouldComponentUpdate实现能够减小没有必要的从新渲染,可是上一层的ThemedPage中的JSX从新渲染是躲不过去了。

说到底,useContext须要一种表达方式告诉React:“我没有改变,重用上次内容好了。”

但愿Hooks正式发布的时候可以弥补这一缺陷。

Hooks 带来的代码模式改变

Hooks 将大大简化使用 React 的代码。

咱们可能再也不须要 class了,只用函数形式来编写组件。

对于 useContext,它并无为消除 class 作贡献,却为消除 render props 模式作了贡献

对了,全部的 Hooks API 都只能在函数类型组件中调用,class 类型的组件不能用,从这点看,很显然,class 类型组件将会走向消亡。

相关文章
相关标签/搜索