说实话? 最好的方法是 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
商店
与其将状态保存在<App /> 组件中,不如创建一个人员存储来跟踪您的人员列表。
也许它看起来像这样。
// 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 架构,希望时机成熟时,这个过于冗长、漫无边际的答案会为您解决问题。
- 事实并非如此。