React 新特性

  1. React 中一个常见模式是为一个组件返回多个元素。Fragments 可以让你聚合一个子元素列表,并且不在DOM中增加额外节点。
class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}

注意:①key 是唯一可以传递给 Fragment 的属性。② 在 React 中,<></><React.Fragment><React.Fragment/> 的语法糖。

  1. Strict Mode严格模式
    StrictMode是一个用以标记出应用中潜在问题的工具。与Fragment类似,StrictMode不会渲染任何真实的UI。它为其后代元素触发额外的检查和警告。
import { StrictMode, Component } from 'react'

class Child extends Component {
  // 以下三个函数在 React v16.3 已不被推荐,未来的版本会废弃。
  componentWillMount() {
    console.log('componentWillMount')
  }
  componentWillUpdate() {
    console.log('componentWillUpdate')
  }
  componentWillReceiveProps() {
    console.log('componentWillReceiveProps')
  }
  render() {
    return (
      <div />
    )
  }
}

export default class StrictModeExample extends Component {
  render() {
    return (
      <StrictMode>
        <Child />
      </StrictMode>
    )
  }
}

由于在StrictMode内使用了三个即将废弃的API,打开控制台 ,可看到如下错误提醒:

控制台报错信息

注释:严格模式检查只在开发模式下运行,不会与生产模式冲突。

  1. createRef (v16.3)
    (1) 老版本ref使用方式
    ① 字符串形式: <input ref="input" />
    ② 回调函数形式:<input ref={input => (this.input = input)} />
    (2) 字符串形式缺点
    ① 需要内部追踪 refthis 取值,会使 React 稍稍变慢。
    ② 有时候this与你想象的并不一致。
import React from 'react'

class Children extends React.Component {
  componentDidMount() {
    // <h1></h1>
    console.log('children ref', this.refs.titleRef)
  }
  render() {
    return (
      <div>
        {this.props.renderTitle()}
      </div>
    )
  }
}

class Parent extends React.Component {
  // 放入子组件渲染
  renderTitle = () => (
    <h1 ref='titleRef'>{this.props.title}</h1>
  )

  componentDidMount() {
    // undefined
    console.log('parent ref:', this.refs.titleRef)
  }
  render() {
    return (
      <Children renderTitle={this.renderTitle}></Children>
    )
  }
}

export default Parent

(3) createRef语法

import React from 'react'

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

  render() {
    return <input type="text" ref={this.inputRef} />;
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }
}

export default MyComponent
  1. 调用setState更新状态时,若之后的状态依赖于之前的状态,推荐使用传入函数形式。
    语法:setState((prevState, props) => stateChange, [callback])
    例如,假设我们想通过props.step在状态中增加一个值:
this.setState((prevState, props) => {
  return {counter: prevState.counter + props.step};
});
  1. 错误边界
    (1) 错误边界用于捕获其子组件树 JavaScript 异常,记录错误并展示一个回退的 UIReact 组件,避免整个组件树异常导致页面空白。
    (2) 错误边界在渲染期间、生命周期方法内、以及整个组件树构造函数内捕获错误。
    (3) 组件如果定义了static getDerivedStateFromError()componentDidCatch()中的任意一个或两个生命周期方法 。当其子组件抛出错误时,可使用static getDerivedStateFromError()更新state,可使用componentDidCatch()记录错误信息。
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // 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; 
  }
}

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

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

注释:getDerivedStateFromErrorcomponentDidCatch方法使用一个即可捕获子组件树错误。

  1. Portal
    (1) Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。
    (2) 通过 Portals 进行事件冒泡
    尽管 portal 可以被放置在DOM 树的任何地方,但在其他方面其行为和普通的 React 子节点行为一致。一个从 portal 内部会触发的事件会一直冒泡至包含 React 树 的祖先。
import React from 'react';
import ReactDOM from 'react-dom';

class Modal extends React.Component {
  render() {
    return this.props.clicks % 2 === 1
      ? this.props.children
      : ReactDOM.createPortal(
        this.props.children,
        document.getElementById('modal'),
      );
  }
}

class Child extends React.Component {
  render() {
    return (
      <button>Click</button>
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {clicks: 0};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      clicks: prevState.clicks + 1
    }));
  }

  render() {
    return (
      <div onClick={this.handleClick}>
        <p>Number of clicks: {this.state.clicks}</p>
        <Modal clicks={this.state.clicks}>
          <Child />
        </Modal>
      </div>
    );
  }
}

export default Parent

