严格模式
严格模式和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的方式:
- 字符串Ref API
- 回调函数API
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}
{}- string ref 不适用于Flow之类的静态分析。Flow不能猜测框架可以使字符串ref"出现"在react上的神奇效果,以及它的类型(可能有所不同)。回调引用比静态分析更友好。
- string ref 强制React跟踪当前正在执行的组件。这是有问题的,因为它使react模块处于有状态,并在捆绑中复制react模块时导致奇怪的错误。在 reconciliation 阶段,React Element 创建和更新的过程中,ref 会被封装为一个闭包函数,等待 commit 阶段被执行,这会对 React 的性能产生一些影响。
- 以上问题使用回调函数形式会更好。
这也是为什么字符串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还会调用
componentDidMount和componentDidUpdate之类的生命周期方法。 提交阶段通常会很快,但渲染过程可能很慢。因此,即将推出的concurrent模式(默认情况下未启用)将渲染工作分解为多个部分,对任务进行暂停和恢复操作以避免阻塞浏览器。这意味React可以在提交之前多次调用渲染阶段生命周期方法。或者在不提交的情况下调用它们(由于出现错误或更高优先级的任务使其中断)。 渲染阶段的生命周期包括class组件的以下方法: constructorcomponentWillMount(orUNSAFE_componentWillMount)componentWillReceiveProps(orUNSAFE_componentWillReceiveProps)componentWillUpdate(orUNSAFE_componentWillUpdate)getDerivedStateFromPropsshouldComponentUpdaterendersetState
因为上述方法能会被多次调用,所以不要在他们内部编写副作用相关的代码,这个很重要。忽略此规则可能会导致各种问题的产生,包括内存泄漏和出现无限的应用程序状态。并且这些很问题很难发现,因为它们通常具有非确定性。 严格模式不能自动检测到你的副作用,但他可以帮助你发现它们,使她们更具有确定性。
检查过时的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。
