【问题标题】:What is the right/preferred way to make a "Edit Detail" component in React?在 React 中制作“编辑详细信息”组件的正确/首选方法是什么?
【发布时间】:2016-01-09 13:19:31
【问题描述】:

我正在处理一个“数据模型”是一个集合的页面,例如一组人。它们被打包到 React 组件中并平铺在页面上。本质上是这样的:

class App extends React.Component {
    constructor() {
        super();
        this.state = { people: /* some data */ };
    }

    render () {
        return (
            <div>
                {this.state.people.map((person) =>
                    <People data={person}></People>)}
            </div>);
    }
}

现在我想为&lt;People&gt; 组件中的每个条目附加一个编辑部分,它允许用户更新特定条目的姓名、年龄……各种信息。

由于 React 不支持在组件内部修改 props,所以我搜索了一下,发现添加回调作为 props 可以解决向 parent 传递数据的问题。但是由于要更新的​​字段很多,所以会有很多回调,例如onNameChanged、onEmailChanged...,这可能会非常丑陋(随着字段数量的不断增长,也会越来越冗长)。

那么正确的方法是什么?

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    说实话? 最好的方法是 Flux(稍后再讲)。

    如果您开始以 props 的形式将数据传递到树中,然后将其传回以使用回调进行编辑,那么您将打破 React 所围绕的单向数据流。

    但是,并非所有项目都需要按照理想标准编写,并且可以在没有 Flux 的情况下构建它(有时甚至可能是正确的解决方案)。

    无助焊剂

    您可以通过传递单个edit 函数作为道具来实现这一点,而无需大量回调。这个函数应该接受一个id 和一个新的 person 对象,然后在它运行时更新父组件内部的状态。这是一个例子。

    editPerson(id, editedPerson) {
      const people = this.state.people;
      const newFragment = { [id]: editedPerson };
    
      // create a new list of people, with the updated person in
      this.setState({
        people: Object.assign([], people, newFragment)
      });
    },
    render() {
      // ...
      {this.state.people.map((person, index) => {
        const edit = this.editPerson.bind(this, index);
    
        return (
          <People data={person} edit={edit}></People>
        );
      })}
      // ...
    }
    

    然后在您的人员组件中,每当您对人员进行更改时,只需通过回调将人员传递回父状态。

    但是,如果您通过应用程序可视化数据流,您现在已经创建了一个看起来像这样的循环。

     App
      ^
      |
      v
    Person
    

    算出应用中的数据从何而来已不再是小事(在这么小的应用中仍然很简单,但显然越大越难分辨。

    有助焊剂

    一开始,Facebook 开发人员使用单向数据流编写 React 应用程序,他们认为这很好。但是,需要将数据上树,这导致了危机。我们的数据流如何是单向的,并且仍然返回树的顶部?并且在第七天,他们创建了 Flux(1),发现它非常好。

    Flux 允许您将更改描述为操作并将它们从组件传递到存储(自包含状态框),这些存储了解如何根据操作操作其状态。然后 store 告诉所有关心它的组件发生了一些变化,此时组件可以获取新数据进行渲染。

    您重新获得了单向数据流,其架构如下所示。

     App <---- [Stores]
      |            ^
      v            |
    Person --> Dispatcher
    

    商店

    与其将状态保存在&lt;App /&gt; 组件中,不如创建一个人员存储来跟踪您的人员列表。

    也许它看起来像这样。

    // stores/people-store.js
    const people = [];
    
    export function getPeople() {
      return people;
    }
    function editPerson(id, person) {
      // ...
    }
    function addPerson(person) {
      // ...
    }
    function removePerson(id) {
      // ...
    }
    

    现在,我们可以导出这些函数并让我们的组件直接调用它们,但这很糟糕,因为这意味着我们的组件必须了解商店的设计,并且我们希望它们尽可能地保持沉默。

    动作

    相反,我们的组件创建了我们的商店可以理解的简单、可序列化的操作。以下是一些示例:

    // remove person with id 53
    { type: 'PEOPLE_REMOVE', payload: 53 }
    
    // create a new person called John Foo
    { type: 'PEOPLE_ADD', payload: { name: 'John Foo' } }
    
    // edit person 13
    {
      type: 'PEOPLE_EDIT',
      payload: {
        id: 13,
        person: { name: 'Unlucky Bill' }
      }
    }
    

    这些操作不必有这些特定的键,它们甚至也不必是对象,这只是来自Flux Standard Actions 的约定。

    调度员

    现在,我们已经告诉我们的商店在这些操作到达时如何处理它们。

     // stores/people-store.js
     // ...
     dispatcher.register(function(action) {
       switch(action.type) {
         case 'PEOPLE_REMOVE':
           removePerson(action.payload);
         case 'PEOPLE_ADD':
           addPerson(action.payload);
         case 'PEOPLE_EDIT':
           editPerson(action.payload.id, action.payload.person);
       }
     });
    

    呸。到目前为止有很多工作,几乎完成了。

    现在我们可以开始从我们的组件中调度这些操作了。

     // components/people.js
     // ...
     onEdit(editedPerson) {
       dispatcher.dispatch({
         type: 'PEOPLE_EDIT',
         payload: {
           id: this.props.id,
           person: editedPerson
         }
       });
     }
     onRemove() {
       dispatcher.dispatch({
         type: 'PEOPLE_REMOVE',
         payload: this.props.id
       });
     }
     // ...
    

    当您编辑人员时,调用this.onEdit 方法,它将向您的商店发送适当的操作。删除一个人也是如此。通常你会将这些东西移到动作创建者中,但这是另一个话题。

    好的,终于到了某个地方!现在我们的组件可以创建更新我们商店中的数据的操作。我们如何将这些数据返回到我们的组件中?

    最初,它非常简单。我们可以在我们的顶级组件中要求 store 并简单地请求数据。

    // components/app.js
    import { getPeople } from './stores/people-store';
    // ...
    constructor() {
      super();
      this.state = { people: getPeople() };
    }
    

    我们可以用完全相同的方式传递这些数据,但是当数据发生变化时会发生什么?

    Flux 的官方立场基本上是“不是我们的问题”。 Their examples 使用 Node 的 Event Emitter 类来允许商店接受在商店更新时调用的回调函数。

    这使您可以编写如下所示的代码:

    componentWillMount() {
      peopleStore.addListener(this.peopleUpdated);
    },
    componentWillUnmount() {
      peopleStore.removeListener(this.peopleUpdated);
    },
    peopleUpdated() {
      this.setState({ people: getPeople() });
    }
    

    真的,这个球在你的球场上。有许多其他策略可以将数据返回到您的程序中。 Reflux 自动为您创建监听方法,Redux 允许您以声明方式指定哪些组件接收存储的哪些部分作为道具,然后它处理更新。花足够的时间在 Flux 上,你会发现自己的喜好。

    现在,你可能在想,天哪——这似乎需要付出很多努力才能为组件添加编辑功能;你是对的,是的!

    对于小型应用程序,您可能不需要 Flux。

    当然有很多好处,但并不总是需要额外的复杂性。随着您的应用程序的增长,您会发现如果您对它进行了修改,它会更容易管理、维护和调试。

    诀窍是知道什么时候适合使用 Flux 架构,希望时机成熟时,这个过于冗长、漫无边际的答案会为您解决问题。


    1. 事实并非如此。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-30
      • 1970-01-01
      • 2019-06-28
      • 2015-02-22
      • 1970-01-01
      • 1970-01-01
      • 2017-11-02
      • 2022-01-23
      相关资源
      最近更新 更多