注释:当组件由portal渲染方式切换为普通渲染方式,会导致该组件被卸载之后重新渲染。组件放大功能如果通过portal方式实现,放大前的状态(滚动位置、焦点位置等)无法保持。

  1. fiber介绍
    fiberReact 16中新的和解引擎。它的主要目的是使虚拟DOM能够进行增量渲染。
    (1) 同步更新过程的局限
    React决定要加载或者更新组件树时,会做很多事,比如调用各个组件的生命周期函数,计算和比对Virtual DOM,最后更新DOM树,这整个过程是同步进行的。浏览器那个唯一的主线程都在专心运行更新操作,无暇去做任何其他的事情。
    更新过程的函数调用栈

    (2) React Fiber的方式
    破解JavaScript中同步操作时间过长的方法其实很简单——分片。
    React Fiber把更新过程碎片化,执行过程如下面的图所示,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。
    维护每一个分片的数据结构,就是Fiber
    更新过程的函数调用栈

    (3) React Fiber更新过程的两个阶段
    React Fiber中,一次更新过程会分成多个分片完成,所以完全有可能一个更新任务还没有完成,就被另一个更高优先级的更新过程打断,这时候,优先级高的更新任务会优先处理完,而低优先级更新任务所做的工作则会完全作废,然后等待机会重头再来。
    React Fiber一个更新过程被分为两个阶段(Phase):第一个阶段Reconciliation Phase和第二阶段Commit Phase
    在第一阶段Reconciliation PhaseReact Fiber会找出需要更新哪些DOM,这个阶段是可以被打断的;但是到了第二阶段Commit Phase,那就一鼓作气把DOM更新完,绝不会被打断。
    (4) React Fiber对现有代码的影响
    image.png

    因为第一阶段的过程会被打断而且“重头再来”,就会造成第一阶段中的生命周期函数在一次加载和更新过程中可能会被多次调用!
    第一个阶段的四个生命周期函数中,componentWillReceivePropscomponentWillMountcomponentWillUpdate这三个函数可能包含副作用,所以当使用React Fiber的时候一定要重点看这三个函数的实现。
    注释:大家应该都清楚进程(Process)和线程(Thread)的概念,在计算机科学中还有一个概念叫做Fiber,英文含义就是“纤维”,意指比Thread更细的线,也就是比线程(Thread)控制得更精密的并发处理机制。
  2. 声明周期变化
    v16.3 开始,原来的三个生命周期 componentWillMountcomponentWillUpdatecomponentWillReceiveProps 将被废弃,取而代之的是两个全新的生命周期:
    static getDerivedStateFromProps
    getSnapshotBeforeUpdate
  3. getDerivedStateFromProps用法
    static getDerivedStateFromProps(nextProps, prevState)
    组件实例化后接受新属性时将会调用getDerivedStateFromProps。它应该返回一个对象来更新状态,或者返回null来表明新属性不需要更新任何状态。
    如果父组件导致了组件的重新渲染,即使属性没有更新,这一方法也会被调用。
    如果你只想处理变化,你可能想去比较新旧值。调用this.setState()通常不会触发getDerivedStateFromProps()
  4. getStapshotBeforeUpdate
    getSnapshotBeforeUpdate()在最新的渲染输出提交给DOM前将会立即调用。它让你的组件能在当前的值可能要改变前获得它们。这一生命周期返回的任何值将会 作为参数被传递给componentDidUpdate()
class ScrollingList extends React.Component {
  listRef = React.createRef();

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the current height of the list so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      return this.listRef.current.scrollHeight;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    if (snapshot !== null) {
      this.listRef.current.scrollTop +=
        this.listRef.current.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.listRef}>{/* ...contents... */}</div>
    );
  }
}

在上面的例子中,为了支持异步渲染,在getSnapshotBeforeUpdate 中读取scrollHeight而不是componentWillUpdate,这点很重要。由于异步渲染,在“渲染”时期(如componentWillUpdaterender)和“提交”时期(如getSnapshotBeforeUpdatecomponentDidUpdate)间可能会存在延迟。如果一个用户在这期间做了像改变浏览器尺寸的事,从componentWillUpdate中读出的scrollHeight值将是滞后的。

  1. Context用法
import React from 'react'

const ThemeContext = React.createContext();

const ThemedButton = (props) => (
  <ThemeContext.Consumer>
    { context => <span style={{color: context}}>{props.text}</span> }
  </ThemeContext.Consumer>
)

const Toolbar = () => (
  <ThemeContext.Provider value='red'>
    <ThemedButton text="Context API"/>
  </ThemeContext.Provider>
)

export default Toolbar

注释:在 Context.Consumer 中,children必须为函数。

参考资料

React中文文档
深入React v16新特性(一)
深入React v16新特性(二)

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

推荐阅读更多精彩内容