React 新版本特性说明

58 篇文章 0 订阅
9 篇文章 0 订阅

1.React 16版本以前渲染一个组件最外层有时要使用到一个无意义的<div>元素作为包裹元素,否则会报错;React 16.2版本以后,新增了react.fragment API,不需要外层有包裹元素时,可以使用<></>或者<Fragment></Fragment>(需要导入import React, { Fragment } from 'react'

jsx
  1. 注释
    // 注释一,推荐
    {/* xxx */}
    // 注释二
    {
    	// xxx
    }
    
  2. 语法
    1. label中的for -> htmlFor
    2. 类名class -> className
    
react vscode 插件
  1. simple react snippets代码补全插件
react 子组件对父组件的传值进行类型校验

PropTypes

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Test extends Component {

}

Test.propTypes = {
	index: PropTypes.number,
	addr: PropTypes.string,
	addNum: PropTypes.func,
}

export default Test;
componentDidUpdate(preProps, preState, snapshot)
  1. componentDidUpdate(preProps, preState, snapshot)在更新后会立即调用,首次不会调用;第三个参数在组建中使用了getSnapshotBeforeUpdate(),则snapshot值为该函数的返回值,否则为undefined
  2. demo:
    import React, { Suspense } from 'react';
    import ReactDOM from 'react-dom';
    
    const LazyComponent = React.lazy(() => import('./components/Lazy'));
    
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          greet: 'hello, React 16'
        }
        this.changeState = this.changeState.bind(this);
      }
      getSnapshotBeforeUpdate(prevProps, prevState) {
        return 1;
      }
      componentDidUpdate(preProps, preState, snapshot) {
        console.log('preProps: ', preProps);		// preProps: {}
        console.log('preState: ', preState);		// preState: {greet: "hello, React 16"} 
        console.log('snapshot: ', snapshot);		// 1
      }
      changeState() {
        this.setState({
          greet: 'Changed'
        })
      }
      render() {
        return (
          <Suspense fallback={<div>loading...</div>}>
              <LazyComponent greet={this.state.greet} />
              <button onClick={this.changeState}>change</button>
          </Suspense>
        )
      }
    }
    
    ReactDOM.render(<App/>, document.getElementById('root'))
    
    
React生命周期

在这里插入图片描述

shouldComponentUpdate(nextProps, nextState) {
	if (nextProps.input !== this.props.input) {
		return true;
	} else {
		return false;
	}
}

