一文读懂 React16.0-16.6 新特性(实践 思考)

首先,把我知道从16.0开始到现在为止新特性全部列举出来:

v16.0

  • render 支持返回数组和字符串
  • createPortal
  • 支持自定义 DOM 属性
  • Error Boundary
  • Fiber
  • SSR优化
  • 减少文件体积

    v16.1
  • react-call-return

    v16.2
  • Fragment

    v16.3
  • 生命周期函数的更新
  • createContext
  • createRef
  • forwardRef

    v16.4
  • 新增Pointer Events
  • fix getDerivedStateFromProps bug

    v16.5
  • React Profiler 调试

    v16.6
  • memo
  • lazy
  • suspense
  • 简化 contextType
  • 增加 getDerivedStateFromError

 


1. render支持return数组 ,字符串
React16新增加了render的返回格式,你可以return返回number,boolean,null,portal(Portals 
提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。),以及新支持的string和fragments(带有key属性的数组),不需要外层包含div标签,但必须有key。

render() {
  // 不再需要在外边包裹一个额外的元素!
  return [
    // 不要忘记加key哦 :)
    <li key="A"/>First item</li>,
    <li key="B"/>Second item</li>,
    <li key="C"/>Third item</li>,
  ];
}

2. Portals

Portals机制提供了一种最直接的方式可以把一个子组件渲染到父组件渲染的DOM树之外。默认情况下,React组件树和DOM树是完全对应的,因此对于一些Modal,Overlay之类的组件,通常是将它们放在顶层,但逻辑上它们可能只是属于某个子组件,不利于组件的代码组织。通过使用createPortal,我们可以将组件渲染到我们想要的任意DOM节点中,但该组件依然处在React的父组件之内。带来的一个特性就是,在子组件产生的event依然可以被React父组件捕获,但在DOM结构中,它却不是你的父组件。对于组件组织,代码切割来说,这是一个很好的属性。

不使用 ReactDOM.createPortal

使用 ReactDOM.createPortal

子组件

父组件


3. 支持自定义DOM属性

.......有问题

4. Error Boundary

React V16之前如果页面出现错误,整个页面会崩溃掉,本次版本您提供一个稳定的api,componentDidCatch,就像 try catch一样,用于捕获渲染过程的错误信息,避免整个页面崩溃掉。在組件中加入 componentDidCatch 方法就成为 Error Boundary 組件,當「子組件」發生錯誤時就會被這個 Error Boundary 捕捉,不会让错误信息影响到其他组件。
但是并不是所有错误都可以被捕捉上:

  1. 只能捕捉「子」组件错误,边界本身无法被捕捉。
  2. 只能捕捉子组件「渲染」「各生命周期方法」中的错误。
  3. 牵涉到非同步执行 (ex. setTimeout, callback ) 中的错误无法被捕捉。
  4. 无法捕捉 event handler 中发生的错误。

子组件

父组件

5. Fiber

专门写一篇文章研究。

6. SSR优化

  • 生成更简洁的HTML
  • 宽松的客户端一致性校验
  • 无需提前编译
  • react 16服务端渲染速度更快
  • 支持流式渲染 

    1)生成更简洁的HTML

先看下面的HTML,react 15与react 16的服务端分别会生成什么。

renderToString(  
 <div>     
   This is some <span>server-generated</span> <span>HTML.</span> 
 </div> );

react15:

有data-reactid, text noded ,react-text各种属性。

<div data-reactroot="" data-reactid="1" 
    data-react-checksum="122239856">
  <!-- react-text: 2 -->This is some <!-- /react-text -->
  <span data-reactid="3">server-generated</span>
  <!-- react-text: 4--> <!-- /react-text -->
  <span data-reactid="5">HTML.</span>
</div>

react 16:

<div data-reactroot="">   
    This is some <span>server-generated</span> 
    <span>HTML.</span> 
</div>

可以看到,react 16去掉了很多属性,它的好处很明显:增加易读性,同时很大程度上减少html的文件大小。

2) 宽松的客户端一致性校验

react 15:会将SSR的结果与客户端生成的做一个个字节的对比校验 ,一点不匹配发出waring同时就替换整个SSR生成的树。

react 16:对比校验会更宽松一些,比如,react 16允许属性顺序不一致,而且遇到不匹配的标签,还会做子树的修改,不是整个替换。

注意点: react16不会自动fix SSR 属性跟client html属性的不同,但是仍然会报waring,所以我们需要自己手动去修改。

