Knockout.JS 中的组件通信
Component communication in Knockout.JS
我正在用 KnockoutJS 设计一个 "medium" 大小的应用程序,我想知道如何在组件之间发送事件。
想象一下 KnockoutJS 中的嵌套组件层次结构:
Root Viewmodel -> A -> B -> C
-> D
D 如何对来自 C 的消息做出反应?明显的 knockoutJS 方法是让 C 写入一个作为参数传递的 observable,并与 D 共享这个 observable,D 对这个 observable 的变化做出反应。
我不喜欢这种方法的是,A 和 B 需要了解消息,并且 A 和 B 会通过其参数主动转发处理程序。使用正常的依赖注入方法,我可以将组件 C 和 D 直接相互连接起来,例如通过在 A 和 B 不知情的情况下将 D 注入 C。
所以我的问题是:
- 有没有办法手动连接根视图模型中的组件(例如,通过拦截组件创建)?
或改写:
- 如何在不通过其父组件的情况下从主视图模型配置嵌套组件?
ko.components.register("aaa", {
viewModel: function (params) { this.handler = params.handler; },
template: "<span>A</span> <bbb params='handler: handler'></bbb>"
});
ko.components.register("bbb", {
viewModel: function (params) { this.handler = params.handler; },
template: "<span>B</span> <ccc params='handler: handler'></ccc>"
});
ko.components.register("ccc", {
viewModel: function (params) { this.handler = params.handler; },
template: "<span>C</span> <button data-bind='click: handler'>OK</button>"
});
ko.components.register("ddd", {
viewModel: function (params) {
var self = this;
this.text = ko.observable("No event received!");
if (params.onClick) params.onClick.subscribe(function () {
self.text("Event Received!");
});
},
template: "<span>D</span> <span data-bind='text:text'/>"
});
ko.applyBindings({
onClick: ko.observable()
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<p><aaa params='handler: onClick'> </aaa></p>
<p><ddd params='onClick: onClick'> </ddd></p>
一个我个人觉得很丑陋的解决方案,但是可以被认为是这个问题的有效答案:
您可以使用 $root
、$parents[i]
或 $parent
来引用组件绑定上下文中的任何层。我从您的示例中删除了一些 params
以向您展示它是如何工作的。它显示了两种口味:
- 从
params
属性里面的bindingContext
获取一个依赖(首选)
- 访问
bindingContext
内部组件模板(不推荐)
ko.components.register("aaa", {
viewModel: function (params) { },
template: "<span>A</span> <bbb></bbb>"
});
ko.components.register("bbb", {
viewModel: function (params) { },
template: "<span>B</span> <ccc></ccc>"
});
ko.components.register("ccc", {
viewModel: function (params) { },
// This hides the dependency in the template. It's better to pass it in the DOM via `params`
template: "<span>C</span> <button data-bind='click: $root.onClick'>OK</button>"
});
ko.components.register("ddd", {
viewModel: function (params) {
var self = this;
this.text = ko.observable("No event received!");
if (params.onClick) params.onClick.subscribe(function () {
self.text("Event Received!");
});
},
template: "<span>D</span> <span data-bind='text:text'/>"
});
ko.applyBindings({
onClick: ko.observable()
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<p><aaa> </aaa></p>
<!-- This approach isn't *that* ugly, since the dependency is made explicit in the DOM -->
<p><ddd params='onClick: $root.onClick'> </ddd></p>
这里的弱点是你创建了一个难以 document/check 的 DOM 结构的固定依赖。我通常在使用 $parent
...
后立即开始 运行 处理错误
在这种情况下,我认为最好使用 EventEmitter https://github.com/Olical/EventEmitter
通过这种方式,您可以创建 EventEmitter
的全局 ee
实例,例如在主 html
上的脚本中
window.ee = new EventEmitter()
然后你可以在组件之间添加监听器或触发动作:
对于组件 C 中的示例,您可以通过以下方式触发事件:window.ee.emitEvent('some-name-for-the-event', someArgumentsArray)
并且在您的 D 组件中,只需为该事件添加一个侦听器即可做出相应的反应,例如 window.ee.addListener('some-name-for-the-event', function (params) {
// your own code to handle the event
});
Is there a way to manually wire up the components inside the root viewmodel (e.g. by intercepting component creation)?
(emphasis mine)
淘汰机制
您可以通过使用方法 getConfig
、loadComponent
、loadTemplate
和方法的任意组合将对象添加到 ko.components.loaders
数组来注入 custom component loaders loadViewModel
.
由于您要在视图模型之间创建连接,因此这是我们需要定义的唯一方法。来自文档:
loadViewModel(name, viewModelConfig, callback)
The viewModelConfig value is simply the viewModel property from any componentConfig object. For example, it may be a constructor function, ...
方法
您的组件是直接引用构造函数定义的。我们将把这个构造函数包装在一个工厂函数中,该函数用一个连接的 sharedParams
对象替换传递给组件的 params
,该对象包含传递给链上任何组件的所有内容。 这是否 "safe" 足够取决于您。 想出另一种连接 aaa
和 ddd
的方法应该不难一旦您安装了自定义加载器。
简而言之,我们的自定义加载器将:
- 检索组件视图模型的原始构造函数(
VM
)
- 动态创建一个
factory
函数:
- 将传递的
params
添加到绑定上下文
- 使用 shared 参数构造
VM
实例
- returns 新视图模型
- 调用新创建的
factory
函数
在代码中:
ko.components.loaders.unshift({
loadViewModel: function loadViewModel(name, VM, callback) {
const factory = function (params, componentInfo) {
const bc = ko.contextFor(componentInfo.element);
// Overwrite sharedParams
bc.sharedParams = Object.assign(
{ },
bc.sharedParams || {},
params
);
return new VM(bc.sharedParams);
};
return callback(factory);
}
});
运行 示例
查看此链接 fiddle 以自己使用自定义加载程序。我包含了几个嵌套的组件结构,以表明不再需要手动传递 params
。
我做的是当C创建D时,C传递一个参数作为对D的引用,D将自己赋值给这个参数。 param可以是一个observable,D的onDispose可以清空observable,让C知道D是否被disposed。
我正在用 KnockoutJS 设计一个 "medium" 大小的应用程序,我想知道如何在组件之间发送事件。
想象一下 KnockoutJS 中的嵌套组件层次结构:
Root Viewmodel -> A -> B -> C
-> D
D 如何对来自 C 的消息做出反应?明显的 knockoutJS 方法是让 C 写入一个作为参数传递的 observable,并与 D 共享这个 observable,D 对这个 observable 的变化做出反应。
我不喜欢这种方法的是,A 和 B 需要了解消息,并且 A 和 B 会通过其参数主动转发处理程序。使用正常的依赖注入方法,我可以将组件 C 和 D 直接相互连接起来,例如通过在 A 和 B 不知情的情况下将 D 注入 C。
所以我的问题是:
- 有没有办法手动连接根视图模型中的组件(例如,通过拦截组件创建)?
或改写:
- 如何在不通过其父组件的情况下从主视图模型配置嵌套组件?
ko.components.register("aaa", {
viewModel: function (params) { this.handler = params.handler; },
template: "<span>A</span> <bbb params='handler: handler'></bbb>"
});
ko.components.register("bbb", {
viewModel: function (params) { this.handler = params.handler; },
template: "<span>B</span> <ccc params='handler: handler'></ccc>"
});
ko.components.register("ccc", {
viewModel: function (params) { this.handler = params.handler; },
template: "<span>C</span> <button data-bind='click: handler'>OK</button>"
});
ko.components.register("ddd", {
viewModel: function (params) {
var self = this;
this.text = ko.observable("No event received!");
if (params.onClick) params.onClick.subscribe(function () {
self.text("Event Received!");
});
},
template: "<span>D</span> <span data-bind='text:text'/>"
});
ko.applyBindings({
onClick: ko.observable()
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<p><aaa params='handler: onClick'> </aaa></p>
<p><ddd params='onClick: onClick'> </ddd></p>
一个我个人觉得很丑陋的解决方案,但是可以被认为是这个问题的有效答案:
您可以使用 $root
、$parents[i]
或 $parent
来引用组件绑定上下文中的任何层。我从您的示例中删除了一些 params
以向您展示它是如何工作的。它显示了两种口味:
- 从
params
属性里面的bindingContext
获取一个依赖(首选) - 访问
bindingContext
内部组件模板(不推荐)
ko.components.register("aaa", {
viewModel: function (params) { },
template: "<span>A</span> <bbb></bbb>"
});
ko.components.register("bbb", {
viewModel: function (params) { },
template: "<span>B</span> <ccc></ccc>"
});
ko.components.register("ccc", {
viewModel: function (params) { },
// This hides the dependency in the template. It's better to pass it in the DOM via `params`
template: "<span>C</span> <button data-bind='click: $root.onClick'>OK</button>"
});
ko.components.register("ddd", {
viewModel: function (params) {
var self = this;
this.text = ko.observable("No event received!");
if (params.onClick) params.onClick.subscribe(function () {
self.text("Event Received!");
});
},
template: "<span>D</span> <span data-bind='text:text'/>"
});
ko.applyBindings({
onClick: ko.observable()
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<p><aaa> </aaa></p>
<!-- This approach isn't *that* ugly, since the dependency is made explicit in the DOM -->
<p><ddd params='onClick: $root.onClick'> </ddd></p>
这里的弱点是你创建了一个难以 document/check 的 DOM 结构的固定依赖。我通常在使用 $parent
...
在这种情况下,我认为最好使用 EventEmitter https://github.com/Olical/EventEmitter
通过这种方式,您可以创建 EventEmitter
的全局 ee
实例,例如在主 html
window.ee = new EventEmitter()
然后你可以在组件之间添加监听器或触发动作:
对于组件 C 中的示例,您可以通过以下方式触发事件:window.ee.emitEvent('some-name-for-the-event', someArgumentsArray)
并且在您的 D 组件中,只需为该事件添加一个侦听器即可做出相应的反应,例如 window.ee.addListener('some-name-for-the-event', function (params) {
// your own code to handle the event
});
Is there a way to manually wire up the components inside the root viewmodel (e.g. by intercepting component creation)?
(emphasis mine)
淘汰机制
您可以通过使用方法 getConfig
、loadComponent
、loadTemplate
和方法的任意组合将对象添加到 ko.components.loaders
数组来注入 custom component loaders loadViewModel
.
由于您要在视图模型之间创建连接,因此这是我们需要定义的唯一方法。来自文档:
loadViewModel(name, viewModelConfig, callback)
The viewModelConfig value is simply the viewModel property from any componentConfig object. For example, it may be a constructor function, ...
方法
您的组件是直接引用构造函数定义的。我们将把这个构造函数包装在一个工厂函数中,该函数用一个连接的 sharedParams
对象替换传递给组件的 params
,该对象包含传递给链上任何组件的所有内容。 这是否 "safe" 足够取决于您。 想出另一种连接 aaa
和 ddd
的方法应该不难一旦您安装了自定义加载器。
简而言之,我们的自定义加载器将:
- 检索组件视图模型的原始构造函数(
VM
) - 动态创建一个
factory
函数:- 将传递的
params
添加到绑定上下文 - 使用 shared 参数构造
VM
实例 - returns 新视图模型
- 将传递的
- 调用新创建的
factory
函数
在代码中:
ko.components.loaders.unshift({
loadViewModel: function loadViewModel(name, VM, callback) {
const factory = function (params, componentInfo) {
const bc = ko.contextFor(componentInfo.element);
// Overwrite sharedParams
bc.sharedParams = Object.assign(
{ },
bc.sharedParams || {},
params
);
return new VM(bc.sharedParams);
};
return callback(factory);
}
});
运行 示例
查看此链接 fiddle 以自己使用自定义加载程序。我包含了几个嵌套的组件结构,以表明不再需要手动传递 params
。
我做的是当C创建D时,C传递一个参数作为对D的引用,D将自己赋值给这个参数。 param可以是一个observable,D的onDispose可以清空observable,让C知道D是否被disposed。