我应该如何按照单一责任模式处理组件状态

How should I handle component state following single responsibility pattern

我是 ReactJs 的新手,正在尝试遵循最佳实践。从我的研究中,我遇到了几篇相互矛盾的文章,讨论了应该如何实施。

状态是否应该依赖于从父组件传递下来的属性?在下面的比较中,他们都遵循 SRP,但不确定哪个最好。需要您的建议,谢谢!

1。 -- Best Practices for Component State in React.js

First, and probably the most important of all, the state of a component should not depend on the props passed in. (see below for example of what we should not do)

class UserWidget extends React.Component {
  // ...

  // BAD: set this.state.fullName with values received through props
  constructor (props) {
    this.state = {
      fullName: `${props.firstName} ${props.lastName}`
    };
  }
  // ...
}

2。 -- 7 architectural attributes of a reliable React component

Let's refactor to have one responsibility: render form fields and attach event handlers. It shouldn't know how to use storage directly.....The component receives the stored input value from a prop initialValue, and saves the input value using a prop function saveValue(newValue). These props are provided by withPersistence() HOC using props proxy technique.

class PersistentForm extends Component {  
    constructor(props) {
        super(props);

        this.state = { inputValue: props.initialValue };
    }
    // ...
}

3。 -- 就我而言,我有类似以下内容(想知道这是否是一个可接受的实现?)- 状态应该在任务中处理,还是在位于 TasksWithData 和任务之间的另一种 TasksWithPersistence 类型的组件中处理?

export default function TasksWithData(TasksComponent) {  

    return class withData extends React.Component {
        render() {
            const tasks = TaskAPI.getTasks();
            return (
                <TasksComponent 
                    tasks={tasks} 
                    {...this.props} 
                />
            )
        }
    }

}


export default class Tasks extends React.Component {

    state = { 
        tasks: [], 
        addItemInput: null 
    };

    // ...

    componentDidMount() {
        this.updateComponentState({tasks: this.props.tasks});
    }

    componentDidUpdate() {
        this.prepUIForNextAddition();
    }

    // ...
}

示例 1 和示例 2 之间存在巨大差异。

在示例 #1 中,以这种方式从这些道具设置状态的原因是如果道具发生变化,小部件将不会更新。无论是否是最佳实践,这在任何框架中都是错误和糟糕的。在那种特殊情况下,使用状态真的没有意义。道具就足够了。

在示例 #2 中,prop 仅用于为状态提供初始值(该 prop 甚至被命名为 initialValue),这意味着状态的进一步更改将由组件控制,而不管道具变化。将 props 用于初始状态并没有违反单一责任原则,尤其是当它明确用于该目的时。

我真的不认为这两个例子是矛盾的,因为它们完全不同。此外,单一职责原则中没有规定不能通过 props 设置状态,你只需要注意你正在做的上下文。

你的问题的要点似乎围绕 anti-pattern 展开,即将一些 props 复制到 state.这,道具的变异,不是国家的目的。道具是不可变的,将它们欺骗到状态会破坏这种设计。

状态的目的是管理特定于 React 组件的事物,即仅限于该 React 组件。例如,用于在 React 组件中显示某些内容的 showHide 开关。如果有帮助,请将状态视为局部范围的变量。

大多数情况下,这种 anti-pattern 的道具复制可以通过 React object 中的函数来满足。例如,您的 state.full_name 变量成为一个命名函数 fullName,绑定到 React 组件。 (所有代码示例均采用 JSX 语法)

注意:在 JavaScript 中,驼峰式是函数和变量的命名结构,我假设您来自 ruby 基于下划线命名约定。 IMO 最好坚持编写代码所使用的语言的约定。这就是我使用驼峰命名的原因。

...
fullName() {
    return this.props.firstName + " " + this.props.lastName
} 
...

然后可以在组件的渲染中调用该函数

# in render() portion of your React component, assuming jsx syntax
<p>Hello, {this.fullName()}</p>

注意:请记住,在 ES6 中,您必须在构造函数中 bind the methods in your react class 或使用 => 语法,以便您可以使用此调用它们。

...
constructor(props) {
  super(props);
  this.fullName = this.fullName.bind(this);
}
...

您还可以将相关部分分解为一个名为 FullName 的新组件,如果它将被多个组件使用的话。

<FullName firstName={this.props.firstName} lastName={this.props.lastName} />

从技术上讲,"the react way" 至少在作者看来,是将其分解为另一个组件以实现可重用性。但是,需要权衡组件重用与增加的复杂性,即不要过早优化。所以你一开始可能不想做得太过火。需要的时候自然会出现。

React props 的一个非常广泛的概括是它们是有保证的、不可变的,并且它们像瀑布一样从最顶层的组件流下。如果您需要更新它们,请在有意义的最高级别更新它们。

在单一的基于 React 的方法中,如果你有一些 parent 需要注意的东西,"lift" 直到 parent 的那部分代码,反之亦然将它绑定到 child 作为道具,例如调用 API 的 AJAX 函数。我认为它是试图让组件尽可能地保持 dumb

parent 成为您 "lifted" 的项目的 "source of truth"。 parent 处理更新,然后将结果传递给 children。所以在 parent 中,它可能作为状态变量存在,然后作为 props 传递给 child object,然后它作为 props 传递给它的 child object,等等。 children 会随着 parent 中状态的变化而更新,当它作为道具通过链向下传播时。

如果您的应用仅是 React,即没有管理 objects 的存储,例如在 flux 模式或 redux 模式中,您可能必须将内容存储在最顶层对象的状态中,这在技术上可以被视为坏的。随着您的系统变得越来越复杂,此功能将由 flux 或 redux 的部分更好地处理。

希望对您有所帮助!