3) 无需提前编译

react 15:如果你直接使用SSR,会有很多需要检查procee.env的地方,但是读取在node中读取process.env是很消耗时间的。所以在react 15的时候,需要提前编译,这样就可以移除 process.env的引用。

react 16:只有一次检查process.env的地方,所以就不需要提前编译了,可以开箱即用。

4) react 16服务端渲染速度更快

为什么呢? 因为react 15下,server client都需要生成vDOM,但是其实在服务端, 当我们使用renderToString的时候,生成的vDom就会被立即抛弃掉, 所以在server端生成vDom是没有意义的。

5) 支持流式渲染 (renderyToNodeStream)

使用流式渲染会提升首个字节到(TTFB)的速度。但是什么是流式渲染呢?

可以理解为内容以一种流的形式传给前端。所以在下一部分的内容被生成之前,开头的内容就已经被发到浏览器端了。这样浏览器就可以更早的编译渲染文件内容。

7. 减小了32%bundle的体积

React 库大小从 20.7kb(压缩后 6.9kb)降低到 5.3kb(压缩后 2.2kb)

ReactDOM 库大小从 141kb(压缩后 42.9kb)降低到 103.7kb(压缩后 32.6kb)

React + ReactDOM 库大小从 161.7kb(压缩后 49.8kb)降低到 109kb(压缩后 43.8kb)

8. react-call-return

现在只是实验性质的api,不建议使用。

8. Fragment

使用 React.Fragment 组件,React.Fragment 组件可以在 React 对象上使用。 这可能是必要的,如果你的工具还不支持 JSX 片段。 注意在 React 中, <></> 是 <React.Fragment/> 的语法糖。也可以在一个循环中使用生成代码片段,可以是唯一一个可以给fragment的属性

class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}


function Glossary(props) {
  return (
    <dl>
      {props.items.map(item => (
        // 没有`key`,将会触发一个key警告
        <React.Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.description}</dd>
        </React.Fragment>
      ))}
    </dl>
  );
}

9. createContext

想了又想还是专门写文章介绍。

10. createRef

Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素。

16.3之前,ref 通过字符串(string ref)或者回调函数(callback ref)的形式进行获取

使用 React.createRef() 创建 refs,通过 ref 属性来获得 React 元素。当构造组件时,refs 通常被赋值给实例的一个属性,这样你可以在组件中任意一处使用它们.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

那为什么要替换 string ref ,下面我们来列举string ref的诸多罪状:

1) 当 ref 定义为 string 时,需要 React 追踪当前正在渲染的组件,在 reconciliation 阶段,React Element 创建和更新的过程中,ref 会被封装为一个闭包函数,等待 commit 阶段被执行,这会对 React 的性能产生一些影响。

function coerceRef(
  returnFiber: Fiber,
  current: Fiber | null,
  element: ReactElement,
) {
  ...
  const stringRef = '' + element.ref;
  // 从 fiber 中得到实例
  let inst = ownerFiber.stateNode;
  
  // ref 闭包函数
  const ref = function(value) {
    const refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;
    if (value === null) {
      delete refs[stringRef];
    } else {
      refs[stringRef] = value;
    }
  };
  ref._stringRef = stringRef;
  return ref;
  ...
}

2) 当使用 render callback 模式时,使用 string ref 会造成 ref 挂载位置产生歧义。

class MyComponent extends Component {
  renderRow = (index) => {
    // string ref 会挂载在 DataTable this 上
    return <input ref={'input-' + index} />;

    // callback ref 会挂载在 MyComponent this 上
    return <input ref={input => this['input-' + index] = input} />;
  }
 
  render() {
    return <DataTable data={this.props.data} renderRow={this.renderRow} />
  }
}

3) string ref 无法被组合,例如一个第三方库的父组件已经给子组件传递了 ref,那么我们就无法再在子组件上添加 ref 了,而 callback ref 可完美解决此问题。

/** string ref **/
class Parent extends React.Component {
  componentDidMount() {
    // 可获取到 this.refs.childRef
    console.log(this.refs);
  }
  render() {
    const { children } = this.props;
    return React.cloneElement(children, {
      ref: 'childRef',
    });
  }
}

