我正处于React应用程序开发的中间,这是我用于组件的方法:我使用PropTypes验证验证我希望收到的道具,但我仍然分配默认值为了避免在收到的数据出现问题时它会中断。
最近我被告知我们不应该这样做,道具是我们对父母的期望,如果合同没有得到尊重让组件破裂。
哪种方法是正确的,有什么优点和缺点?
我的一些考虑因素值得思考......
按照我的初始方法,在测试中我明确地测试了默认值,传递给测试中的组件一些无效数据,并期望仍然打印出有效的快照。 由于一些不良数据,测试不会失败,但是我会打印出PropTypes验证警告(如果需要,可能会转换为错误 - 我认为 - 或者在测试中将它们嘲笑)。
在测试和实际应用程序中的这些警告比仅仅看到错误说"无法阅读&some; #Prop'更加简洁明了。从 未定义"或类似(并让React渲染循环中断)。 propType验证直接并清楚地告诉你你做错了什么(你传给错误的类型作为道具,道具完全缺失等等)。
使用第二种方法,测试失败,因为应用程序中断。我认为只有当测试覆盖率真的很好(90/100%)时这才是一个好方法,否则它会带来风险 - 它可以上线并打破破坏产品声誉的边缘情况。 重构或需求变更经常发生,并且一些边缘情况可能最终导致破坏应用程序的不需要的数据,并且未在自动或手动测试中捕获。
这意味着当应用程序处于活动状态时,由于某些不良数据而整个应用程序停止工作,代码可能会在父组件中中断,而在第一种情况下,应用程序具有弹性并且只是显示 一些受控制的空场。
思想?
遵循一个简化的例子:
import React from 'react';
import PropTypes from 'prop-types';
import styles from './styles.css';
export const App = ({ person : { name, surname, address, subscription } = {} }) => (
<div style={styles.person}>
<p> {person.name} </p>
<p> {person.surname} </p>
<p> {person.address} </p>
<div>
{
person.subscription &&
<Subscription details={person.subscription} />
}
</div>
</div>
);
// PS. this is incorrect in this example (as pointed out in an answer). Real code used inline initialization.
// App.defaultProps = {
// person: { subscription: undefined },
// };
App.propTypes = {
person: PropTypes.shape({
name: PropTypes.string.isRequired,
surname: PropTypes.string.isRequired,
address: PropTypes.string,
subscription: PropTypes.object,
}).isRequired,
};
import React from 'react';
import { shallow } from 'enzyme';
import { mockOut } from 'testUtils/mockOut';
import { App } from '../index.js';
describe('<App>', () => {
mockout(App, 'Subscription');
it('renders correctly', () => {
const testData = {
name: 'a name',
surname: 'a surname',
address: '1232 Boulevard Street, NY',
subscription: { some: 'data' },
}
const tree = shallow(<App person={testData} />);
expect(tree.html()).toMatchSnapshot();
});
it('is resilient in case of bad data - still generates PropTypes validation logs', () => {
const tree = shallow(<App person={undefined} />);
expect(tree.html()).toMatchSnapshot();
});
});
更新
问题的主要焦点在于是否正确为使用isRequired标记的道具指定默认值(而不是让他们的缺席打破组件)
答案 0 :(得分:6)
最近我被告知我们不应该这样做,道具是 我们对父母的期望以及合同是否得到尊重 让组件破坏。
确切地说,如果组件中的props是可选的,则组件(呈现实际视图的人)应该处理它,而不是父组件。
但是,如果任何子组件合同中断,父级应该会中断。我可以想到两种可能的方法来处理这种情况 -
将错误通知程序传递给子组件,如果出现任何问题,子进程可以向父组件报告错误。但这不是一个干净的解决方案,因为如果有N个孩子并且如果有多个孩子会破坏(或报告错误)给父母,你将毫无头绪并且难以管理。[这根本没有效果,但写在这里是因为当我学习React时,我常常遵循这个:P]
在父组件中使用try/catch
,不要盲目信任任何子组件,并在出现问题时显示错误消息。当您在所有组件中使用try/catch
时,如果未达成任何合同,您可以安全地从组件中抛出错误。
哪种方法是正确的,有什么优点和缺点?
IMO,第二种方法(组件中try/catch
和未满足要求时抛出错误)是有效的,并将解决所有问题。在没有传递道具时编写组件测试时,加载组件时可能会出错。
如果您使用的是React&gt; 16,here是处理错误的方法。
答案 1 :(得分:6)
通过组件.isRequred
为defaultProps
道具分配默认值是不正确的。
根据{{3}}:
defaultProps将用于确保this.props.name将具有 如果未由父组件指定的值。 propTypes 在解决defaultProps之后发生了类型检查,因此进行了类型检查 也将适用于defaultProps。
如果在Component.defaultProps中设置默认属性值,那么如果父组件未提供此支柱,则永远不会收到警告。
答案 2 :(得分:4)
在我看来,我不会让一两个缺失属性来破坏我的申请。 React在我的应用程序中充当表示层,我认为当我在一个对象中找不到键时,显示“糟糕!有错误”的方法。这似乎是来自一个破损严重的服务器的消息,具有500状态,但我们知道这绝对没错。
对我来说,我确实构建了一些规则来处理render function和defaultProps之间的通信:
假设我们有一个从父代传递的用户对象:
defaultProps: {
user: {
avatar: {
small: ''
}
}
}
在渲染功能
中render() {
const { user } = this.props;
// if user.avatar is not defined or user.avatar.small is empty string or undefined then we render another component we have prepared for this situation.
if (!user.avatar || !user.avatar.small) {
return (
// components
...
);
}
// normal situation
return (
// components
...
);
}
上面的例子是针对字符串的,我们需要为其他数据类型使用不同的工具。
祝你好运。