Skip to content

严格模式

严格模式和PropTypes类型检查

StrictMode是一个用来突出显示应用程序中潜在问题的工具。和Fragment一样,StrictMode不会渲染任何可见的UI。他为其后台元素触发额外的检查和警告。严格模式检查仅在开发模式下运行;它们不会影响生产构建。

启用严格模式,例如:

import React from 'react';
function ExampleApplication() {
  return (
    <div>
      <Header  />
      <React.StrictMode>
        <div>
          <ComponentOne  />
          <ComponentTwo  />
        </div>
      </React.StrictMode>
      <Footer  />
    </div>
  );
}

在上述的示例中,不会对 Header 和 Footer 组件运行严格模式检查。但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查。 StrictMode针对React进行检查:

  • 识别不安全的生命周期
  • 关于使用过时的字符串ref API的警告
  • 关于使用废弃的findDOMNode方法的警告
  • 检查意外的副作用
  • 检查过时的context API

识别不安全的生命周期

这里的"不安全"不是指真正意义上的不安全,而是指经常被误解和滥用,并且在异步渲染中,潜在误用的可能性更大,所以在后续React版本中会被加上UNSAFE_前缀的API。而使用这些生命周期的代码在 React 的未来版本中更有可能出现 bug,尤其是在启用异步渲染之后。所以称之为不安全的生命周期:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

过时的ref API

以前,React提供了两种管理refs的方式:

  1. 字符串Ref API
  2. 回调函数API
  3. React.createRef

比较:

  • string ref 不可组合(React.createRef也不行):即子组件内部使用过了ref属性绑定过了一个由父组件传进来的组件,父组件里对这个组件的绑定的ref就失效了。
class Parent extends React.Component {
    componentDidMount () {
        console.log(this.refs)
    }
    render () {
        const { children } = this.props;
        return <div>
            {children.map((child,index)=>React.cloneElement(child,{
                ref:"childref"+index,
                key:index
            }))}
        </div>

    }
}

class App extends React.Component {
    componentDidMount () {
        console.log(this.Divref)
        console.log(this.refs)
    }
    render () {
        this.Divref = React.createRef();
        return <Parent >
            <div ref="appdiv">app</div>
            <div ref={this.Divref}>app</div>
        </Parent>
    }
}

如上,App组件中打印的都是空,而在Parent中能打印出来。

  • string ref的所有者,由当前执行的组件决定,而不是设置的组件。例如在render props中。
class Parent extends React.Component {
    componentDidMount () {
        console.log(this.refs)
        console.log(this.Divref)
    }
    render () {
        const { render } = this.props;

        return <div>
            {render()}
        </div>

    }
}

class App extends React.Component {
    constructor(props){
        super(props)
        this.renderProp = this.renderProp.bind(this)
    }
    componentDidMount () {
        console.log('-------------- app componentDidMount --------------')
        console.log(this.Divref)
        console.log(this.refs)
    }
    renderProp () {
        this.Divref = React.createRef();
        return <React.Fragment>
            <div ref="appdiv">app1</div>
            <div ref={this.Divref}>app2</div>
        </React.Fragment>
    }
    render () {
        return <Parent render={this.renderProp} >
        </Parent>
    }
}

输出:

{appdiv: div}
undefined
-------------- app componentDidMount --------------
{current: div}
{}
  1. string ref 不适用于Flow之类的静态分析。Flow不能猜测框架可以使字符串ref"出现"在react上的神奇效果,以及它的类型(可能有所不同)。回调引用比静态分析更友好。
  2. string ref 强制React跟踪当前正在执行的组件。这是有问题的,因为它使react模块处于有状态,并在捆绑中复制react模块时导致奇怪的错误。在 reconciliation 阶段,React Element 创建和更新的过程中,ref 会被封装为一个闭包函数,等待 commit 阶段被执行,这会对 React 的性能产生一些影响。
  3. 以上问题使用回调函数形式会更好。

这也是为什么字符串ref称为过时的原因。

