React16后一些重要的API和概念

662 阅读6分钟

1. 组合和继承

React 有十分强大的组合模式。React推荐我们使用组合而非继承来实现组件间的代码重用。 通过 JSX 嵌套,实现组件之间的组合。

// 渲染模板
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
// 传递数据
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

2. Context

Context 灵魂三拷问?

  • Context 是什么?
  • Context 使用的场景是什么?
  • Context 怎么用?

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

相关API:

  • React.createContext
  • Context.Provider
  • Class.contextType
  • Context.Consumer
  • Context.displayName
// App.js
import React from 'react';
import Parent from './Parent'
export const {Provider, Consumer} = React.createContext();
export function App() {
  return (
      <Provider value={'li'}>
        <Parent/>
      </Provider>
      );
}

// Parent.js
import React from 'react';
import Child from './Child'
class Parent extends React.Component {
    render() {
        return <Child/>
    }
}
export default Parent;

// Child.js
import React from 'react';
import {Consumer} from './App'
class Child extends React.Component {
    render() {
        return (
            <div>
                <Consumer>
                    {
                        theme => <div>{theme}</div>
                    }
                </Consumer>
            </div>
        )
    }
}
export default Child;
  • ContextType

Context会让组件变得不纯粹,因为依赖了全局变量。所以这决定了Context一般不会大规模的使用。所以一般在一个组件中使用一个Context就好。由于Consumer的特性,里面的代码必须是这个函数的返回值。这样就显得复杂与不优雅了。

  • 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。
import React from 'react'
import MyContext from './App.js'

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* 基于 MyContext 组件的值进行渲染 */
  }
}
MyClass.contextType = MyContext;
  • 如果你正在使用实验性的 public class fields 语法,你可以使用 static 这个类属性来初始化你的 contextType
import React, { Component, createContext } from 'react';

const BatteryContext = createContext();

//声明一个孙组件 ,使用context.type
class Leaf extends Component {
    static contextType = BatteryContext;
    render() {
        const battery = this.context;
        return<h1>Battery : {battery}</h1>
    }
}

//声明一个子组件
class Middle extends Component {
    render() {
        return <Leaf />
    }
}

class MyClass extends Component {
    state = {
        battery: 60,
    }
    render() {
        const { battery } = this.state;
        return (
            <BatteryContext.Provider value={battery}>
                <button
                    type="button"
                    onClick={() => this.setState({ battery: battery - 1 })}
                >
                    减减
                </button>
                <Middle />
            </BatteryContext.Provider>
        );
    }

}

注意事项:
Providervalue 状态提升到父节点的 state 里:防止当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。

3. Suspense

Suspense 使得组件可以“等待”某些操作结束后,再进行渲染。目前,Suspense 仅支持的使用场景是:通过 React.lazy 动态加载组件。它将在未来支持其它使用场景,如数据获取等。

  • React.lazy
  • React.Suspense

(3.1)、 Lazy

React.lazy函数用来加载动态引入的组件。

React.lazy 函数能让你像渲染常规组件一样处理动态引入(的组件)。
React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。

然后应在 Suspense 组件中渲染 lazy 组件,如此使得我们可以使用在等待加载 lazy 组件时做优雅降级(如 loading 指示器等)。 fallback 属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense 组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense 组件包裹多个懒加载组件。

// parent
import React, {Suspense} from "react";

const OtherComponent = React.lazy(() => {return import('./OtherComponent.js')});

export default function LazyDemo() {
    return (
        <div>
            <Suspense fallback={<div>Loading...</div>}>
                <OtherComponent/>
            </Suspense>
        </div>
    );
}

// child
import React from "react";

export default function OtherComponent() {
    return (
        <div>777</div>
    )
}

(3.2)、 Suspense

React 16.6添加了一个组件,允许您“等待”一些代码加载,

suspense是React 16.6添加了一个组件,此组件使得加载其他代码成为同步,并在等待时声明性地指定加载状态(例如spinner)。

4. memo

React.memo 为高阶组件,主要用于性能优化。它与 React.PureComponent 非常相似,但它适用于函数组件,但不适用于 class 组件。默认情况下其只会对复杂对象做浅层对比。

// 1. 现在有一个显示时间的组件,每一秒都会重新渲染一次
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>
        )
    }
    
