根据Knockout Documentation here,组件viewModel仅“按需”实例化,认为它被声明为:
<div data-bind='component: {
name: componentNameObservable,
params: { mode: "detailed-list", items: productsList }
}'></div>
考虑具有路由机制的单页应用程序的场景,例如SammyJS,Crossroads.js或任何其他。
当路由更改请求与路由模式匹配时,通常会为路由匹配事件创建路由库处理程序,以便为componentNameObservable设置新值。这将触发将新组件注入绑定元素。 最重要的是,在这个路由匹配处理程序中,假设我想执行在绑定的组件viewModel内声明的函数,刷新/设置视图模型数据,响应路由更改事件。怎么可能呢? 由于在这些情况下组件viewModel实例化由Knockout的内部组件绑定机制控制,我无法访问它的函数,因此我可以将它们作为要执行的回调引用。 如果可能的话,我会将一个组件viewModel函数作为回调参数传递给路由库路由匹配处理程序,然后执行回调函数,一切都会很好......但似乎没有那样工作... < / p>
我正在使用CommonJS注册组件以使用Browserify,如此处所述
http://knockoutjs.com/documentation/component-loaders.html#note-integrating-with-browserify
在这种情况下,组件视图模型上的module.exports必须公开其整个非实例化的构造函数,以便将任何require('myViewModel')转换为,使视图模型正确“new up up “绑定到元素。
有什么建议吗?
答案 0 :(得分:4)
假设我想执行在绑定的组件viewModel中声明的函数,以刷新/设置视图模型数据,响应路由更改事件。怎么可能?
在component registration documentation中,您有4种不同的选项来提供视图模型:
实际上,第4个选项是使用AMD模块返回其他3个选项中的一个。因此,只有3种可能的选项,无论是否使用require
。
如果您的SPA仅使用组件的单个实例,则可以使用第二个解决方案:创建视图模型,将其存储在始终在范围内的变量中(例如,在全局范围内或模块内),以及注册组件以使用它。这样,当组件由路由事件实例化时,您可以通过变量访问viewmodel以调用所需的功能。
如果您的SPA可以有多个不同的组件实例,或者您只是不想使用以前的解决方案,则必须使用第一个或第三个选项(关于这个问题,哪一个并不重要)。在这种情况下,您可以将回调函数传递给组件,该组件将在构造函数(第一个选项)或工厂方法(第三个选项)参数中可用。构造函数(或工厂)可以调用此回调以显示其功能。你可以实现这样的东西,但不一定完全像这样:
在您的申请的主要范围内
// Here you'll store the component APIs to access them:
var childrenComponentsApi = {};
// This will be passed as a callback, so that the child component
// can register the API
var registerChildComponentApi = function(api) {
childrenComponentsApi.componentX = api;
};
注意:拥有一个可以注册功能的对象非常重要,这样就不会丢失引用
在视图中:
<div data-bind='component: {
name: 'componentX',
params: { registerApi: registerChildComponentApi, /*other params*/ }
}'></div>
在组件视图模型构造函数(或工厂)主体中:
params.registerApi({ // The callback is available in thereceived params
func1: func1, // register the desired functions
func2: func2});
稍后,在主范围内,您可以访问组件的功能:
childrenComponentsApi.componentX.fun1(/ * params * /);
这不是完全正常工作的代码,但我希望它能让您了解如何实现所需的代码。
如果不立即调用API,此解决方案可以正常工作。当用户的操作调用该功能时,我使用了这个实现,因此我确信该组件已经被实例化了。
但是,在您的情况下,组件创建是异步的,因此您必须修改实现。至少有两种可能的方式:
1)更容易更改registerApi
实现并使用它来初始化。像这样:
在viewmodels构造函数中:
params.registerApi({
init: init, // initialization function
func1: func1, // other exposed functionality
func2: func2});
在主要范围内:
var registerChildComponentApi = function(api) {
childrenComponentsApi.componentX = api;
childrenComponentsApi.componentx.init(/* params*/)
}
在此实现中,在客户端组件运行回调之后调用init
,因此您可以确保该组件可用。
2)更复杂的解决方案涉及使用承诺。如果你的组件需要做异步操作来做好准备(比如做AJAX调用),你可以让它返回除了所有暴露的API之外的一个promise,这样组件就可以在它真正准备就绪时解决它,并且主要范围运行只有在承诺解决后才能使用API功能。像这样:
在视图模型构造函数中:
params.registerApi({
ready: ready, // promise created and solved by the component
func1: func1, // exposed functionality
func2: func2});
在主要范围内:
var registerChildComponentApi = function(api) {
childrenComponentsApi.componentX = api;
}
childrenComponentsApi.componentx.ready().then(
childrenComponentsApi.componentx.func1;
);
这些是一些实现示例,但它们可能有很多变化。例如,如果您只需要运行组件视图模型的init
函数,则可以向组件提供类似provideInit
的参数,并通过设置组件的{{1}来在consturctor中运行它。 }。像这样:
init
并且不要忘记也可以通过将所有必需的参数传递给构造函数来初始化组件的viewmodel。甚至可以将observable作为参数传递并从主范围修改它们。
我能给你的最后一个最好的建议是你标准化并正确记录<div data-bind='component: {
name: 'componentX',
params: { provideInit: provideInit, /*other params*/ }
}'></div>
var proviedInit: function(init) {
init(/* params */);
};
功能,以便以相同的方式实现和使用所有组件。
正如我在开始时所说,任何这些解决方案都可以直接实现,也可以使用AMD模块实现。即您可以直接注册提供视图模型构造函数的组件(第一个选项),或者将构造函数定义为AMD模块并使用第四个选项。