七、验证部件属性
在本章中,您将了解 React 组件中的属性验证。乍一看,这似乎很简单,但这是一个重要的话题,因为它会带来无 bug 的组件。首先,我们将讨论可预测的结果,以及这将如何导致组件在整个应用中可移植。
接下来,我们将浏览 React 附带的一些简单类型检查属性验证器的示例。然后,我们将介绍一些更复杂的属性验证场景。最后,我们将用一个如何实现您自己的自定义验证器的示例来结束本章。
知道该期待什么
React 组件中的属性验证类似于 HTML 表单中的字段验证。验证表单字段的基本前提是用户知道他们提供了一个不可接受的值。理想情况下,验证错误消息足够清晰,用户可以轻松修复该情况。使用 React 组件属性验证,我们正尝试做同样的事情,以便于修复提供意外值的情况。属性验证增强了开发人员体验,而不是用户体验。
属性验证的关键方面是了解作为属性值传递到组件中的内容。例如,如果我们期望的是一个数组,而传递的是一个布尔值,则可能会出错。如果使用内置的 React 验证机制验证属性值,那么您就知道传递了意外的消息。如果组件需要一个数组以便调用map()
方法,那么如果传递了布尔值,它将失败,因为它没有map()
方法。但是,在发生此故障之前,您将看到属性验证警告。
我们的想法不是通过属性验证快速失败。这是为了向开发者提供信息。当属性验证失败时,您会知道某些内容是作为组件属性提供的,而不应该这样做。因此,找到值在代码中传递的位置并修复它是一个简单的问题。
注
Fail fast 是软件的一种体系结构特性,在这种特性中,系统将完全崩溃,而不是在不一致的状态下继续运行。
推广便携式组件
当我们知道从组件属性中可以得到什么时,使用组件的上下文就变得不那么重要了。这意味着,只要组件能够验证其属性值,在哪里使用组件就不重要了;任何功能都可以轻松使用它。
如果您想要一个跨应用功能可移植的通用组件,您可以编写组件验证代码,也可以编写在渲染时运行的防御代码。防御性编程的挑战在于它稀释了声明性组件的价值。使用 React 样式的属性验证,可以避免编写防御代码。相反,属性验证机制在某些内容未通过时发出警告,通知您需要修复某些内容。
注
防御代码是在生产环境中运行时需要考虑大量边缘情况的代码。当在开发过程中无法检测到潜在问题时,如 React 组件属性验证,需要进行防御性编码。
简单属性验证器
在本节中,您将学习如何使用PropTypes
对象中提供的简单属性类型验证器。然后,您将学习如何接受任何属性值,以及如何将属性设置为 required而不是可选。
基本类型验证
让我们来看看处理最原始类型的 JavaScript 值的验证器。您将经常使用这些验证器,因为您希望知道属性是字符串还是函数。本示例还将向您介绍在组件上设置验证所涉及的机制。这是组件本身;它只是使用基本标记呈现一些属性:
import React, { PropTypes } from 'react';
const MyComponent = ({
myString,
myNumber,
myBool,
myFunc,
myArray,
myObject,
}) => (
<section>
{ /* Strings and numbers can be rendered
just about anywhere. */ }
<p>{myString}</p>
<p>{myNumber}</p>
{ /* Booleans are typically used as property
values. */ }
<p><input type="checkbox" defaultChecked={myBool} /></p>
{ /* Functions can return values, or be assigned as
event handler property values. */ }
<p>{myFunc()}</p>
{ /* Arrays are typically mapped to produce new
JSX elements. */ }
<ul>
{myArray.map(i => (
<li key={i}>{i}</li>
))}
</ul>
{ /* Objects typically use their properties in some
way. */ }
<p>{myObject.myProp}</p>
</section>
);
// The "propTypes" specification for this component.
MyComponent.propTypes = {
myString: PropTypes.string,
myNumber: PropTypes.number,
myBool: PropTypes.bool,
myFunc: PropTypes.func,
myArray: PropTypes.array,
myObject: PropTypes.object,
};
export default MyComponent;
属性验证机制有两个关键部分。首先,您拥有静态propTypes
属性。这是类级属性,不是实例属性。当 React 找到propTypes
时,它使用此对象作为组件的属性规范。其次,您有PropTypes
工具,它有几个内置的验证器功能。
在本例中,MyComponent
有六个属性,每个属性都有自己的类型。当您查看propTypes
规范时,应该清楚该组件将接受哪种类型的值。现在让我们来看看这个,并用一些属性值渲染这个组件:
import React from 'react';
import { render as renderJSX } from 'react-dom';
import MyComponent from './MyComponent';
// The properties that we'll pass to the component.
// Each property is a different type, and corresponds
// to the "propTypes" spec of the component.
const validProps = {
myString: 'My String',
myNumber: 100,
myBool: true,
myFunc: () => 'My Return Value',
myArray: ['One', 'Two', 'Three'],
myObject: { myProp: 'My Prop' },
};
// These properties don't correspond to the "<MyComponent>"
// spec, and will cause warnings to be logged.
const invalidProps = {
myString: 100,
myNumber: 'My String',
myBool: () => 'My Reaturn Value',
myFunc: true,
myArray: { myProp: 'My Prop' },
myObject: ['One', 'Two', 'Three'],
};
// Renders "<MyComponent>" with the given "props".
function render(props) {
renderJSX(
(<MyComponent {...props} />),
document.getElementById('app')
);
}
render(validProps);
render(invalidProps);
第一次渲染<MyComponent>
时,它使用validProps
属性。这些值都符合组件属性规范,因此控制台中不会记录任何警告。第二次,使用了invalidProps
属性,但属性验证失败,因为每个属性都使用了错误的类型。控制台输出应如下所示:
Invalid prop `myString` of type `number` supplied to `MyComponent`, expected `string`
Invalid prop `myNumber` of type `string` supplied to `MyComponent`, expected `number`
Invalid prop `myBool` of type `function` supplied to `MyComponent`, expected `boolean`
Invalid prop `myFunc` of type `boolean` supplied to `MyComponent`, expected `function`
Invalid prop `myArray` of type `object` supplied to `MyComponent`, expected `array`
Invalid prop `myObject` of type `array` supplied to `MyComponent`, expected `object`
TypeError: myFunc is not a function
最后一个错误很有趣。您可以清楚地看到,属性验证正在抱怨无效的属性类型。这包括传递给myFunc
的无效函数。因此,尽管对属性进行了类型检查,JSX 仍然会尝试调用值 is(如果它是函数)。
以下是渲染输出的外观:
注
同样,React 组件中属性验证的目的是帮助您在开发过程中发现 bug。当 React 处于生产模式时,属性验证将完全关闭。这意味着您不必担心编写昂贵的属性验证代码;它永远不会在生产中运行。但是,错误仍然会发生,因此请修复它。
需要值
让我们对前面的示例进行一些调整。组件属性规范需要特定类型的值,但只有当属性作为 JSX 属性传递给组件时,才会检查这些值。例如,您可能完全省略了myFunc
属性,它将被验证。值得庆幸的是,PropTypes
函数有一个工具,可以让您指定必须提供的属性以及它必须具有特定的值。以下是修改后的组件:
const MyComponent = ({
myString,
myNumber,
myBool,
myFunc,
myArray,
myObject,
}) => (
<section>
<p>{myString}</p>
<p>{myNumber}</p>
<p><input type="checkbox" defaultChecked={myBool} /></p>
<p>{myFunc()}</p>
<ul>
{myArray.map(i => (
<li key={i}>{i}</li>
))}
</ul>
<p>{myObject.myProp}</p>
</section>
);
// The "propTypes" specification for this component. Every
// property is required, because they each have the
// "isRequired" property.
MyComponent.propTypes = {
myString: PropTypes.string.isRequired,
myNumber: PropTypes.number.isRequired,
myBool: PropTypes.bool.isRequired,
myFunc: PropTypes.func.isRequired,
myArray: PropTypes.array.isRequired,
myObject: PropTypes.object.isRequired,
};
export default MyComponent;
正如您所看到的,这个组件与我们在上一节中实现的组件之间没有太大的变化。主要区别在于propTypes
中的规格。您会注意到,isRequired
值被追加到所使用的每个类型验证器中。例如,string.isRequired
意味着属性值必须是字符串,并且属性不能丢失。现在让我们对该组件进行测试:
import React from 'react';
import { render as renderJSX } from 'react-dom';
import MyComponent from './MyComponent';
const validProps = {
myString: 'My String',
myNumber: 100,
myBool: true,
myFunc: () => 'My Return Value',
myArray: ['One', 'Two', 'Three'],
myObject: { myProp: 'My Prop' },
};
// The same as "validProps", except it's missing
// the "myObject" property. This will trigger a
// warning.
const missingProp = {
myString: 'My String',
myNumber: 100,
myBool: true,
myFunc: () => 'My Return Value',
myArray: ['One', 'Two', 'Three'],
};
// Renders "<MyComponent>" with the given "props".
function render(props) {
renderJSX(
(<MyComponent {...props} />),
document.getElementById('app')
);
}
render(validProps);
render(missingProp);
第一次使用所有正确的特性类型呈现组件。第二次,渲染组件时不使用myObject
属性。控制台错误应如下所示:
Required prop `myObject` was not specified in `MyComponent`.
Cannot read property 'myProp' of undefined
您可以看到,由于myObject
的属性规范,显然需要为myObject
属性提供对象值。最后一个错误是因为组件假定存在一个属性为myProp
的对象。但这很好,因为由于属性验证,问题很容易发现和修复。
注
理想情况下,我们将在本例中验证myProp
对象属性,因为它直接用于 JSX。JSX 标记中用于对象形状和形状的特定属性可以进行验证,您将在本章后面看到。
任何财产价值
本节的最后一个主题是any
属性验证器。也就是说,它实际上并不关心它得到什么值,任何东西都是有效的,包括根本不传递值。事实上,isRequired
验证器可以与any
验证器组合使用。例如,如果您正在处理一个组件,并且您只想确保传递了某些内容,但还不确定需要哪种类型,那么您可以执行类似于myProp: PropTypes.any.isRequired
的操作。
另一个原因是为了一致性而使用any
属性验证器。每个组件都应该有属性规范。当我们不确定属性类型时,any
验证器在一开始很有用。我们至少可以开始属性规范,然后随着事情的发展完善它。
现在让我们来看看一些代码:
import React, { PropTypes } from 'react';
// Renders a component with a header and a simple
// progress bar, using the provided property
// values.
const MyComponent = ({
label,
value,
max,
}) => (
<section>
<h5>{label}</h5>
<progress {...{ max, value }} />
</section>
);
// These property values can be anything, as denoted by
// the "PropTypes.any" prop type.
MyComponent.propTypes = {
label: PropTypes.any,
value: PropTypes.any,
max: PropTypes.any,
};
该组件实际上不会验证任何内容,因为其属性规范中的三个属性将接受任何内容。但是,这是一个很好的起点,因为我一眼就能看到这个组件使用的三个属性的名称。所以稍后,当我决定这些属性应该具有哪些类型时,更改很简单。现在让我们看看这个组件的运行情况:
import React from 'react';
import { render } from 'react-dom';
import MyComponent from './MyComponent';
render((
<section>
{ /* Passes a string and two numbers to
"<MyComponent>". Everything works as
expected. */ }
<MyComponent
label="Regular Values"
max={20}
value={10}
/>
{ /* Passes strings instead of numbers to the
progress bar, but they're correctly
interpreted as numbers. */}
<MyComponent
label="String Values"
max="20"
value="10"
/>
{ /* The "label" has no issue displaying
"MAX_SAFE_INTEGER", but the date that's
passed to "max" causes the progress bar
to break. */ }
<MyComponent
label={Number.MAX_SAFE_INTEGER}
max={new Date()}
value="10"
/>
</section>
),
document.getElementById('app')
);
如您所见,字符串和数字在多个地方可以互换。因此,仅限于其中一种似乎过于严格。正如您将在下一节中看到的,React 具有其他属性验证器,允许您进一步限制组件中允许的属性值。
下面是渲染时组件的外观:
类型和值验证器
在本节中,我们将了解 ReactPropTypes
设施中提供的更高级的验证器功能。首先,您将了解检查可在 HTML 标记内呈现的值的元素和节点验证器。然后,您将看到如何检查特定类型,而不仅仅是上一节中看到的基本类型检查。最后,我们将实现查找特定值的验证。
可以呈现的事物
有时,您只需要确保属性值可以由 JSX 标记呈现。例如,如果属性值是数组,则无法通过将其放入{}
来呈现。您必须将数组项映射到 JSX 元素。
如果组件将属性值作为子元素传递给其他元素,这种检查尤其有用。让我们看一个这样的例子:
import React, { PropTypes } from 'react';
const MyComponent = ({
myHeader,
myContent,
}) => (
<section>
<header>{myHeader}</header>
<main>{myContent}</main>
</section>
);
// The "myHeader" property requires a React
// element. The "myContent" property requires
// a node that can be rendered. This includes
// React elements, but also strings.
MyComponent.propTypes = {
myHeader: PropTypes.element.isRequired,
myContent: PropTypes.node.isRequired,
};
export default MyComponent;
此组件有两个属性,需要可以呈现的值。myHeader
属性需要一个element
。这可以是任何 JSX 元素。myContent
物业需要一个node
。这可以是任何 JSX 元素或任何字符串值。让我们向该组件传递一些值并渲染它:
import React from 'react';
import { render } from 'react-dom';
import MyComponent from './MyComponent';
// Two React elements we'll use to pass to
// "<MyComponent>" as property values.
const myHeader = (<h1>My Header</h1>);
const myContent = (<p>My Content</p>);
render((
<section>
{ /* Renders as expected, both properties are passed
React elements as values. */ }
<MyComponent
{...{ myHeader }}
{...{ myContent }}
/>
{ /* Triggers a warning because "myHeader" is expecting
a React element instead of a string. */ }
<MyComponent
myHeader="My Header"
{...{ myContent }}
/>
{ /* Renders as expected. A string is a valid type for
the "myContent" property. */ }
<MyComponent
{...{ myHeader }}
myContent="My Content"
/>
{ /* Renders as expected. An array of React elements
is a valid type for the "myContent" property. */ }
<MyComponent
{...{ myHeader }}
myContent={[myContent, myContent, myContent]}
/>
</section>
),
document.getElementById('app')
);
正如您所看到的,myHeader
属性对它将接受的值有更严格的限制。myContent
属性将接受字符串、元素或元素数组。这两个验证器在从属性传递子数据时很重要,就像这个组件一样。例如,尝试将普通对象或函数作为子对象传递将不起作用,最好使用验证器检查这种情况。
下面是渲染时此组件的外观:
需要特定类型
有时,您需要一个属性验证器来检查应用定义的类型。例如,假设您有以下用户类:
import cuid from 'cuid';
// Simple class the exposes an API that the
// React component expects.
export default class MyUser {
constructor(first, last) {
this.id = cuid();
this.first = first;
this.last = last;
}
get name() {
return `${this.first} ${this.last}`;
}
}
现在,假设您有一个组件想要使用这个类的实例作为属性值。您需要一个验证器来检查属性值是否是MyUser
的实例。让我们实现一个组件,该组件可以实现以下功能:
import React, { PropTypes } from 'react';
import MyUser from './MyUser';
const MyComponent = ({
myDate,
myCount,
myUsers,
}) => (
<section>
{ /* Requires a specific "Date" method. */ }
<p>{myDate.toLocaleString()}</p>
{ /* Number or string works here. */ }
<p>{myCount}</p>
<ul>
{ /* "myUsers" is expected to be an array of
"MyUser" instances. So we know that it's
safe to use the "id" and "name" property. */ }
{myUsers.map(i => (
<li key={i.id}>{i.name}</li>
))}
</ul>
</section>
);
// The properties spec is looking for an instance of
// "Date", a choice between a string or a number, and
// an array filled with specific types.
MyComponent.propTypes = {
myDate: PropTypes.instanceOf(Date),
myCount: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
myUsers: PropTypes.arrayOf(PropTypes.instanceOf(MyUser)),
};
export default MyComponent;
这个组件有三个属性,它们需要特定的类型,每个属性都超出了本章中到目前为止所看到的基本类型验证器。现在让我们来看看这些:
myDate
需要Date
的实例。它使用instanceOf()
函数来构建验证程序函数,以确保值实际上是Date
实例。myCount
要求通过数字或字符串输入值。此验证器功能是通过组合oneOfType
、PropTypes.number()
和PropTypes.string()
创建的。myUsers
需要一个MyUser
实例数组。此验证器由arrayOf()
和instanceOf()
组合而成。
这个例子说明了我们可以通过组合 React 提供的属性验证器来处理的场景数量。以下是渲染输出的外观:
需要特定值
到目前为止,我们的重点是验证属性值的类型,但这并不总是我们想要检查的。有时,具体的价值观很重要。让我们看看如何验证特定属性值:
import React, { PropTypes } from 'react';
// Any one of these is a valid "level"
// property value.
const levels = new Array(10)
.fill(null)
.map((v, i) => i + 1);
// This is the "shape" of the object we expect
// to find in the "user" property value.
const userShape = {
name: PropTypes.string,
age: PropTypes.number,
};
const MyComponent = ({
level,
user,
}) => (
<section>
<p>{level}</p>
<p>{user.name}</p>
<p>{user.age}</p>
</section>
);
// The property spec for this component uses
// "oneOf()" and "shape()" to define the required
// property vlues.
MyComponent.propTypes = {
level: PropTypes.oneOf(levels),
user: PropTypes.shape(userShape),
};
export default MyComponent;
level
属性应该是levels
数组中的数字。这很容易使用oneOf()
功能进行验证。user
属性需要一个特定的形状。形状是对象的预期属性和类型。本例中定义的userShape
需要name
字符串和age
编号。shape()
和instanceOf()
之间的关键区别在于,我们不一定关心类型。我们可能只关心组件的 JSX 中使用的值。
让我们来看看这个组件是如何使用的:
import React from 'react';
import { render } from 'react-dom';
import MyComponent from './MyComponent';
render((
<section>
{ /* Works as expected. */ }
<MyComponent
level={10}
user={{ name: 'Name', age: 32 }}
/>
{ /* Works as expected, the "online"
property is ignored. */ }
<MyComponent
user={{ name: 'Name', age: 32, online: false }}
/>
{ /* Fails. The "level" value is out of range,
and the "age" property is expecting a
number, not a string. */ }
<MyComponent
level={11}
user={{ name: 'Name', age: '32' }}
/>
</section>
),
document.getElementById('app')
);
下面是渲染组件时的外观:
编写自定义属性验证器
在最后一节中,您将看到如何构建自己的自定义属性验证函数,并在属性规范中应用它们。一般来说,只有在绝对必要的情况下,才应该实现自己的属性验证器。PropTypes
中提供的默认验证器涵盖了广泛的场景。
但是,有时需要确保将非常特定的属性值传递给组件。请记住,这些将不会在生产模式下运行,因此对集合进行迭代是完全可以接受的。现在让我们实现两个自定义验证器函数:
import React from 'react';
const MyComponent = ({
myArray,
myNumber,
}) => (
<section>
<ul>
{myArray.map(i => (
<li key={i}>{i}</li>
))}
</ul>
<p>{myNumber}</p>
</section>
);
MyComponent.propTypes = {
// Expects a property named "myArray" with a non-zero
// length. If this passes, we return null. Otherwise,
// we return a new error.
myArray: (props, name, component) =>
(Array.isArray(props[name]) &&
props[name].length) ? null : new Error(
`${component}.${name}: expecting non-empty array`
),
// Expects a property named "myNumber" that's
// greater than 0 and less than 99\. Otherwise,
// we return a new error.
myNumber: (props, name, component) =>
(Number.isFinite(props[name]) &&
props[name] > 0 &&
props[name] < 100) ? null : new Error(
`${component}.${name}: ` +
`expecting number between 1 and 99`
),
};
export default MyComponent;
myArray
属性需要一个非空数组,myNumber
属性需要一个大于0
小于100
的数字。让我们尝试向这些验证器传递一些数据:
import React from 'react';
import { render } from 'react-dom';
import MyComponent from './MyComponent';
render((
<section>
{ /* Renders as expected... */ }
<MyComponent
myArray={['first', 'second', 'third']}
myNumber={99}
/>
{ /* Both custom validators fail... */ }
<MyComponent
myArray={[]}
myNumber={100}
/>
</section>
),
document.getElementById('app')
);
如您所见,第一个元素呈现得很好,因为两个验证器都返回 null。但是,空数组和数字100
会导致两个验证器返回错误:
MyComponent.myArray: expecting non-empty array
MyComponent.myNumber: expecting number between 1 and 99
以下是渲染输出的外观:
总结
本章的重点是 React 组件属性验证。当您实现属性验证时,您知道会发生什么;这促进了可移植性。组件不关心属性值如何传递给它,只要它们是有效的。
然后,我们浏览了几个使用基本 React 验证器检查基本 JavaScript 类型的示例。您还了解到,如果需要属性,则必须明确这一点。然后,您学习了如何通过组合 React 附带的内置验证器来验证更复杂的属性值。
最后,您实现了自己的自定义验证器函数,以执行超出内置 React 验证器可能实现的验证。在下一章中,您将学习如何使用新数据和行为扩展 React 组件。
版权属于:月萌API www.moonapi.com,转载请注明出处