美文网首页React
react16的新特性(一)

react16的新特性(一)

作者: Mr无愧于心 | 来源:发表于2019-06-12 18:34 被阅读0次

    主要包括以下特性的增加:错误边界、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>
        )
      }
    }
    

    相关文章

      网友评论

        本文标题:react16的新特性(一)

        本文链接:https://www.haomeiwen.com/subject/vxhkfctx.html