ReactJS应用程序 - 弹性VS快速失败

时间:2017-10-06 01:58:01

标签: reactjs validation testing resiliency

我正处于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标记的道具指定默认值(而不是让他们的缺席打破组件)

3 个答案:

答案 0 :(得分:6)

  

最近我被告知我们不应该这样做,道具是   我们对父母的期望以及合同是否得到尊重   让组件破坏。

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

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

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

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

  3.   

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

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

    更新

    如果您使用的是React&gt; 16,here是处理错误的方法。

答案 1 :(得分:6)

通过组件.isRequreddefaultProps道具分配默认值是不正确的。 根据{{​​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
    ...
  );
}

上面的例子是针对字符串的,我们需要为其他数据类型使用不同的工具。

祝你好运。