class App extends React.Component {
  componentDidMount() {
    // this.refs.child 无法获取到
    console.log(this.refs);
  }
  render() {
    return (
      <Parent>
        <Child ref="child" />
      </Parent>
    );
  }
}
/** callback ref **/
class Parent extends React.Component {
  componentDidMount() {
    // 可以获取到 child ref
    console.log(this.childRef);
  }
  render() {
    const { children } = this.props;
    return React.cloneElement(children, {
      ref: (child) => {
        this.childRef = child;
        children.ref && children.ref(child);
      }
    });
  }
}

class App extends React.Component {
  componentDidMount() {
    // 可以获取到 child ref
    console.log(this.child);
  }
  render() {
    return (
      <Parent>
        <Child ref={(child) => {
          this.child = child;
        }} />
      </Parent>
    );
  }
}

4) 在根组件上使用无法生效。

ReactDOM.render(<App ref="app" />, document.getElementById('main')); 

5) 对于静态类型较不友好,当使用 string ref 时,必须显式声明 refs 的类型,无法完成自动推导。

6) 编译器无法将 string ref 与其 refs 上对应的属性进行混淆,而使用 callback ref,可被混淆。

/** string ref,无法混淆 */
this.refs.myRef
<div ref="myRef"></div>

/** callback ref, 可以混淆 */
this.myRef
<div ref={(dom) => { this.myRef = dom; }}></div>

this.r
<div ref={(e) => { this.r = e; }}></div>

11. forwardRef
Ref forwarding是组件一个可选的特征。一个组件一旦有了这个特征,它就能接受上层组件传递下来的ref,然后顺势将它传递给自己的子组件。

为什么会有这个api呢?这个api主要作用就是通过父元素获取子元素的 Ref,可能是管理元素的聚焦,文本选择或者动画相关的操作。在HOC上,ref是无法通过props进行传递的,我们只可以采用折中的方案,但是这种这种方案,相当于从HOC上获取了这个接口,这从设计上是不优雅的,应该是透过HOC来获取到子组件。我们来看看在16.3之前我们实现方式,我们通过实现一个类中的方法,方法返回的就是这个Child实例,即input实例。

import React from 'react'
function HOCFc(WrappedComponent) {
    class paintRedComponent extends React.Component {
      constructor(props) {
        super(props);
        this.setWrappedInstance = this.setWrappedInstance.bind(this);
      }
      getWrappedInstance() { // 返回的即是WrappedComponent即input
        return this.wrappedInstance;
      }
      setWrappedInstance(ref) {
        this.wrappedInstance = ref;
      }
      render() {
        return <WrappedComponent ref={this.setWrappedInstance} {...this.props} />;
      }
    }
    return paintRedComponent;
}
class Child extends React.Component {
    inputRef = React.createRef()
    render() {
        return (
            <input type="text"
                defaultValue='高阶组件包裹'
                ref={ this.inputRef }
                style={{ color: this.props.color }}/>
        )
    }
}
const Parent = HOCFc(Child)
export default class ForwardRef extends React.Component {
    isFocus = false
    ref = React.createRef()
    inputNode = null
    componentDidMount() {
        console.log(this.ref, 'ref')
        this.inputNode = this.ref.inputRef.current
    }
    toggleFocus = () => {
        this.isFocus = !this.isFocus
        if (this.isFocus) {
            this.inputNode.focus()
        } else {
            this.inputNode.blur()
        }
    }
    render() {
        return (
            <React.Fragment>
                <Parent ref={(dom)=>{this.ref = dom.getWrappedInstance()}}/> 
                <button onClick={this.toggleFocus}>点我改变焦点</button>
            </React.Fragment>
        )
    }
}

然后我们将这部分代码,改为forwardRef形式:

import React from 'react'

const HOCFc = Component => React.forwardRef(
    (props, ref) => (
        <Component color='red' ref={ref} {...props}/>
    )
)
class Child extends React.Component {
    inputRef = React.createRef()
    render() {
        return (
            <input type="text"
                defaultValue='高阶组件包裹'
                ref={ this.inputRef }
                style={{ color: this.props.color }}/>
        )
    }
}

const Parent = HOCFc(Child)

export default class ForwardRef extends React.Component {
    isFocus = false
    ref = React.createRef()
    inputNode = null
    componentDidMount() {
        this.inputNode = this.ref.current.inputRef.current
    }
    toggleFocus = () => {
        this.isFocus = !this.isFocus
        if (this.isFocus) {
            this.inputNode.focus()
        } else {
            this.inputNode.blur()
        }
    }
    render() {
        return (
            <React.Fragment>
                <Parent ref={this.ref}/>
                <button onClick={this.toggleFocus}>点我改变焦点</button>
            </React.Fragment>
        )
    }
}
  • 注意,上面提到的第二个参数ref只有在你通过调用React.forwardRef()来定义组件的情况下才会存在。普通的function component和 class component是不会收到这个ref参数的。同时,ref也不是props的一个属性。

