react16的新特性(一)

主要包括以下特性的增加:错误边界、render方法新增返回类型、Fragment、createContext、createRef、forwardRef、生命周期函数的更新、lazy、suspense、Portals、memo、Strict Mode

错误边界 Error Boundary

错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。

//最佳实践:将ErrorBoundary抽象为一个公用的组件类
import React ,{Component} from 'react';
export default ErrorBoundary extends Component{
  constructor(props){
    super(props);
    this.state={hasError:false};
  }
  componentDidCatch(err,info){
    //如果调用了这个函数就说明子组件抛出错误了,改变状态,渲染备用的组件
    this.setState({hasError:true});//修改状态,通知出错了
    console.log(err,info);//打印错误信息
  }
  render(){
    if(this.state.hasError){
      return <div>throw error</div>
    }
    return this.props.children
  }
}

使用错误边界的组件

import React ,{Component} from 'react';
export default class App extends Component{
  constructor(){
    this.state={user:'aa'}
  }
  changeState=()=>{
    this.setState({
      user:null
    })
  }
  render(){
    return <div>
                <ErrorBoundary>
                    <CouldThrowErrComponent user={this.state.user} />
                </ErrorBoundary>
                <button onClick={this.changeState}>Update</button>
           </div>
  }
}
//可能出错的子组件
export default class CouldThrowErrComponent extends Component{
  constructor(props){
    super(props);
  }
  componentDidUpdate(){
    return throw new Error('抛错')
  }
  render(){
    let {user} =this.props;
    return <div>{user}</div>
  }
}

  • 错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
  • 错误边界仅可以捕获其子组件的错误
  • 无法捕捉 1. 异步的抛错(事件,计时器等)、2。服务端渲染、3.自身抛错
  • 一般使用static getDerivedStateFromError() 来渲染一个提示错误的UI,使用componentDidCatch() 来记录一些error的详细信息,错误调用栈等等

render方法新增返回类型

render方法支持直接返回string,number,boolean,null,portal,以及fragments(带有key属性的数组),这可以在一定程度上减少页面的DOM层级

//string
render(){
    return 'hello,world'
}

//number
render(){
    return 12345
}

//boolean
render(){
    return isTrue?true:false
}

//null
render(){
    return null
}

//fragments,未加key标识符,控制台会出现warning
render(){
    return [
        <div>hello</div>,
        <span>world</span>,
        <p>oh</p>
    ]
}

Fragment

Fragment 组件其作用是可以将一些子元素添加到 DOM tree 上且不需要为这些元素提供额外的父节点,相当于 render 返回数组元素。

render() {
  return (
    <Fragment>
      Some text.
      <h2>A heading</h2>
      More text.
      <h2>Another heading</h2>
      Even more text.
    </Fragment>
  );
}

如果使用数组需要加key,而且更麻烦

render() {
  return (
      [
      "Some text.",
      <h2 key="1">A heading</h2>,
      "More text.",
      <h2 key="2">Another heading</h2>,
      "Even more text."
      ]
  );
}

createContext / Provider / contextType / Consumer

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。顶层的Provider中传入value,在子孙级的Consumer中获取该值,并且能够传递函数,用来修改context

  • React.createContext 是一个函数,它接收初始值并返回带有 Provider 和 Consumer 组件的对象;
  • Provider 组件是数据的发布方,一般在组件树的上层并接收一个数据的初始值;
  • Consumer 组件是数据的订阅方,它的 props.children 是一个函数,接收被发布的数据,并且返回 React Element;
    以前跨组件传递数据都是通过逐层传递
class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
  // 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
  // 因为必须将这个值层层传递所有组件。
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

使用context,避免中间元素传递props

 const ThemeContext = React.createContext(
  "dark" // 默认值
);
class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}
// 类组件一般使用添加属性的方式接受数据
class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

函数式组件一般使用Consumer接收数据

//创建Context组件
const ThemeContext = React.createContext({
  theme: 'dark',
  toggle: () => {}, //向上下文设定一个回调方法
});

