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

ReactJS Application - resiliency VS failing fast

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

最近我被告知我们不应该那样做,props 是我们期望从 parent 得到的,如果不遵守合同让组件损坏。

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

我的一些思考值得深思..

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

这些在测试和实际应用程序中的警告比仅仅看到一个错误“无法读取 'someProp' 来自 undefined" 或类似的(并让 React 渲染周期中断)。propType 验证直接并清楚地告诉你你做错了什么(你将错误的类型作为 prop 传递,prop 完全丢失,等等)。

使用第二种方法测试失败,因为应用程序中断。我认为只有当测试覆盖率真的很好 (90/100%) 时,这才是一个好方法,否则这是一种风险——它可能会上线并在边缘情况下中断,从而破坏产品声誉。 重构或需求更改经常发生,一些边缘情况可能会以不需要的数据结束,这些数据会破坏应用程序并且未在自动或手动测试中捕获。

这意味着当应用程序处于活动状态时,由于某些错误数据,代码可能会在父组件中中断,并且整个应用程序将停止工作,而在第一种情况下,应用程序是有弹性的并且只是显示 以受控方式出现一些空白字段。

想法?

下面是一个简化的例子:

React 组件

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 的道具分配默认值是否正确(而不是让它们的缺失破坏组件)

Recently I've been told that we should not do that, that the props are what we expect from the parent and if the contract is not respected to let the component break.

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

但是,如果任何 child 组件合同被破坏,您可能会遇到 parent 应该被破坏的情况。我可以想到两种可能的方法来处理这种情况-

  1. 将错误通知器传递给 child 组件,如果出现任何问题 child 可以向 parent 组件报告错误。但这不是干净的解决方案,因为如果有 N child 并且如果超过一个会中断(或报告错误)到 parent,你将毫无头绪并且难以管理。[这是完全没有效果但是写在这里是因为我在学习 React 时曾经遵循过这个:P]

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

Which approach is correct and what are the pros and cons?

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

更新

如果您使用的 React > 16,here 是处理错误的方式。

在我看来,我不会让一两个缺失的属性破坏我的申请。 React 在我的应用程序中充当表示层,我认为当我在一个对象中找不到键时显示 "Oops! There is something wrong" 已经很远了。这似乎是来自状态为 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
    ...
  );
}

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

祝你好运。

通过组件 defaultProps 将默认值分配给 .isRequred 道具是不正确的。 根据 official docs:

The defaultProps will be used to ensure that this.props.name will have a value if it was not specified by the parent component. The propTypes typechecking happens after defaultProps are resolved, so typechecking will also apply to the defaultProps.

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