我正在一个页面上,其“数据模型”是一个集合,例如,一群人。它们被打包到React Components中并在页面上平铺。本质上是这样的:
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>);
}
}
现在,我想为
<People>
组件中的每个条目附加一个编辑部分,它允许用户更新名称,年龄...特定条目的所有信息。由于React不支持组件内部的props变异,因此我搜索并发现将回调添加为props可以解决将数据传递给parent的问题。但是由于有许多要更新的字段,因此会有许多回调,例如
onNameChanged
,onEmailChanged
...可能非常难看(随着字段数量的增长,冗长程度也越来越高)。那么正确的方法是什么呢?
最佳答案
老实说最好的方法是助焊剂(在一分钟内即可返回)。
如果您开始以道具的形式将数据传递到树上,然后再传递回去使用回调进行编辑,那么您将打破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允许您将更改描述为操作,并将更改从组件传递到存储区(自包含的状态框),这些存储区了解如何根据操作来操纵其状态。然后,存储告诉所有关心它的组件某些更改,此时组件可以获取要渲染的新数据。
您将使用如下所示的体系结构重新获得单向数据流。
App <---- [Stores]
| ^
v |
Person --> Dispatcher
专卖店
与其将状态保留在
<App />
组件中,您可能希望创建一个People商店来跟踪您的人员列表。也许看起来像这样。
// 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);
}
});
ew到目前为止,还有很多工作要做。
现在,我们可以开始从组件中调度这些操作。
// 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
方法,它将把适当的操作发送到您的商店。删除一个人也是如此。通常,您会将这些内容移交给动作创建者,但这是另一个话题。好吧,终于到了!现在,我们的组件可以创建操作来更新商店中的数据。我们如何将这些数据返回到我们的组件中?
最初,这非常简单。我们可以在顶级组件中要求商店,而只需索取数据。
// 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自动为您创建listen方法,Redux允许您声明性地指定哪些组件将商品作为道具接收商店的哪些部分,然后进行更新。花足够的时间在Flux上,您会发现自己的喜好。
现在,您可能在想,Blimey –似乎只是为了向组件添加编辑功能而付出了很多努力。你是对的,是的!
对于小型应用程序,您可能不需要Flux。
当然,这样做有很多好处,但并非总是保证额外的复杂性。随着应用程序的增长,您会发现,如果对它进行了改进,则将更易于管理,维护和调试。
诀窍是要知道何时适合使用Flux架构,并希望何时到来,这个漫长而漫不经心的答案将为您解决问题。
这不是真的。