//运行APP
class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggle = () => { //设定toggle方法,会作为context参数传递
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    this.state = {
      theme: themes.light,
      toggle: this.toggle,
    };
  }

  render() {
    return (
      <ThemeContext.Provider value={this.state}> //state包含了toggle方法
        <Content />
      </ThemeContext.Provider>
    );
  }
}

//中间组件
function Content() {
  return (
    <div>
      <Button />
    </div>
  );
}

//接收组件
function Button() {
  return (
    <ThemeContext.Consumer>
      {({theme, toggle}) => (
        <button
          onClick={toggle} //调用回调
          style={{backgroundColor: theme}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

createRef / forwardRef

通过ref只能拿到标签元素或者通过class创建的react元素,不能拿到通过函数创建的react元素
适用于表单获取、元素动画等场景

class Child extends React.Component{
  constructor(props){
    super(props);
    this.myRef=React.createRef();//创建ref对象
  }
  componentDidMount(){
    console.log(this.myRef.current);//通过current拿到ref元素input标签
  }
  render(){
    return <input ref={this.myRef}/>//挂载ref绑定的元素
  }
}

React.forwardRef 是 Ref 的转发, 它能够让父组件访问到子组件的 Ref,从而操作子组件的 DOM。

//把FancyButton上的ref转发到button上
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));
class App extends from Component{
  constructor(props){
    super(props);
   // 你可以直接获取 DOM button 的 ref:
    this.myRef = React.createRef();
  }
   componentDidMount(){
    console.log(this.myRef.current)//拿到button 
   }
  render(){
    return <div>
              <FancyButton ref={this.myRef}>Click me!</FancyButton>
          </div>
  }
}

回调 Refs

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = null;
    this.setTextInputRef = element => {
      this.textInput = element;
    };
    this.focusTextInput = () => {
      // 使用原生 DOM API 使 text 输入框获得焦点
      if (this.textInput) this.textInput.focus();
    };
  }
  componentDidMount() {
    // 组件挂载后,让文本框自动获得焦点
    this.focusTextInput();
  }
  render() {
    // 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
    // 实例上(比如 this.textInput)
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

生命周期函数的更新

  • React 引入了 getDerivedStateFromProps 、 getSnapshotBeforeUpdate 及 componentDidCatch 等三个全新的生命周期函数。
  • 将 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 标记为不安全的方法。
getDerivedStateFromProps(nextProps, prevState) 
//不仅在 props 更新时会被调用,setState 时也会被触发

getSnapshotBeforeUpdate(prevProps, prevState)
//会在组件更新之前获取一个快照,
//并可以将计算得的值或从 DOM 得到的信息传递到 componentDidUpdate(prevProps, prevState, snapshot) 函数的第三个参数
//常常用于 scroll 位置定位等场景。

componentDidCatch() 
//函数让开发者可以自主处理错误信息,诸如错误展示,上报错误等

lazy/suspense

React.lazy() 提供了动态 import 组件的能力,实现代码分割。
Suspense 作用是在等待组件时 suspend(暂停)渲染,并显示加载标识。

import React, { Component, lazy, Suspense } from 'react';
const LazyTest1 = lazy(() => import('./components/LazyTest.1'));
const LazyTest2 = lazy(() => import('./components/LazyTest.2'));

class App extends Component {
  fallback = () =>{
    return (
      <div>Loading...</div>
    );
  }
  render() {
    return (
      <div>
        <Suspense fallback={this.fallback()}>
          <h1>懒加载组件</h1>
          <LazyTest1 />
          <LazyTest2 />
        </Suspense>
      </div>
    );
  }
}

Suspense 可以放在懒加载的组件外层的任意位置,fallback 是懒加载组件载入过程中的一个过渡,可以放一些过渡效果或方法。

Portals

使用createPortal将组件渲染到当前组件树之外,我们可以将组件渲染到我们想要的任意DOM节点中,但该组件依然处在React的父组件之内。使用于弹窗、浮层类的组件

//实现一个简易蒙层效果,抽象出一个通用的Overlay组件
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

export default class Overlay extends Component {
    constructor(props) {
        super(props);
        this.container = document.createElement('div');
        document.body.appendChild(this.container);
    }
    componentWillUnmount() {
        document.body.removeChild(this.container);
    }
    render() {
        return ReactDOM.createPortal(//将组件放到了创建的container上
            <div className='overlay'>
                <span className='overlay-close' onClick={this.props.onClose}>&times;</span>
                {this.props.children}
            </div>,
            this.container
        )
    }
}
//该组件对应的样式如下
.overlay{
    box-sizing:border-box;
    position: fixed;
    top:50%;
    left:50%;
    width:260px;
    height:200px;
    margin-left:-130px;
    margin-top:-100px;
    padding:10px;
    background-color: #fff;
    outline: rgba(0,0,0,.5) solid 9999px;
}
.overlay-close{
    position: absolute;
    top:10px;
    right:10px;
    color:red;
    cursor: pointer;
}

使用方式

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      overlayActive: false
    }
    this.closeOverlay = this.closeOverlay.bind(this);
    this.showOverlay = this.showOverlay.bind(this);
  }
  closeOverlay() {
    this.setState({ overlayActive: false })
  }
  showOverlay() {
    this.setState({ overlayActive: true })
  }
  render() {
    return (
      <div className="App">
        <div>hello world!</div>
        {this.state.overlayActive &&
          <Overlay onClose={this.closeOverlay}>overlay content</Overlay>}
        <button onClick={this.showOverlay}>show</button>
      </div>
    );
  }
}