在这里插入图片描述

  1. 在React16版本中,以下方法被标记为不安全方法,应该避免使用:
    1. UNSAFE_componentWillMount()
    2. UNSAFE_componentWillUpdate()
    3. UNSAFE_componentWillReceiveProps()
  2. React16版本新增的生命周期函数:
    1. static getDerivedStateFromProps() —— 仅用于组件在props变化时更新state;
    2. getSnapshotBeforeUpdate()
    3. static getDerivedStateFromError()
    4. componentDidCatch()
  3. 挂载时生命周期函数调用顺序:
    1. constructor()
    2. static getDerivedStateFromProps()
    3. render()
    4. componentDidMount()
  4. 更新时生命周期函数调用顺序:
    1. static getDerivedStateFromProps()
    2. shouldComponentUpdate()
    3. render()
    4. getSnapShotBeforeUpdate()
    5. componentDidUpdate()
  5. 卸载时
    1. componentWillUnmount()
  6. 错误处理
    1. static getDerivedStateFromError()
    2. componentDidCatch()
  7. constructor()使用:
    不初始化state或者进行方法绑定时,不需要为组件实现构造函数;(不调用时默认会被加一个空的constructor
  8. super使用
    构造函数中的操作不应该具有副作用;
    super仅能用于以下两种方式:
    1. 函数调用super(),通常用于构造函数中;
    2. 访问父类原型链属性或方法:super.propsuper[prop]
      在这里插入图片描述
class Parent {
    constructor() {
        this.name = 'fn';
    }
    getName() {
        return this.name;
    }
}
Parent.prototype.className = 'Parent';

class Child extends Parent {
    constructor(props) {
        super(props);
    }
    getName() {
        return super.getName() + ' from child.'
    }
	getParent() {
		return super.className;
	}
}

let child = new Child();
console.log(child.getName());		// 'fn from child.'
console.log(child.getParent());		// 'Parent'
React.lazy与Suspense

React.lazy()提供了动态加载组件的能力;Suspense是react内置的组件,用于懒加载组件时显示一些站位信息或提示信息(功能类似于loading过渡文字或动画);

// Lazy.js
import React from 'react';

export default props => (
    <div>{ props.greet }</div>
)


// index.js
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';

const LazyComponent = React.lazy(() => import('./components/Lazy'));

const App = () => {
  return (
    <Suspense fallback={<div>loading...</div>}>
        <LazyComponent greet="hello, react16" />
    </Suspense>
  )
}

ReactDOM.render(<App/>, document.getElementById('root'))

在这里插入图片描述

Refs转发

refs转发允许某些组件接收ref,并允许其向下传递ref;根据官方文档,主要应用包括:

  1. 转发 refs 到 DOM 组件
  2. 在高阶组件中转发 refs
  3. 在 DevTools 中显示自定义名称
/**
* 转发 refs 到 DOM 组件
**/

// RefTrans.js
import React from 'react';

export default React.forwardRef((props, ref) => (
    <button className="fancy-button" ref={ref}>
        {props.children}
    </button>
))


// index.js
import React from 'react';
import FancyBtn from './components/RefTrans';

class App extends React.Component {
	constructor(props) {
		super(props);
		this.ref = React.createRef();
	}
	componentDidMount() {
		console.log('ref: ', this.ref.current)
	}
	render() {
		return (
			<FancyBtn ref={this.ref}>Ref转发</FancyBtn>
		)
	}
}

HOC

  1. 定义:HOC(High-Order Component)即高阶组件,实质是一个函数,参数和返回值均为组件;个人感觉HOC在某些方面和函数柯里化有点像,某种程度上可以用于组件定制化。
  2. 常见的高阶组件:react-redux中的connect是一个高阶函数,内部返回一个高阶组件;
  3. 用途:属性代理、反响继承、渲染劫持等
  4. demo:
    class Btn extends React.Component {
      render() {
        return <button style={{color: this.props.color}}>{this.props.desc}</button>
      }
    }
    
    const customizedBtn = (WrappedComponent, config) => 
      class CustomizedBtn extends React.Component {
        render() {
          let props = {
            ...this.props,
            color: config.color
          }
          return <WrappedComponent {...props} />
        }
      }
    
    // 仿react-redux中的connect函数
    const customizedBtn2 = (config) => (WrappedComponent) => 
      class CustomizedBtn extends React.Component {
        render() {
          let props = {
            ...this.props,
            color: config.color
          }
          return <WrappedComponent {...props} />
        }
      }
    
    const RedPrimaryBtn = customizedBtn(Btn, {color: 'red'});
    const BlackSheepBtn = customizedBtn2({color: 'blue'})(Btn)
    
    在这里插入图片描述
Portals
  1. 意义:Portals允许你将一个子组件渲染到父组件之外的DOM节点上;比如一些弹窗等;
  2. 用法:ReactDOM.createPortal(child, container)——child为需要渲染到外部的子元素(包括react元素、数组或fragments、字符串或数值、布尔类型或null);container为DOM元素;
  3. demo:
    // MyPortal.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    
    export default class extends React.Component {
        constructor(props) {
            super(props);
            this.el = document.createElement('div');
        }
        componentDidMount() {
            const portalRoot = document.getElementById('portal')
            portalRoot.appendChild(this.el);
        }
        render() {
            return ReactDOM.createPortal(
                this.props.children,
                this.el
            )
        }
    }
    
    // index.js
    render() {
    	<>
        	<div id="portal"></div>
        	<MyPortal>
          		<span>myportal</span>
        	</MyPortal>
    	</>
    }
    
easy mock网站模拟接口数据,在无后端支持时独立开发,无缝衔接
react-transition-group动画库

包含三个模块:Transition、CSSTransition、TransitionGroup

react hooks

从React 16.8版本开始,新增的React hooks可以让用户在不用创建react class的同时可以使用state等react属性;

React框架分层

  1. Virtual DOM层:构建虚拟DOM树,描述页面长什么样

  2. Reconciler层:负责调用组件生命周期方法,计算DOM diff等;

    Reconciliation:
    The algorithm React uses to diff one tree with another to determine which parts need to be changed

    翻译:Reconciliation是React一种用于计算两个dom tree之间差异并决定哪些部分需要更新的算法
    Reconciliation的核心是:

    • 不同类型的组件会生成不同的DOM Tree,React不会尝试对它们做diff,而是使用新的Dom Tree替换旧的;
    • 列表的diff依赖于key值,key的取值应该是稳定、唯一并可预测的;
  3. Renderer层:负责根据虚拟DOM树和diff patch渲染对应页面;

React Fiber

React Fiber是React 16提出的新概念,是React重构的核心,主要是对Reconciler层的重写(Stack Reconciler ⇒ Fiber Reconciler);

Fiber Reconciler包括两个阶段:render phasecommit phase

  1. render phase可以异步执行,调用的生命周期函数列表:

    	[UNSAFE_]componentWillMount (deprecated)
    	[UNSAFE_]componentWillReceiveProps (deprecated)
    	getDerivedStateFromProps
    	shouldComponentUpdate
    	[UNSAFE_]componentWillUpdate (deprecated)
    	render
    
  2. commit phase通常是同步执行(因为该步骤涉及到真实DOM的更新及UI展现),调用的生命周期函数:

    getSnapshotBeforeUpdate
    componentDidMount
    componentDidUpdate
    compoentWillUnmount
    
    

React 15及以前的版本,在渲染大型组件时,由于其Reconciler层和Renderer层的执行是连续不可间断的;如果遇到大型组件;组件的执行时间有时会非常长,可能会阻塞动画渲染、用户交互等高实时性的任务,出现掉帧等,影响了用户体验;

我们知道,页面的显示其实就是通过一系列数据渲染得到视图,实质就是页面渲染相关的函数调用;
UI = render(data)

React 15及以前的版本,Reconciler的实现是基于内置的调用栈的同步递归模型去遍历Dom Tree;每次更新需要栈中函数全部调用结束,即栈空为止;这个过程是不被打断的,一次可能执行很长时间,阻塞页面渲染,所以会导致掉帧等;

由于调用栈进行视图更新的过程是连续的,为了把React渲染任务切分为增量单元分段执行,React Fiber针对React组件对调用栈进行了部分重写;使其变为一个链表结构;

特点

React Fiber引入的目的是为了增强动画、布局等实时性要求高的任务的稳定性;

  1. 主要特点是增量渲染(incremental rendering)—— 即可以把渲染工作进行切片,并在多帧内的空闲时间完成渲染工作;
  2. 为不同的更新任务分配不同的优先级,根据优先级的高低优先执行;
  3. 任务的暂停与恢复;
  4. 重用已完成的任务;
  5. 终止不再需要的任务;
  6. 当新的任务进来时,旧的任务可以被暂停、终止或重用;如果新任务优先级更高,则挂起旧任务并优先执行新任务;
Fiber node structure

React Fiber的每个节点都是一个对象fiber,其数据结构表示为:

class Node {
    constructor(instance) {
        this.instance = instance;
        this.child = null;
        this.sibling = null;
        this.return = null;
    }
}

区别于React Fiber,fiber是一个组件输入/输出信息的对象,可以看作一个虚拟的stack frame;每个React元素都有一个对应的fiber,它包含以下属性:

  • typekey:用途与组件类似,

    Conceptually, the type is the function (as in v = f(d)) whose execution is being tracked by the stack frame.
    Along with the type, the key is used during reconciliation to determine whether the fiber can be reused.
    翻译: 通常,type是一个函数,该函数的执行用于追踪栈帧;key通常用于在reconciliation阶段判断该fiber是否可以重用;

  • childsibling:用于指向其他fiberschild通常是组件render方法的返回值;sibling是render方法返回的顶层元素(数组形式,此时child为数组首元素;Fiber支持render函数中返回多个元素)

    function Parent() {
      return [<Child1 />, <Child2 />]
    }
    // child是Child1, siblings是Child1和Child2
    
  • return:reference to the parent

  • output:每一个fiber最终都有output,但是output只在一些Host Components(DOM节点)中产生;这些输出最终给Renderer用于应用更新;

举例说明,定义一个组件ClickCounter

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

    handleClick() {
        this.setState((state) => {
            return {count: state.count + 1};
        });
    }


    render() {
        return [
            <button key="1" onClick={this.handleClick}>Update counter</button>,
            <span key="2">{this.state.count}</span>
        ]
    }
}

