可观察对象
可观察对象 是具有可观察属性的对象。这意味着当此类属性的值发生变化时,可观察对象会触发事件,并且更改会反映在监听该事件的代码的其他部分中。
可观察对象是 CKEditor 5 框架 的常见构建块。它们在 UI 中特别受欢迎,View
类及其子类从可观察接口中获益最多:它是 绑定到可观察对象的模板 使用户界面变得动态且交互式。一些基本类,如 Editor
或 Command
也是可观察对象。
任何类都可以成为可观察对象;您需要做的就是将 Observable
混入其中
import { ObservableMixin, mix } from 'ckeditor5';
class AnyClass {
// Any class definition.
// ...
}
mix( AnyClass, ObservableMixin );
可观察对象在管理应用程序状态时非常有用,应用程序状态可能是动态的,而且通常是在应用程序组件之间集中且共享的。一个可观察对象还可以使用 属性绑定 将其状态(或其部分)传播到另一个可观察对象。
可观察对象也可以 装饰其方法,这使得可以使用事件监听器来控制它们的执行,从而使外部代码对它们的执行行为具有一定的控制权。
# 使属性可观察
在将 Observable
混入您的类后,您可以定义可观察属性。为此,请使用 set()
方法。
让我们创建一个名为 Button
的简单 UI 视图(组件),它包含几个属性,看看它们是什么样子的
class Button extends View {
constructor() {
super();
// This property is not observable.
// Not all properties must be observable, it's always up to you!
this.type = 'button';
const bind = this.bindTemplate;
// this.label is observable but undefined.
this.set( 'label' );
// this.isOn is observable and false.
this.set( 'isOn', false );
// this.isEnabled is observable and true.
this.set( 'isEnabled', true );
// More observable's properties.
// ...
}
}
请注意,由于 Button
扩展了 View
类(它已经是可观察的),因此您不需要混入 ObservableMixin
。
set()
方法可以接受一个键值对对象来缩短代码。了解这一点后,使属性可观察就像下面这样简单
this.set( {
label: undefined,
isOn: false,
isEnabled: true
} );
最后,让我们创建一个新的视图,看看它是如何与世界通信的。
每次 label
属性发生变化时,视图都会触发 change:label
事件,该事件包含有关其过去状态和新值的信息。change:isEnabled
和 change:isOn
事件将分别为 isEnabled
和 isOn
的更改触发。
const view = new Button();
view.on( 'change:label', ( evt, propertyName, newValue, oldValue ) => {
console.log(
`#${ propertyName } has changed from "${ oldValue }" to "${ newValue }"`
);
} )
view.label = 'Hello world!'; // -> #label has changed from "undefined" to "Hello world!"
view.label = 'Bold'; // -> #label has changed from "Hello world!" to "Bold"
view.type = 'submit'; // Changing a regular property fires no event.
视图触发的事件用于更新 DOM 并使组件变得动态。让我们给我们的视图一些模板,并将其绑定到我们创建的可观察属性。
您可以在专门的 指南 中了解有关编辑器 UI 和模板系统的更多信息。
class Button extends View {
constructor() {
super();
// Previously defined properties.
// ...
// This template will have the following symbolic representation in DOM:
//
// <button class="[ck-disabled] ck-[on|off]" type="button">
// {{ this.label }}
// </button>
//
this.setTemplate( {
tag: 'button',
attributes: {
class: [
// The 'ck-on' and 'ck-off' classes toggle according to the #isOn property.
bind.to( 'isOn', value => value ? 'ck-on' : 'ck-off' ),
// The 'ck-enabled' class appears when the #isEnabled property is false.
bind.if( 'isEnabled', 'ck-disabled', value => !value )
],
type: this.type
},
children: [
{
// The text of the button is bound to the #label property.
text: bind.to( 'label' )
}
]
} );
}
}
由于 label
、isOn
和 isEnabled
是可观察的,因此任何更改都将立即反映在 DOM 中
const button = new Button();
// Render the button to create its #element.
button.render();
button.label = 'Bold'; // <button class="ck-off" type="button">Bold</button>
button.isOn = true; // <button class="ck-on" type="button">Bold</button>
button.label = 'B'; // <button class="ck-on" type="button">B</button>
button.isOff = false; // <button class="ck-off" type="button">B</button>
button.isEnabled = false; // <button class="ck-off ck-disabled" type="button">B</button>
# 属性绑定
一个可观察对象也可以将自己的状态(或其一部分)传播到另一个可观察对象,以简化代码,例如,避免使用多个 change:property
事件监听器。要开始绑定对象属性,请确保这两个对象(类)都混入了 Observable
,然后使用 bind()
方法来创建绑定。
# 简单绑定
使用上一节中的粗体按钮实例,将其绑定到粗体命令。这将使按钮使用某些命令属性并仅用几行代码自动执行用户界面。
粗体命令是编辑器的一个实际命令(由 BoldEditing
注册),它提供两个可观察属性:value
和 isEnabled
。要获取命令,请使用 editor.commands.get( 'bold' )
。
请注意,Button
和 Command
类都是 可观察的,这就是您可以绑定它们的属性的原因。
const button = new Button();
const command = editor.commands.get( 'bold' );
任何“像样的”按钮都必须在命令变为禁用时更新其外观。一个简单的属性绑定可以执行此操作,如下所示
button.bind( 'isEnabled' ).to( command );
之后
button.isEnabled
**立即等于**command.isEnabled
,- 每当
command.isEnabled
发生变化时,button.isEnabled
将立即反映其值, - 因为按钮的模板将其类绑定到
button.isEnabled
,所以按钮的 DOM 元素也将被更新。
请注意,command.isEnabled
**必须** 使用 set()
方法定义,以便绑定变得动态。在本例中,我们很幸运,因为 isEnabled
是编辑器中每个命令的标准可观察属性。但请记住,当您创建自己的可观察类时,使用 set()
方法是定义可观察属性的唯一方法。
# 重命名属性
现在让我们深入研究一下 bind( /* ... */ ).to( /* ... */ )
语法。事实上,最后一个示例对应于以下代码
const button = new Button();
const command = editor.commands.get( 'bold' );
button.bind( 'isEnabled' ).to( command, 'isEnabled' );
您可能已经注意到了 to( /* ... */ )
接口,它有助于指定属性的名称(或者只是在绑定中“重命名”属性)。
Button
和 Command
类都共享相同的 isEnabled
属性,这使我们能够缩短代码。但是,如果我们决定将 Button#isOn
绑定到 Command#value
,那么代码将如下所示
button.bind( 'isOn' ).to( command, 'value' );
属性在绑定中被“重命名”,从现在起,每当 command.value
发生变化时,button.isOn
的值都会反映它。
# 处理属性值
另一个用例是处理绑定属性值,例如,当按钮仅在满足特定条件时才禁用。将回调作为第三个参数传递允许实现自定义逻辑。
在下面的示例中,仅当 command.value
等于 'heading1'
时,isEnabled
属性才会被设置为 true
。
const command = editor.commands.get( 'heading' );
button.bind( 'isOn' ).to( command, 'value', value => value === 'heading1' );
# 绑定多个属性
可以一次绑定多个属性以简化代码
const button = new Button();
const command = editor.commands.get( 'bold' );
button.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
这与以下内容相同
button.bind( 'isOn' ).to( command, 'value' );
button.bind( 'isEnabled' ).to( command, 'isEnabled' );
在上述绑定中,button.isEnabled
的值将反映 command.isEnabled
,而 button.isOn
的值将反映 command.value
。
请注意,命令的 value
属性也在绑定中被“重命名”,就像在 上一个示例 中一样。
# 使用多个可观察对象绑定
绑定可以包含多个可观察对象,将多个属性组合在自定义回调函数中。让我们创建一个按钮,该按钮仅在 command
启用且 编辑文档(也是一个 Observable
)处于焦点时启用
const button = new Button();
const command = editor.commands.get( 'bold' );
const editingDocument = editor.editing.view.document;
button.bind( 'isEnabled' ).to( command, 'isEnabled', editingDocument, 'isFocused',
( isCommandEnabled, isDocumentFocused ) => isCommandEnabled && isDocumentFocused );
绑定使 button.isEnabled
的值依赖于 command.isEnabled
和 editingDocument.isFocused
,如函数中所指定:两者都必须为 true
,按钮才会启用。
# 使用可观察对象数组绑定
可以将同一个属性绑定到可观察对象的数组。让我们将我们的按钮绑定到多个命令,以便每个命令都必须启用,按钮才会启用
const button = new Button();
const commands = [ commandA, commandB, commandC ];
button.bind( 'isEnabled' ).toMany( commands, 'isEnabled', ( isAEnabled, isBEnabled, isCEnabled ) => {
return isAEnabled && isBEnabled && isCEnabled;
} );
可以使用扩展运算符 (...
) 和 Array.every()
方法来简化绑定
const commands = [ commandA, commandB, commandC ];
button.bind( 'isEnabled' ).toMany( commands, 'isEnabled', ( ...areEnabled ) => {
return areEnabled.every( isCommandEnabled => isCommandEnabled );
} );
这种绑定在以下情况下可能很有用,例如,当一个按钮打开一个包含其他命令按钮的下拉菜单时,如果所有命令都未启用,则该按钮应被禁用。
# 释放绑定
如果您不再希望对象的属性被绑定,可以使用 unbind()
方法。
您可以指定属性的名称来有选择地取消绑定它们
const button = new Button();
const command = editor.commands.get( 'bold' );
button.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// More bindings.
// ...
// From now on, button#isEnabled is no longer bound to the command.
button.unbind( 'isEnabled' );
或者,您可以通过不带参数地调用该方法来取消所有绑定
const button = new Button();
const command = editor.commands.get( 'bold' );
button.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// More bindings.
// ...
// Both #isEnabled and #isOn properties are independent back again.
// They will retain the last values determined by the bindings, though.
button.unbind();
# 装饰对象方法
装饰对象方法将它们转换为事件驱动的,而不改变其原始行为。
当一个方法被装饰时,会创建一个同名事件,并在每次执行该方法时触发。通过监听事件,可以取消执行,更改方法的参数或返回值。这提供了额外的灵活性,例如,让第三方代码以某种方式与装饰其方法的核心类进行交互。
使用 decorate()
方法可以进行装饰。装饰你之前在 上一节 中创建的 Button
类中的 focus
方法,看看它能提供什么。
class Button extends View {
constructor() {
// Setting the template and bindings.
// ...
this.decorate( 'focus' );
}
/**
* Focuses the button.
*
* @param {Boolean} force When `true`, the button will be focused again, even if already
* focused in DOM.
* @returns {Boolean} `true` when the DOM element was focused in DOM, `false` otherwise.
*/
focus( force ) {
console.log( `Focusing button, force argument="${ force }"` );
// Unless forced, the button will only focus when not already focused.
if ( force || document.activeElement != this.element ) {
this.element.focus();
return true;
}
return false;
}
}
# 取消执行
由于 focus()
方法现在是事件驱动的,因此可以从外部控制它。例如,可以停止对某些参数的聚焦。请注意,用于拦截默认操作的 high
监听器 优先级
const button = new Button();
// Render the button to create its #element.
button.render();
// The logic controlling the behavior of the button.
button.on( 'focus', ( evt, [ isForced ] ) => {
// Disallow forcing the focus of this button.
if ( isForced === true ) {
evt.stop();
}
}, { priority: 'high' } );
button.focus(); // -> 'Focusing button, force argument="undefined"'
button.focus( true ); // Nothing is logged, the execution has been stopped.
# 更改返回值
可以使用事件监听器来控制装饰方法的返回值。返回值在事件数据中作为 return
属性传递。
const button = new Button();
// Render the button to create its #element.
button.render();
// The logic controlling the behavior of the button.
button.on( 'focus', ( evt, [ isForced ] ) => {
// Pretend the button wasn't focused if the focus was forced.
if ( isForced === true ) {
evt.return = false;
}
} );
console.log( button.focus() ); // -> true
console.log( button.focus( true ) ); // -> false
# 动态更改参数
就像返回值一样,传递给方法的参数可以在事件监听器中更改。请注意,用于拦截默认操作的 high
监听器 优先级
const button = new Button();
// Render the button to create its #element.
button.render();
// The logic controlling the behavior of the button.
button.on( 'focus', ( evt, args ) => {
// Always force the focus.
args[ 0 ] = true;
}, { priority: 'high' } );
button.focus(); // -> 'Focusing button, force="true"'
button.focus( true ); // -> 'Focusing button, force="true"'
我们每天都在努力使我们的文档保持完整。您是否发现过时信息?是否缺少内容?请通过我们的 问题追踪器 报告。
随着 42.0.0 版本的发布,我们重写了大部分文档,以反映新的导入路径和功能。我们感谢您的反馈,帮助我们确保其准确性和完整性。