React 特性剪辑(版本 16.0 ~ 16.9)

image

Before you're going to hate it, then you're going to love it.

image

Concurrent Render(贯穿 16)

在 18年的 JSConf Iceland 上, Dan 神提到 Concurrent Render 涉及到 CPU 以及 IO 这两方面。

image

Time Slicing 对应解决左侧的问题, Suspense 对应解决了右侧的问题。它们共同要解决的是的提升用户体验, 在更多的场景下都可以做到可交互。而 Fiber 架构是上述两者的基石。

Time Slicing

在 16 之前的版本的渲染过程可以想象成一次性潜水 30 米,在这期间做不了其它事情(Stack Reconciler);

image

痛点概括:

  • 一次性渲染到底
  • 中途遇到优先级更高的事件无法调整相应的顺序

接着拿上面的潜水例子为例,现在变为可以每次潜 10 米,分 3 个 chunk 进行; chunk 和 chunk 之间通过链表连接; chunk 间插入优先级更高的任务, 先前的任务被抛弃。

image

开启 Fiber 后,获取异步数据的方法应放到 render 后面的生命周期钩子里(phase 2 阶段)进行, 因为 render 前面的生命周期钩子(phase 1阶段)会被执行多次

注意: 并没有缩短原先组件的渲染时间(甚至还加长了),但用户却能感觉操作变流畅了。

requestIdleCallback(): 借力此 api, 浏览器能在空闲的时间处理低优先级的事。

Suspense(16.6, 16.8, 16.9)

Suspense 意思是能暂停当前组件的渲染, 当完成某件事以后再继续渲染。

  • code splitting(16.6, 已上线): 文件懒加载。在此之前的实现方式是 react-loadable
  • 并发模式(16.8, 2019 年 Q2 季度): 在文件懒加载的同时能做其它交互;
  • data fetching(16.9 版本, 2019 年中): 数据动态呈现;
import React, { lazy, Suspense } from 'react'
const OtherComponent = lazy(() => import('./OtherComponent'))

function MyComponent() {
  return (
    <Suspense fallback={<div>loading...</div>}>
      <OtherComponent />
    </Suspense>
  )
}

一种简单的预加载思路, 可参考 preload

const OtherComponentPromise = import('./OtherComponent');
const OtherComponent = React.lazy(() => OtherComponentPromise);

render 新增的返回类型

在 React16 版本中 render() 增加了一些返回类型,到目前为止支持的返回类型如下:

  • React elements.
  • Arrays and fragments.
  • Portals.
  • String and numbers.
  • Booleans or null.

render

其中 render() 支持返回 Arrays 能让我们少写一个父节点, 如下所示:

const renderArray = () => [
  <div key="A">A</div>
  <div key="B">B</div>
]

render() 支持返回数组的特性类似 Fragments(16.2), 使用 Fragments 可以不用写 key。

Portals(传送门)

将 React 子节点渲染到指定的节点上

案例:实现一个 Modal 组件,demo

另外关于 Portals 做到冒泡到父节点的兄弟节点这个现象, demo, 我想可以这样子实现:如果组件返回是 Portal 对象,则将该组件的父组件的上的事件 copy 到该组件上。其实并不是真的冒泡到了父节点的兄弟节点上。

Error Boundaries

React 16 提供了一个新的错误捕获钩子 componentDidCatch(error, errorInfo), 它能将子组件生命周期里所抛出的错误捕获, 防止页面全局崩溃。demo

componentDidCatch 并不会捕获以下几种错误

  • 事件机制抛出的错误(事件里的错误并不会影响渲染)
  • Error Boundaries 自身抛出的错误
  • 异步产生的错误
  • 服务端渲染

服务端渲染

服务端渲染一般是作为最后的优化手段, 这里浅显(缺乏经验)谈下 React 16 在其上的优化。

在 React 16 版本中引入了 React.hydrate(), 它的作用主要是将相关的事件注水html 页面中, 同时会比较前端生成的 html 和服务端传到前端的 html 的文本内容的差异, 如果两者不一致将前端产生的文本内容替换服务端生成的(忽略属性)。

支持自定义属性

在 React 16 版本中, 支持自定义属性(推荐 data-xxx), 因而 React 可以少维护一份 attribute 白名单, 这也是 React 16 体积减少的一个重要因素。

Context(16.3、16.6)

Context 相当于是用组件化的方式使用 global, 使用其可以共享认证的用户、首选语言(国际化)等一些全局的信息, 而不必通过组件一层层传递。

以下是比较冗余的传递:

<Page riderId={riderId} />
// ... which renders ...
<RiderDetail riderId={riderId} />
// ... which renders ...
<RiderLevel riderId={riderId} />
// ... which renders ...
<Avatar riderId={riderId} />

Context 之前可以传递 <Avatar> 本身(Component Composition 的思想), 写法如下:

function Page(props) {
  const avatar = <Avatar riderId={props.riderId} />
  return <RiderDetail avatar={avatar} />
}