// 2. 对于Child组件我们肯定不希望也跟着渲染,所有需要用到 PureComponent
class Child extends React.PureComponent {
    render(){
        console.log('I am rendering');
        return (
            <div>I am update every {this.props.seconds} seconds</div>
        )
    }
}

// 3. 现在新出了一个 React.memo() 可以满足创建纯函数 而不是一个类的需求
// 3.1 React.memo()
import React, { useState, useMemo, memo } from 'react'

export default function MemoCallback() {
    const [count, setCount] = useState(0);
    const double = useMemo(() => {
        return count * 2;
    }, [count === 5])

    const Counter = memo(function Child(props) {
        return (
            <span>子组件:{props.count}</span>
        )
    });

    return (
        <div>
            <button onClick={() => {setCount(count + 1)}}>click: {count}</button>
            {/*<span>double: {double}</span>*/}
            <Counter count={double}/>
        </div>
    )
}

// 3.2 React.memo()可接受2个参数,
// 第一个参数为纯函数的组件,第二个参数用于对比props控制是否刷新,与shouldComponentUpdate()功能类似。
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
    }else {
        return false
    }

}
export default React.memo(Child,areEqual)

// 3.3 React.memo() 与Redux
import React from "react";
function Child({seconds,state}){
    console.log('I am rendering');
    return (
      <div>
        <div>I am update every {seconds} seconds</div>
        <p>{state}</p>
      </div>
    )
};
const mapStateToProps = state => ({
    state: 'React.memo()用在connect()(内)'
});
export default connect(mapStateToProps)(React.memo(Child))

5. 在 React 中有两种流行的方式来共享组件之间的状态逻辑

  • 高阶组件
  • render props

6. React.PureComponent

React.PureComponentReact.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。

注意
React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层比较(也就是说,如果是引用类型的数据,只会比较是不是同一个地址,而不会比较具体这个地址存的数据是否完全一致)。如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。

// 1. shouldComponentUpdate 的 demo
class CounterButton extends React.Component {
    constructor(props) {
        super(props);
        this.state = {count: 1};
    }
    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.color !== nextProps.color) {
            return true;
        }
        if (this.state.count !== nextState.count) {
            return true;
        }
        return false;
    }
    render() {
        return (
            <button
                color={this.props.color}
                onClick={() => this.setState(state => ({count: state.count + 1}))}
            >
                Count: {this.state.count}
            </button>
        );
    }
}

// 2. React.PureComponent 的 demo
class ListOfWords extends React.PureComponent {
 render() {
     return <div>{this.props.words.join(',')}</div>;
 }
}
class WordAdder extends React.Component {
 constructor(props) {
     super(props);
     this.state = {
         words: ['marklar']
     };
     this.handleClick = this.handleClick.bind(this);
 }
 handleClick() {
     // This section is bad style and causes a bug
     const words = this.state.words;
     words.push('marklar');
     this.setState({words: words});
 }
 render() {
     return (
         <div>
             <button onClick={this.handleClick}>click</button>
             <ListOfWords words={this.state.words} />
         </div>
     );
 }
}

7. ref

ref的3中用法:

  • 字符串

dom节点上使用,通过this.refs[refName]来引用真实的dom节点

<input ref="inputRef" /> //this.refs['inputRef']来访问
  • 回调函数

React 支持给任意组件添加特殊属性。ref 属性接受一个回调函数,它在组件被加载或卸载时会立即执行。

当给 HTML 元素添加 ref 属性时,ref 回调接收了底层的 DOM 元素作为参数。 当给组件添加 ref 属性时,ref 回调接收当前组件实例作为参数。 当组件卸载的时候,会传入null ref 回调会在componentDidMount 或 componentDidUpdate 这些生命周期回调之前执行。

<input ref={(input) => {this.textInput = input;}} type="text" />   //HTML 元素添加 ref 属性时
<CustomInput ref={(input) => {this.textInput = input;}} />   //组件添加 ref 属性
  • React.createRef()

在React 16.3版本后,使用此方法来创建ref。将其赋值给一个变量,通过ref挂载在dom节点或组件上,该ref的current属性 将能拿到dom节点或组件的实例

class Child extends React.Component{
    constructor(props){
        super(props);
        this.myRef=React.createRef();
    }
    componentDidMount(){
        console.log(this.myRef.current);
    }
    render(){
        return <input ref={this.myRef}/>
    }
}