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