memo / PureComponent(类似vue的keepAlive)

React.memo()是一个高阶函数,它与 React.PureComponent类似,但是一个函数组件而非一个类。

情景:

import React  from 'react';

export default class extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            date : new Date()
        }
    }

    componentDidMount(){
        setInterval(()=>{
            this.setState({
                date:new Date()
            })
        },1000)
    }
    render(){
        return (
            <div>
                <Child seconds={1}/>
                <div>{this.state.date.toString()}</div>
            </div>
        )
    }
}

现在有一个显示时间的组件,每一秒都会重新渲染一次,对于Child组件我们肯定不希望也跟着渲染,所有需要用到PureComponent

class Child extends React.PureComponent {
    render(){
        console.log('I am rendering');
        return (
            <div>I am update every {this.props.seconds} seconds</div>
        )
    }
}

现在新出了一个React.memo()可以满足创建纯函数而不是一个类的需求

function Child({seconds}){
    console.log('I am rendering');
    return (
        <div>I am update every {seconds} seconds</div>
    )
};
export default React.memo(Child)

React.memo()可接受2个参数,第一个参数为纯函数的组件,第二个参数用于对比props控制是否刷新,与shouldComponentUpdate()功能类似。
此方法仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。

import React from "react";

function Child({seconds}){
    console.log('I am rendering');
    return (
        <div>I am update every {seconds} seconds</div>
    )
};
function areEqual(prevProps, nextProps) {
    if(prevProps.seconds===nextProps.seconds){
        return true//返回true就不刷新
    }else {
        return false//返回false就刷新
    }
}
export default React.memo(Child,areEqual)

Strict Mode

启用严格模式下:

  • 识别被标志位不安全的生命周期函数
  • 对弃用的 API 进行警告
  • 探测某些产生副作用的方法
  • 检测是否使用 findDOMNode
  • 检测是否采用了老的 Context API
class App extends React.Component {
  render() {
    return (
      <div>
        <React.StrictMode>
          <ComponentA />
        </React.StrictMode>
      </div>
    )
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • 1、React新特性介绍 React v16.0 render 支持返回数组和字符串、Error Boundari...
    做最棒的阅读 4,719评论 0 4
  • 更多技术文章,可以浏览我的github地址,https://github.com/HuYuee/blog 原英文链...
    古朋阅读 1,385评论 0 5
  • 生命周期 其中标红的( componentWillMount、 componentWillReceiveProps...
    jy789阅读 542评论 0 0
  • HTML模版 之后出现的React代码嵌套入模版中。 1. Hello world 这段代码将一个一级标题插入到指...
    ryanho84阅读 6,225评论 0 9
  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,433评论 1 33