10

我正在开发 React 应用程序,这是我用于组件的方法:我使用 PropTypes 验证验证我希望收到的道具,但我仍然分配默认值以避免它如果接收到的数据出现问题,则中断。

最近有人告诉我,我们不应该这样做,道具是我们对父级的期望,如果不遵守合同让组件中断。

哪种方法是正确的,优缺点是什么?

我的一些考虑作为思考的食物..

按照我最初的方法,在测试中,我明确测试传递给被测组件的一些无效数据的默认值,并期望仍然打印出有效的快照。测试不会因为一些错误的数据而失败,但我会打印出 PropTypes 验证警告(如果需要,可以将其转换为错误 - 我认为 - 或者在测试中将它们模拟出来)。

测试和实际应用程序中的这些警告比仅仅看到“无法从未定义中读取 'someProp'”或类似错误(并让 React 渲染循环中断)更简洁明了。propType 验证直接并清楚地告诉您您做错了什么(您将错误的类型作为 prop 传递,该 prop 完全丢失等)。

使用第二种方法,测试失败,因为应用程序中断。我认为只有在测试覆盖率真的很好(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 的道具分配默认值是否正确(而不是让它们的缺席破坏组件)

4

3 回答 3

7

最近有人告诉我,我们不应该这样做,道具是我们对父级的期望,如果不遵守合同让组件中断。

确切地说,如果组件中的道具是可选的,则组件(呈现实际视图的)应该处理它,而不是父组件。

但是,您可能会遇到这样一种情况,即如果任何子组件合同中断,则父组件应该中断。我可以想到两种可能的方法来处理这种情况-

  1. 将错误通知器传递给子组件,如果出现任何问题,子组件可以向父组件报告错误。但这不是干净的解决方案,因为如果有 N 个孩子并且如果有多个孩子会向父母破坏(或报告错误),您将毫无头绪并且难以管理。[这根本没有效果,但写在这里是因为当我学习 React 时,我曾经遵循这一点:P]

  2. 在父组件中使用try/catch,不要盲目信任任何子组件,并在出现问题时显示错误消息。当您try/catch在所有组件中使用时,当任何合同未履行时,您可以安全地从组件中抛出错误。

哪种方法是正确的,优缺点是什么?

IMO,第二种方法(try/catch在组件中并在不满足要求时抛出错误)是有效的,并将解决所有问题。在未通过道具时为组件编写测试时,您可能会在加载组件时出现错误。

更新

如果你使用 React > 16,这里是处理错误的方法。

于 2017-10-10T09:20:38.663 回答
7

.isRequred通过 component 为 props分配默认值是不正确的defaultProps。根据官方文档

defaultProps 将用于确保 this.props.name 有一个值,如果它没有被父组件指定。propTypes 类型检查发生在解决 defaultProps 之后,因此类型检查也将应用于 defaultProps。

如果您在 Component.defaultProps 中设置默认属性值,那么如果父组件未提供此属性,您将永远不会收到警告。

于 2017-10-12T13:15:39.177 回答
4

在我看来,我不会让一两个缺失的属性破坏我的应用程序。React 在我的应用程序中充当表示层,我认为当我在一个对象中找不到键时显示“糟糕!有问题”是很遥远的事情。这似乎是来自状态为 500 的严重损坏服务器的消息,但我们知道这绝对不是错误的。

对我来说,我确实建立了一些规则来处理渲染函数和 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
    ...
  );
}

上面的示例是针对字符串的,对于其他数据类型我们需要不同的实现。

祝你好运。

于 2017-10-10T13:31:50.647 回答