12. Pointer Events

Pointer Events API 是Hmtl5的事件规范之一,它主要目的是用来将鼠标(Mouse)、触摸(touch)和触控笔(pen)三种事件整合为统一的API.

在web页面复用在多种设备下的情况下, Pointer Events整合了mouse, touch, pen的触摸事件, 使其能在多种设备上触发,我们无需对各种类型的事件区分对待,更高的提高了开发效率。

新增:

13. fix getDerivedStateFromProps bug

React这次更新修复了getDerivedStateFromProps这个生命周期的触发节点, 在之前, 它触发的方式和旧生命周期getDerivedStateFromProps类似, 都是在被父组件re-render的时候才会触发,并且本组件的setState的调用也不会触发 这种方式在之前同步渲染的时候是没有问题的, 但是为了支持新的还未启用的fiber异步渲染机制, 现在, getDerivedStateFromProps在组件每一次render的时候都会触发,也就是说无论是来自父组件的re-render, 还是组件自身的setState, 都会触发getDerivedStateFromProps这个生命周期。
v 16.3:

v 16.4:

14. React Profiler 调试

React 16.5 添加了对新的 profiler DevTools 插件的支持。这个插件使用 React 的 Profiler 实验性 API 去收集所有 component 的渲染时间,目的是为了找出你的 React App 的性能瓶颈。它将会和我们即将发布的 时间片 特性完全兼容。

后续上实践图。。。。。

 

15. memo

memo的缩写是memoization即记忆化,刚开始不了解,专门看了下维基百科上的解释:

在react中React.memo是一个更高阶的组件。它类似于React.PureComponent功能组件而不是类。如果你的函数组件在给定相同的道具的情况下呈现相同的结果,你可以React.memo通过记忆结果将它包装在一些调用中以提高性能。这意味着React将跳过渲染组件,并重用最后渲染的结果。默认情况下,它只会浅显比较props对象中的复杂对象。如果要控制比较,还可以提供自定义比较函数作为第二个参数。此方法仅作为性能优化存在不要依赖它来“防止”渲染,因为这会导致错误。

组件仅在它的 props 发生改变的时候进行重新渲染。通常来说,在组件树中 React 组件,只要有变化就会走一遍渲染流程。但是通过 PureComponent 和 React.memo(),我们可以仅仅让某些组件进行渲染。由于只有需要被渲染的组件被渲染了,所以这是一个性能提升。PureComponent 要依靠 class 才能使用。而 React.memo() 可以和 functional component 一起使用。

function MyComponent(props) {
  /* render using props */
}
function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(MyComponent, areEqual);

实际意义是:函数式组件也有“shouldComponentUpdate”生命周期了注意,compare默认是shallowEqual,所以React.memo第二个参数compare实际含义是shouldNotComponentUpdate,而不是我们所熟知的相反的那个。

16. lazy与 suspense

lazy与suspense可以让我们在不依赖第三方库的情况下简化对延迟加载(lazy loading)的处理,这项新功能使得可以不借助任何附加库就能通过代码分割(code splitting)延迟加载 react 组件。延迟加载是一种优先渲染必须或重要的用户界面项目,而将不重要的项目悄然载入的技术。这项技术已经被完全整合进了 react 自身的核心库中。之前我们要用 react-loadable 达到这一目的,但现在用 react.lazy() 就行了。Suspense是一个延迟函数所必须的组件,通常用来包裹住延迟加载组件。多个延迟加载的组件可被包在一个 suspense 组件中。它也提供了一个 fallback 属性,用来在组件的延迟加载过程中显式某些 react 元素。

import React, { lazy, Suspense } from ‘react’;
import ReactDOM from ‘react-dom’;
import ‘./index.css’;
// import Artists from ‘./Artists’;
const Artists = lazy(() => import(‘./Artists’))
class App extends React.Component {
 render(){
  return(
   <div className=”App”>
    <Suspense fallback={<h1>Still Loading…</h1>}>
     <Artists />
    </Suspense>
   </div>
  );
 }
}
ReactDOM.render(<App />, document.getElementById(‘root’));

不足:暂不支持ssr

原文https://zhuanlan.zhihu.com/p/65268280 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值