<Page riderId={riderId} />
// ... which renders ...
<RiderDetail avatar={avatar} />
// ... which renders ...
<RiderLevel avatar={avatar} />
// ... which renders ...
{ props.avatar }

接着是使用 Context 书写的例子, 写法如下:

const RiderContext = React.createContext(1) // 这里为默认值

function Page(props) {
  const riderId = props.riderId
  return (
    <RiderContext.Provider value={riderId}>
      <RiderDetail />
    </RiderContext.Provider>
  )
}

function RiderDetail() {
  return <RiderLevel />
}

class RiderLevel extends React.Component {
  static contextType = RiderContext
  render() {
    return <Avatar avatar={this.context} />;
  }
}

新的生命周期(16.3)

image

在未来 17 的版本中,将移除的生命周期钩子如下:

  • componentWillMount(): 移除这个 api 基于以下两点考虑:
    • 服务端渲染: 在服务端渲染的情景下, componentWillMount 执行完立马执行 render 会导致 componentWillMount 里面执行的方法(获取数据, 订阅事件) 并不一定执行完;
    • Concurrent Render: 在 fiber 架构下, render 前的钩子会被多次调用, 在 componentWillMount 里执行订阅事件就会产生内存泄漏;

迁移思路, 将以前写在 componentWillMount 的获取数据、时间订阅的方法写进 componentDidMount 中;

  • componentWillReceiveProps(nextProps): 移除这个 api 基于如下考虑:
    • 语义不太契合逻辑

举个例子: 比如切换 tab 时都要重新获取当前页面的数据, 之前通常会这么做:

componentWillReceiveProps(nextProps) {
  if (nextProps.riderId !== this.props.riderId) {
    fetchData()
  }
}

新的钩子 getDerivedStateFromProps() 更加纯粹, 它做的事情是将新传进来的属性和当前的状态值进行对比, 若不一致则更新当前的状态。之前 componentWillReceiveProps() 里的获取数据的逻辑之前提到 Concurrent render 的时候也提到了应该后置到 componentDidUpdate() 中。

getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.riderId !== prevState.riderId) {
    return {
      riderId: nextProps.riderId
    }
  }
  // 返回 null 则表示 state 不用作更新
  return null
}
  • componentWillUpdate(): 目前将其理解为和 componentWillMount 一样的情况

在 React 16.3 的版本中,新加入了两个生命周期:

  • getDerivedStateFromProps(nextProps, prevState): 更加语义化, 用来替代 componentWillMount()componentWillReceiveProps(nextProps);

  • getSnapshotBeforeUpdate(prevProps, prevState): 可以将该钩子返回的结果传入 componentDidUpdate 的第三个参数中, 从而达到 dom 数据统一。用来替代 componentWillUpdate();

具体 demo 可见 Update on Async Rendering

React.memo(16.6)

React.memo 是一个高阶组件, 它使无状态组件拥有有状态组价中的 shouldComponentUpdate() 以及 PureComponent 的能力。

const MyComponent = React.memo(function MyComponent(props) {
  ...
})

Hooks(16.7)

在 React 16.7 之前,React 有两种形式的组件,有状态组件(类)和无状态组件(函数)。Hooks 的意义就是赋能先前的无状态组件,让之变为有状态。这样一来更加契合了 React 所推崇的函数式编程。

接下来梳理 Hooks 中最核心的 2 个 api, useStateuseEffect

useState

useState 返回状态和一个更新状态的函数

const [count, setCount] = useState(initialState)

使用 Hooks 相比之前用 class 的写法最直观的感受是更为简洁

function App() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  )
}

useEffect(fn)

在每次 render 后都会执行这个钩子。可以将它当成是 componentDidMountcomponentDidUpdatecomponentWillUnmount 的合集。因此使用 useEffect 比之前优越的地方在于:

  1. 可以避免在 componentDidMount、componentDidUpdate 书写重复的代码;
  2. 可以将关联逻辑写进一个 useEffect;(在以前得写进不同生命周期里);

React 的未来

image

今年的 React Conf 的一张图, 可以看到 React 从出来到现在势头呈稳健上升趋势, 并在 2018 年这个节点上把 Jquery 拉下了王座。但可以看见 React 未来还有一段很长的路要走。

相关链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容

  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,448评论 1 33
  • 本文整理了 React 16.x 出现的耳目一新的概念与 api 以及应用场景。 更多 React 系列文章可以订...
    牧云云阅读 1,371评论 0 1
  • 说在前面 关于 react 的总结过去半年就一直碎碎念着要搞起来,各(wo)种(tai)原(lan)因(le)。心...
    陈嘻嘻啊阅读 6,873评论 7 41
  • 40、React 什么是React?React 是一个用于构建用户界面的框架(采用的是MVC模式):集中处理VIE...
    萌妹撒阅读 1,017评论 0 1
  • 昨天是全民买买买的日子,今天之后很多人会吃一段时间的土,没有淘宝、天猫和京东不是地球人的我,异常平静,情不...
    简素七年阅读 228评论 0 0