我刚开始使用ReactJS,我有一个问题。
我的应用程序本质上是一个带有过滤器和更改布局的按钮的列表。 目前我正在使用三个组件:<list />, < Filters />和<TopBar />,现在显然,当我在< Filters />中更改设置时,我想在<list />中触发一些方法来更新我的视图。
我如何使这3个组件相互交互,或者我是否需要某种全局数据模型,我只需要对其进行更改?
我刚开始使用ReactJS,我有一个问题。
我的应用程序本质上是一个带有过滤器和更改布局的按钮的列表。 目前我正在使用三个组件:<list />, < Filters />和<TopBar />,现在显然,当我在< Filters />中更改设置时,我想在<list />中触发一些方法来更新我的视图。
我如何使这3个组件相互交互,或者我是否需要某种全局数据模型,我只需要对其进行更改?
当前回答
下面的代码帮助我建立两个兄弟姐妹之间的通信。设置是在render()和componentDidMount()调用期间在它们的父节点中完成的。 它基于https://reactjs.org/docs/refs-and-the-dom.html 希望能有所帮助。
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}
其他回答
下面的代码帮助我建立两个兄弟姐妹之间的通信。设置是在render()和componentDidMount()调用期间在它们的父节点中完成的。 它基于https://reactjs.org/docs/refs-and-the-dom.html 希望能有所帮助。
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}
有多种方法可以使组件进行通信。有些可能适合您的用例。下面是一些我认为有用的知识。
反应
亲子直接交流
const Child = ({fromChildToParentCallback}) => (
<div onClick={() => fromChildToParentCallback(42)}>
Click me
</div>
);
class Parent extends React.Component {
receiveChildValue = (value) => {
console.log("Parent received value from child: " + value); // value is 42
};
render() {
return (
<Child fromChildToParentCallback={this.receiveChildValue}/>
)
}
}
在这里,子组件将调用父组件提供的带有值的回调,父组件将能够获得父组件中子组件提供的值。
如果你为你的应用程序构建一个功能/页面,最好有一个单一的父组件来管理回调/状态(也称为容器或智能组件),所有的子组件都是无状态的,只向父组件报告事情。通过这种方式,您可以轻松地将父进程的状态“共享”给任何需要它的子进程。
上下文
React Context允许将状态保存在组件层次结构的根,并且能够很容易地将这种状态注入嵌套非常深的组件中,而不必麻烦地将道具传递给每个中间组件。
到目前为止,上下文还只是一个实验性的特性,但在React 16.3中有了一个新的API。
const AppContext = React.createContext(null)
class App extends React.Component {
render() {
return (
<AppContext.Provider value={{language: "en",userId: 42}}>
<div>
...
<SomeDeeplyNestedComponent/>
...
</div>
</AppContext.Provider>
)
}
};
const SomeDeeplyNestedComponent = () => (
<AppContext.Consumer>
{({language}) => <div>App language is currently {language}</div>}
</AppContext.Consumer>
);
使用者正在使用呈现道具/子函数模式
查看这篇博客文章了解更多细节。
在React 16.3之前,我建议使用React -broadcast,它提供了非常相似的API,并使用以前的上下文API。
门户网站
当你想让两个组件靠得很近,让它们用简单的函数通信时,使用门户,就像正常的父/子组件一样,但你不希望这两个组件在DOM中有父/子关系,因为它暗示了视觉/ CSS约束(如z-index,不透明度……)
在这种情况下,您可以使用“门户”。有不同的react库使用门户,通常用于模态,弹出窗口,工具提示…
考虑以下几点:
<div className="a">
a content
<Portal target="body">
<div className="b">
b content
</div>
</Portal>
</div>
在reactAppContainer中渲染时可以产生以下DOM:
<body>
<div id="reactAppContainer">
<div className="a">
a content
</div>
</div>
<div className="b">
b content
</div>
</body>
详情请点击这里
槽
在某处定义一个槽,然后从呈现树的另一个位置填充该槽。
import { Slot, Fill } from 'react-slot-fill';
const Toolbar = (props) =>
<div>
<Slot name="ToolbarContent" />
</div>
export default Toolbar;
export const FillToolbar = ({children}) =>
<Fill name="ToolbarContent">
{children}
</Fill>
这有点类似于门户,除了填充的内容将呈现在您定义的槽中,而门户通常呈现一个新的dom节点(通常是document.body的子节点)
检查react-slot-fill库
事件总线
正如React文档中所述:
对于没有父子关系的两个组件之间的通信,您可以设置自己的全局事件系统。在componentDidMount()中订阅事件,在componentWillUnmount()中取消订阅,并在收到事件时调用setState()。
可以使用许多方法来设置事件总线。您可以只创建一个侦听器数组,在事件发布时,所有侦听器都将收到该事件。或者你可以使用EventEmitter或PostalJs之类的东西
Flux
Flux基本上是一个事件总线,只是事件接收器是存储。这类似于基本的事件总线系统,只是状态是在React之外管理的
最初的Flux实现看起来像是试图以一种俗套的方式来做事件源。
对我来说,Redux是最接近事件源的Flux实现,这得益于事件源的许多优势,比如时间旅行的能力。它没有严格地链接到React,也可以与其他函数视图库一起使用。
Egghead的Redux视频教程非常好,解释了它的内部工作原理(真的很简单)。
游标
游标来自ClojureScript/Om,在React项目中广泛使用。它们允许在React之外管理状态,并允许多个组件对状态的同一部分进行读/写访问,而不需要了解任何关于组件树的信息。
有很多实现,包括ImmutableJS, react -游标和Omniscient
编辑2016:似乎人们同意游标在较小的应用程序中工作得很好,但在复杂的应用程序中却不能很好地伸缩。Om Next不再有游标(虽然最初是Om引入了这个概念)
榆树架构
Elm体系结构是一种由Elm语言使用的体系结构。即使Elm不是ReactJS,也可以在React中完成Elm架构。
Redux的作者Dan Abramov使用React实现了Elm架构。
Redux和Elm都很棒,都倾向于在前端支持事件源概念,都允许时间旅行调试、撤销/重做、重放……
Redux和Elm之间的主要区别是Elm倾向于对状态管理更加严格。在Elm中,你不能有本地组件状态或挂载/卸载钩子,所有DOM更改必须由全局状态更改触发。Elm体系结构提出了一种可伸缩的方法,允许在单个不可变对象中处理所有状态,而Redux提出了一种方法,邀请您在单个不可变对象中处理大多数状态。
虽然Elm的概念模型非常优雅,其架构允许在大型应用程序上很好地扩展,但在实践中,实现简单的任务可能会很困难,或者涉及更多的样板文件,比如在安装输入后将焦点放在输入上,或者与现有的库集成,使用强制接口(如JQuery插件)。相关的问题。
此外,Elm体系结构涉及更多的代码样板。写起来并没有那么冗长或复杂,但我认为Elm架构更适合静态类型语言。
FRP
像RxJS, BaconJS或Kefir这样的库可以用来生成FRP流来处理组件之间的通信。
你可以以Rx-React为例
我认为使用这些库与使用ELM语言提供的信号非常相似。
CycleJS框架不使用ReactJS,而是使用vdom。它与Elm架构有很多相似之处(但在现实生活中更容易使用,因为它允许vdom钩子),它广泛使用RxJs而不是函数,如果你想在React中使用FRP,它可以是一个很好的灵感来源。Egghead视频很好地理解了它是如何工作的。
CSP
CSP(通信顺序进程)目前很流行(主要是因为Go/goroutines和core.async/ClojureScript),但你也可以在JS-CSP的javascript中使用它们。
James Long做了一个视频,解释了它如何与React一起使用。
Sagas
saga是来自DDD / EventSourcing / CQRS世界的后端概念,也称为“过程管理器”。 它是由redux-saga项目推广的,主要是作为redux-thunk处理副作用(即API调用等)的替代品。目前大多数人认为它只服务于副作用,但实际上它更多的是关于解耦组件。
It is more of a compliment to a Flux architecture (or Redux) than a totally new communication system, because the saga emit Flux actions at the end. The idea is that if you have widget1 and widget2, and you want them to be decoupled, you can't fire action targeting widget2 from widget1. So you make widget1 only fire actions that target itself, and the saga is a "background process" that listens for widget1 actions, and may dispatch actions that target widget2. The saga is the coupling point between the 2 widgets but the widgets remain decoupled.
如果你感兴趣,看看我的答案
结论
如果您想查看使用这些不同样式的相同小应用程序的示例,请检查此存储库的分支。
我不知道从长远来看什么是最好的选择,但我真的很喜欢Flux看起来像事件源的样子。
如果您不了解事件源的概念,可以看看这个非常具有教学意义的博客:用apache Samza彻底改变数据库,这是一个必读的文章,可以理解Flux为什么很好(但这也适用于FRP)
我认为社区同意最有前途的Flux实现是Redux,由于热重新加载,它将逐步提供非常高效的开发体验。令人印象深刻的现场编码阿拉Bret维克多的发明原则视频是可能的!
我就是这么处理的。 假设你有一个<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>
即使他们不是父母-孩子关系,也有这样的可能性——那就是流动。有一个很好的(对我个人来说)实现称为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:不相关的组件(任何组件到任何组件)通信