这里,ClickCounter组件对应的fiber node的数据结构:

{
    stateNode: new ClickCounter,
    type: ClickCounter,
    alternate: null,
    key: null,
    updateQueue: null,
    memoizedState: {count: 0},
    pendingProps: {},
    memoizedProps: {},
    tag: 1,
    effectTag: 0,
    nextEffect: null
}

spanDOM节点

{
    stateNode: new HTMLSpanElement,
    type: "span",
    alternate: null,
    key: "2",
    updateQueue: null,
    memoizedState: null,
    pendingProps: {children: 0},
    memoizedProps: {children: 0},
    tag: 5,
    effectTag: 0,
    nextEffect: null
}

React Fiber基于新的浏览器API—— requestIdleCallbackrequestAnimationFrame进行实现;
requestIdleCallback调度一个低优先级的函数在浏览器空闲时间执行;
requestAnimationFrame调度一个高优先级的函数在下一个动画帧中执行;


参考文献

  1. react-fiber-architecture
  2. Reconciliation
  3. react basic
  4. Design Principles
  5. fiber-reconciler
  6. The how and why on React’s usage of linked list in Fiber to walk the component’s tree
  7. React生命周期速查表
  8. React.Components
  9. Refs转发
  10. 可渲染的React元素
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neil-

你们的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值