我刚开始使用ReactJS,我有一个问题。
我的应用程序本质上是一个带有过滤器和更改布局的按钮的列表。 目前我正在使用三个组件:<list />, < Filters />和<TopBar />,现在显然,当我在< Filters />中更改设置时,我想在<list />中触发一些方法来更新我的视图。
我如何使这3个组件相互交互,或者我是否需要某种全局数据模型,我只需要对其进行更改?
我刚开始使用ReactJS,我有一个问题。
我的应用程序本质上是一个带有过滤器和更改布局的按钮的列表。 目前我正在使用三个组件:<list />, < Filters />和<TopBar />,现在显然,当我在< Filters />中更改设置时,我想在<list />中触发一些方法来更新我的视图。
我如何使这3个组件相互交互,或者我是否需要某种全局数据模型,我只需要对其进行更改?
当前回答
我曾经是你现在的位置,作为一个初学者,你有时会觉得不适应如何反应的方式来做这件事。我将尝试用我现在思考的方式来解决这个问题。
国家是交流的基石
通常它归结于你改变这个组件的状态的方式在你的例子中你指出三个组件。
<List />:它可能会根据筛选器显示项目列表 <Filters />:筛选将更改数据的选项。 <TopBar />:选项列表。
为了协调所有这些交互你需要一个更高的组件我们叫它App,它会把动作和数据传递给每个组件例如,它看起来是这样的
<div>
<List items={this.state.filteredItems}/>
<Filter filter={this.state.filter} setFilter={setFilter}/>
</div>
因此,当setFilter被调用时,它将影响filteredItem并重新渲染两个组件;。如果这不是完全清楚,我给你一个例子,你可以在一个文件中检查复选框:
import React, {Component} from 'react';
import {render} from 'react-dom';
const Person = ({person, setForDelete}) => (
<div>
<input type="checkbox" name="person" checked={person.checked} onChange={setForDelete.bind(this, person)} />
{person.name}
</div>
);
class PeopleList extends Component {
render() {
return(
<div>
{this.props.people.map((person, i) => {
return <Person key={i} person={person} setForDelete={this.props.setForDelete} />;
})}
<div onClick={this.props.deleteRecords}>Delete Selected Records</div>
</div>
);
}
} // end class
class App extends React.Component {
constructor(props) {
super(props)
this.state = {people:[{id:1, name:'Cesar', checked:false},{id:2, name:'Jose', checked:false},{id:3, name:'Marbel', checked:false}]}
}
deleteRecords() {
const people = this.state.people.filter(p => !p.checked);
this.setState({people});
}
setForDelete(person) {
const checked = !person.checked;
const people = this.state.people.map((p)=>{
if(p.id === person.id)
return {name:person.name, checked};
return p;
});
this.setState({people});
}
render () {
return <PeopleList people={this.state.people} deleteRecords={this.deleteRecords.bind(this)} setForDelete={this.setForDelete.bind(this)}/>;
}
}
render(<App/>, document.getElementById('app'));
其他回答
最好的方法取决于您计划如何安排这些组件。下面是我现在想到的几个例子:
<Filters />是<List />的子组件 <Filters />和<List />都是父组件的子组件 <Filters />和<List />完全存在于单独的根组件中。
可能还有其他我没有想到的情况。如果你的不符合这些要求,请告诉我。以下是我处理前两种情况的一些非常粗略的例子:
场景# 1
您可以将处理程序从<List />传递到<Filters />,然后可以在onChange事件上调用该处理程序以使用当前值筛选列表。
JSFiddle for #1 →
/** @jsx React.DOM */
var Filters = React.createClass({
handleFilterChange: function() {
var value = this.refs.filterInput.getDOMNode().value;
this.props.updateFilter(value);
},
render: function() {
return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
}
});
var List = React.createClass({
getInitialState: function() {
return {
listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
nameFilter: ''
};
},
handleFilterUpdate: function(filterValue) {
this.setState({
nameFilter: filterValue
});
},
render: function() {
var displayedItems = this.state.listItems.filter(function(item) {
var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
return (match !== -1);
}.bind(this));
var content;
if (displayedItems.length > 0) {
var items = displayedItems.map(function(item) {
return <li>{item}</li>;
});
content = <ul>{items}</ul>
} else {
content = <p>No items matching this filter</p>;
}
return (
<div>
<Filters updateFilter={this.handleFilterUpdate} />
<h4>Results</h4>
{content}
</div>
);
}
});
React.renderComponent(<List />, document.body);
场景# 2
与场景#1类似,但是父组件将把处理程序函数传递给<Filters />,并将过滤后的列表传递给< list />。我更喜欢这个方法,因为它将<List />与<Filters />解耦。
JSFiddle for #2 →
/** @jsx React.DOM */
var Filters = React.createClass({
handleFilterChange: function() {
var value = this.refs.filterInput.getDOMNode().value;
this.props.updateFilter(value);
},
render: function() {
return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
}
});
var List = React.createClass({
render: function() {
var content;
if (this.props.items.length > 0) {
var items = this.props.items.map(function(item) {
return <li>{item}</li>;
});
content = <ul>{items}</ul>
} else {
content = <p>No items matching this filter</p>;
}
return (
<div className="results">
<h4>Results</h4>
{content}
</div>
);
}
});
var ListContainer = React.createClass({
getInitialState: function() {
return {
listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
nameFilter: ''
};
},
handleFilterUpdate: function(filterValue) {
this.setState({
nameFilter: filterValue
});
},
render: function() {
var displayedItems = this.state.listItems.filter(function(item) {
var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
return (match !== -1);
}.bind(this));
return (
<div>
<Filters updateFilter={this.handleFilterUpdate} />
<List items={displayedItems} />
</div>
);
}
});
React.renderComponent(<ListContainer />, document.body);
场景# 3
当组件不能在任何类型的父子关系之间进行通信时,文档建议设置一个全局事件系统。
即使他们不是父母-孩子关系,也有这样的可能性——那就是流动。有一个很好的(对我个人来说)实现称为Alt.JS(与Alt-Container)。
例如,您可以拥有依赖于组件详细信息设置的边栏。Component Sidebar与SidebarActions和SidebarStore连接,而Details是DetailsActions和DetailsStore。
你可以像那样使用AltContainer
<AltContainer stores={{
SidebarStore: SidebarStore
}}>
<Sidebar/>
</AltContainer>
{this.props.content}
这将保留商店(我可以用“商店”而不是“商店”道具)。现在,{this.props。content}可以是细节,取决于路由。假设/Details将我们重定向到那个视图。 例如,细节将有一个复选框,如果选中的话,将把侧边栏元素从X更改为Y。
从技术上讲,它们之间没有关系,没有通量是很难做到的。但这样就相当容易了。
现在让我们进入DetailsActions。我们会在那里创建
class SiteActions {
constructor() {
this.generateActions(
'setSiteComponentStore'
);
}
setSiteComponent(value) {
this.dispatch({value: value});
}
}
和DetailsStore
class SiteStore {
constructor() {
this.siteComponents = {
Prop: true
};
this.bindListeners({
setSiteComponent: SidebarActions.COMPONENT_STATUS_CHANGED
})
}
setSiteComponent(data) {
this.siteComponents.Prop = data.value;
}
}
现在,这就是魔法开始的地方。
如你所见,这里有SidebarActions的bindListener。如果使用setSiteComponent,将使用ComponentStatusChanged。
现在在SidebarActions
componentStatusChanged(value){
this.dispatch({value: value});
}
我们有这样的东西。它会在调用时分派那个对象。如果setSiteComponent in store将被使用(你可以在组件中使用例如onChange on Button或其他)它将被调用
现在在边栏商店中我们会有
constructor() {
this.structures = [];
this.bindListeners({
componentStatusChanged: SidebarActions.COMPONENT_STATUS_CHANGED
})
}
componentStatusChanged(data) {
this.waitFor(DetailsStore);
_.findWhere(this.structures[0].elem, {title: 'Example'}).enabled = data.value;
}
这里你可以看到,它会等待DetailsStore。这是什么意思?这或多或少意味着这个方法需要等待DetailsStoreto更新才能更新自己。
博士tl; 一个商店正在监听商店中的方法,并将从组件操作中触发一个操作,该操作将更新自己的商店。
我希望它能对你有所帮助。
我看到这个问题已经有了答案,但如果你想了解更多细节,组件之间的通信总共有3种情况:
案例1:父到子通信 案例2:子到父通信 案例3:不相关的组件(任何组件到任何组件)通信
我曾经是你现在的位置,作为一个初学者,你有时会觉得不适应如何反应的方式来做这件事。我将尝试用我现在思考的方式来解决这个问题。
国家是交流的基石
通常它归结于你改变这个组件的状态的方式在你的例子中你指出三个组件。
<List />:它可能会根据筛选器显示项目列表 <Filters />:筛选将更改数据的选项。 <TopBar />:选项列表。
为了协调所有这些交互你需要一个更高的组件我们叫它App,它会把动作和数据传递给每个组件例如,它看起来是这样的
<div>
<List items={this.state.filteredItems}/>
<Filter filter={this.state.filter} setFilter={setFilter}/>
</div>
因此,当setFilter被调用时,它将影响filteredItem并重新渲染两个组件;。如果这不是完全清楚,我给你一个例子,你可以在一个文件中检查复选框:
import React, {Component} from 'react';
import {render} from 'react-dom';
const Person = ({person, setForDelete}) => (
<div>
<input type="checkbox" name="person" checked={person.checked} onChange={setForDelete.bind(this, person)} />
{person.name}
</div>
);
class PeopleList extends Component {
render() {
return(
<div>
{this.props.people.map((person, i) => {
return <Person key={i} person={person} setForDelete={this.props.setForDelete} />;
})}
<div onClick={this.props.deleteRecords}>Delete Selected Records</div>
</div>
);
}
} // end class
class App extends React.Component {
constructor(props) {
super(props)
this.state = {people:[{id:1, name:'Cesar', checked:false},{id:2, name:'Jose', checked:false},{id:3, name:'Marbel', checked:false}]}
}
deleteRecords() {
const people = this.state.people.filter(p => !p.checked);
this.setState({people});
}
setForDelete(person) {
const checked = !person.checked;
const people = this.state.people.map((p)=>{
if(p.id === person.id)
return {name:person.name, checked};
return p;
});
this.setState({people});
}
render () {
return <PeopleList people={this.state.people} deleteRecords={this.deleteRecords.bind(this)} setForDelete={this.setForDelete.bind(this)}/>;
}
}
render(<App/>, document.getElementById('app'));
我就是这么处理的。 假设你有一个<select>表示月,一个<select>表示日。 天数取决于选择的月份。
这两个列表都属于第三个对象,即左面板。两个<select>也是leftPanel <div>的子元素 这是一个在LeftPanel组件中带有回调和处理程序的游戏。
要测试它,只需将代码复制到两个分开的文件中并运行index.html。然后选择一个月,看看天数的变化情况。
dates.js
/** @jsx React.DOM */
var monthsLength = [0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
var MONTHS_ARR = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
var DayNumber = React.createClass({
render: function() {
return (
<option value={this.props.dayNum}>{this.props.dayNum}</option>
);
}
});
var DaysList = React.createClass({
getInitialState: function() {
return {numOfDays: 30};
},
handleMonthUpdate: function(newMonthix) {
this.state.numOfDays = monthsLength[newMonthix];
console.log("Setting days to " + monthsLength[newMonthix] + " month = " + newMonthix);
this.forceUpdate();
},
handleDaySelection: function(evt) {
this.props.dateHandler(evt.target.value);
},
componentDidMount: function() {
this.props.readyCallback(this.handleMonthUpdate)
},
render: function() {
var dayNodes = [];
for (i = 1; i <= this.state.numOfDays; i++) {
dayNodes = dayNodes.concat([<DayNumber dayNum={i} />]);
}
return (
<select id={this.props.id} onChange = {this.handleDaySelection}>
<option value="" disabled defaultValue>Day</option>
{dayNodes}
</select>
);
}
});
var Month = React.createClass({
render: function() {
return (
<option value={this.props.monthIx}>{this.props.month}</option>
);
}
});
var MonthsList = React.createClass({
handleUpdate: function(evt) {
console.log("Local handler:" + this.props.id + " VAL= " + evt.target.value);
this.props.dateHandler(evt.target.value);
return false;
},
render: function() {
var monthIx = 0;
var monthNodes = this.props.data.map(function (month) {
monthIx++;
return (
<Month month={month} monthIx={monthIx} />
);
});
return (
<select id = {this.props.id} onChange = {this.handleUpdate}>
<option value="" disabled defaultValue>Month</option>
{monthNodes}
</select>
);
}
});
var LeftPanel = React.createClass({
dayRefresh: function(newMonth) {
// Nothing - will be replaced
},
daysReady: function(refreshCallback) {
console.log("Regisering days list");
this.dayRefresh = refreshCallback;
},
handleMonthChange: function(monthIx) {
console.log("New month");
this.dayRefresh(monthIx);
},
handleDayChange: function(dayIx) {
console.log("New DAY: " + dayIx);
},
render: function() {
return(
<div id="orderDetails">
<DaysList id="dayPicker" dateHandler={this.handleDayChange} readyCallback = {this.daysReady} />
<MonthsList data={MONTHS_ARR} id="monthPicker" dateHandler={this.handleMonthChange} />
</div>
);
}
});
React.renderComponent(
<LeftPanel />,
document.getElementById('leftPanel')
);
以及用于运行左侧面板组件的HTML index . html
<!DOCTYPE html>
<html>
<head>
<title>Dates</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
<script src="//fb.me/react-0.11.1.js"></script>
<script src="//fb.me/JSXTransformer-0.11.1.js"></script>
</head>
<style>
#dayPicker {
position: relative;
top: 97px;
left: 20px;
width: 60px;
height: 17px;
}
#monthPicker {
position: relative;
top: 97px;
left: 22px;
width: 95px;
height: 17px;
}
select {
font-size: 11px;
}
</style>
<body>
<div id="leftPanel">
</div>
<script type="text/jsx" src="dates.js"></script>
</body>
</html>