关于使用废弃的 findDOMNode 方法的警告

React支持用findDOMNode来在给定class实例的情况下,在树中搜索DOM节点。通常不需要这么做,因为可以使用ref绑定DOM节点。 findDOMNode也可用于class组件,但它违反了抽象原则,他是的父组件需要单独渲染子组件。会产生重构危险,你不能更改组件的实现细节,因为父组件可能正在访问DOM节点。findDOMNode只返回仪狄格子节点,但是使用Fragments,组件可以渲染多个DOM节点。 findDOMNode是一个只读一次的api。调用该方法只会返回第一次查询的结果。如果子组件渲染了不同的节点,则无法跟踪此更改。因此findDOMNode仅在组件返回单个且不可变的DOM节点时才有效。 所以,使用ref来代替这个api更好一些。

检查意外的副作用

从概念上讲,React分两个阶段工作:

  • 渲染阶段会确定需要进行那些更改,比如DOM。在此阶段,React调用render,然后将结果与上次渲染结果进行比较。
  • 提交阶段发生在当React应用变化时。(对于 React DOM 来说,会发生在React插入,更新及删除DOM节点的时候。)在此阶段,React还会调用componentDidMountcomponentDidUpdate之类的生命周期方法。 提交阶段通常会很快,但渲染过程可能很慢。因此,即将推出的concurrent模式(默认情况下未启用)将渲染工作分解为多个部分,对任务进行暂停和恢复操作以避免阻塞浏览器。这意味React可以在提交之前多次调用渲染阶段生命周期方法。或者在不提交的情况下调用它们(由于出现错误或更高优先级的任务使其中断)。 渲染阶段的生命周期包括class组件的以下方法:
  • constructor
  • componentWillMount(or UNSAFE_componentWillMount)
  • componentWillReceiveProps(or UNSAFE_componentWillReceiveProps)
  • componentWillUpdate(or UNSAFE_componentWillUpdate)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState

因为上述方法能会被多次调用,所以不要在他们内部编写副作用相关的代码,这个很重要。忽略此规则可能会导致各种问题的产生,包括内存泄漏和出现无限的应用程序状态。并且这些很问题很难发现,因为它们通常具有非确定性。 严格模式不能自动检测到你的副作用,但他可以帮助你发现它们,使她们更具有确定性。

检查过时的context API

过时的context API 容易出错,将在未来的主要版本中删除。在所有16.x版本中仍然有效,但在严格模式下,将显示警告。

使用PropTypes进行类型检查

随着你的应用程序不断增长,你可以通过类型检查捕获大量错误。对于某些应用程序来说,你可以使用Flow或者TypeScript等javascript扩展来对整个应用程序做类型检查。但是即使你使用这些扩展来对整个应用程序做类型检查。但即使你不使用这些扩展。React也内置了一些类型检查功能。要在组件的props上进行类型检查,你只需要配置特定的propTypes属性:

import PropTypes from 'prop-types'
class Greeting extends React.Component{
    render(){
        return (
            <h1>hello,{this.props.name}</h1>
        )
    }
}
Greeting.propTypes = {
    name: PropTypes.string
}

PropTypes提供了一系列验证器,可用于确保组件接收到的数据类型是有效的。在本例中,我们使用了PropTypes.string。当传入的prop值类型不正确时,Javascript控制台将会显示警告。出于性能方面的考虑,propTypes只在开发模式下进行检查。

PropTypes

不同验证器的例子:

MyComponent.propTypes = {
    // 你可以将属性声明为js的原生类型
    optionalArray: PropTypes.array,
    optionalBool: PropTypes.bool,
    optionalFunc: PropTypes.func,
    optionalNumber: PropTypes.number,
    optionalObject: PropTypes.object,
    optionalString: PropTypes.string,
    optionalSymbol: PropTypes.symbol,
    // 任何可以被渲染的元素 (包括数字 字符串 元素 和数组)
    optionalNode:PropTypes.node,
    // React 元素
    optionalElement:PropTypes.element ,
    // 一个React元素的类型(即MyComponent)
    optionalElementType: PropTypes.elementType,
    // 你也可以申明prop为类的实例,这里使用
    // JS的instanceof操作符
    optionalMessage:PropTypes.instanceof(Message),
    // 你可以让prop只能是指定的值,指定他为枚举类型
    optionalEnum:PropTypes.oneOf(['new','Photos']),
    // 一个对象可以是几种 类型中的任意一个类型
    optionalUnion:PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.number,
        PropTypes.instanceof(Message)
    ])
    // 可以指定一个数组由某一类型的元素组成
    optionalArrayOf:PropTypes.arrayOf(PropTypes.number)
    // 可以只顶某一个对象由某一类型的值组成 必须有
    optionalObjectOf:PropTypes.objectOf(PropTypes.number)
    // 可以指定一个对象由特定的类型值组成
    optionalObjectWithShape:PropTypes.shape({
        color:PropTypes.string,
        fontSize:PropTypes.number
    })
    // 指定类型为对象,且可以指定对象的哪些属性必须有,哪些属性可以没有。如果出现没有定义的属性,会出现警告。
    //下面的代码optionalObjectWithStrictShape的属性值为对象,但是对象的属性最多有两个,optionalProperty 和 requiredProperty。
    //出现第三个属性,控制台出现警告。
    optionalObjectWithStrictShape: PropTypes.exact({
        optionalProperty: PropTypes.string,
        requiredProperty: PropTypes.number.isRequired
    })
    // 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
    // 这个 prop 没有被提供时,会打印警告信息。
    requiredFunc: PropTypes.func.isRequired, // fnc 必穿
    // 任意类型的数据
    requiredAny: PropTypes.any.isRequired,
    // 你可以指定一个自定义验证器。它验证失败的时候返回一个`Error`对象
    // 请不要使用`console.warn`或抛出异常。因为这在`onOfType`中不起作用。

    customProp:function(props,propName,componentName){
        if(!/matchme/.test(props[propName])){
            return new Error(
                'Invalid prop ' + propName + 'supplied to ' + componentName + '. Validation failed.'
            )
        }
    }

    // 你也可以提供一个自定义的`arrayOf`或`objectOf`验证器
    // 它应该在验证失败时返回一个Error对象。
    // 验证器将验证数组或对象中的每个值。验证器的前两个参数
    // 第一个是数组或对象本身
    // 第二个是对他当前的键
    customArrayProp:PropTypes.arrayOf(function(propValue,key,componentName,location,propFullName){
         if(!/matchme/.test(props[propName])){
            return new Error(
                'Invalid prop ' + propName + 'supplied to ' + componentName + '. Validation failed.'
            )
        }
    })

}

限制单个元素

你可以通过PropTypes.element来确保传递给组件的children中只包含一个元素:

import PropTypes from 'prop-types'
class MyComponent extends React.Component {
    render(){
        const children = this.props.children;
        return (
            <div> { children } </div>
        )
    }
}
MyComponent.propTypes = {
    children:PropTypes.element.isRequired
}

默认Prop值

你可以通过配置特定的defaultProps属性来定义props的默认值:

class Greeting extends React.Component{
    render(){
        return (
            <h1> hello , { this.props.name } </h1>
        )
    }
}
Greeting.defaultProps = {
    name:'Stranger'
}
// 渲染出 "Hello, Stranger":

如果你正在使用像 transform-class-properties 的 Babel 转换工具,你也可以在 React 组件类中声明 defaultProps 作为静态属性。此语法提案还没有最终确定,需要进行编译后才能在浏览器中运行。要了解更多信息,请查阅 class fields proposal。

class Greeting extends React.Component {
  static defaultProps = {
    name: 'stranger'
  }

  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}

defaultProps 用于确保 this.props.name 在父组件没有指定其值时,有一个默认值。propTypes 类型检查发生在 defaultProps 赋值后,所以类型检查也适用于 defaultProps。