支持交互

事件监听器

在实例化节点时,为虚拟节点指定事件监听器的方法与指定任何其他属性的方法相同。当输出 VNode 时,VNodeProperties 上事件监听器的名字会镜像到 HTMLElement 的等价事件上。虽然自定义部件的作者可以根据自己的选择命名事件,但通常也遵循类似的 onEventName 的命名约定。

函数属性(如事件处理程序)会自动绑定到实例化此虚拟节点的部件的 this 上下文。但是,如果将已绑定的函数传给属性值,将不会重复绑定给 this

处理 focus

输出 VNode 时,部件可以使用 VNodePropertiesfocus 属性来控制生成的 DOM 元素在渲染时是否获取焦点。这是一个特殊属性,它可接收一个 boolean 类型的对象或者是返回一个 boolean 类型的函数。

当直接传入 true 时,只有上一次的值不是 true 时,元素才会获取焦点(类似于常规属性变更检测)。而传入函数时,只要函数返回 true,元素就会获取焦点,而不管上一次返回值。

例如:

根据元素的顺序,下面的 “firstFocus” 输入框只会在初始化渲染时获取焦点,而 “subsequentFocus” 输入框在每次渲染时都会获取焦点,因为 focus 属性的值是函数。

src/widgets/FocusExample.tsx

基于函数的部件:

  1. import { create, tsx, invalidator } from '@dojo/framework/core/vdom';
  2. const factory = create({ invalidator });
  3. export default factory(function FocusExample({ middleware: { invalidator } }) {
  4. return (
  5. <div>
  6. <input key="subsequentFocus" type="text" focus={() => true} />
  7. <input key="firstFocus" type="text" focus={true} />
  8. <button onclick={() => invalidator()}>Re-render</button>
  9. </div>
  10. );
  11. });

基于类的部件:

  1. import WidgetBase from '@dojo/framework/core/WidgetBase';
  2. import { tsx } from '@dojo/framework/core/vdom';
  3. export default class FocusExample extends WidgetBase {
  4. protected render() {
  5. return (
  6. <div>
  7. <input key="subsequentFocus" type="text" focus={() => true} />
  8. <input key="firstFocus" type="text" focus={true} />
  9. <button onclick={() => this.invalidate()}>Re-render</button>
  10. </div>
  11. );
  12. }
  13. }

委托 focus

基于函数的部件可使用 focus 中间件为其子部件设置焦点,或者接受来自父部件的焦点。基于类的部件可使用 FocusMixin(来自 @dojo/framework/core/mixins/Focus)以相同的方式委托 focus。

FocusMixin 会给部件的类中添加一个 this.shouldFocus() 方法,而基于函数的部件使用 focus.shouldFocus() 中间件方法实现相同的目的。此方法会检查部件是否处于执行了获取焦点的状态(译注:即调用了 this.focus()),并且仅对单个调用返回 true,直到再次调用部件的 this.focus() 方法(基于函数的部件使用等价的 focus.focus())。

FocusMixin 或者 focus 中间件也会为部件的 API 添加一个 focus 函数属性。框架使用此属性的布尔结果来确定渲染时,部件(或其一个子部件)是否应获得焦点。通常,部件通过其 focus 属性将 shouldFocus 方法传递给特定的子部件或输出的节点上,从而允许父部件将焦点委托给其子部件。

基于函数的部件的示例,请参阅 Dojo 中间件参考指南中的 focus 中间件委派示例

下面基于类的部件示例,显示了在部件层次结构内和输出的 VNode 之间委托和控制焦点:

src/widgets/FocusableWidget.tsx

  1. import WidgetBase from '@dojo/framework/core/WidgetBase';
  2. import { tsx } from '@dojo/framework/core/vdom';
  3. import Focus from '@dojo/framework/core/mixins/Focus';
  4. interface FocusInputChildProperties {
  5. onFocus: () => void;
  6. }
  7. class FocusInputChild extends Focus(WidgetBase)<FocusInputChildProperties> {
  8. protected render() {
  9. /*
  10. The child widget's `this.shouldFocus()` method is assigned directly to the
  11. input node's `focus` property, allowing focus to be delegated from a higher
  12. level containing parent widget.
  13. The input's `onfocus()` event handler is also assigned to a method passed
  14. in from a parent widget, allowing user-driven focus changes to propagate back
  15. into the application.
  16. */
  17. return <input onfocus={this.properties.onFocus} focus={this.shouldFocus} />;
  18. }
  19. }
  20. export default class FocusableWidget extends Focus(WidgetBase) {
  21. private currentlyFocusedKey = 0;
  22. private childCount = 5;
  23. private onFocus(key: number) {
  24. this.currentlyFocusedKey = key;
  25. this.invalidate();
  26. }
  27. /*
  28. Calling `this.focus()` resets the widget so that `this.shouldFocus()` will return true when it is next invoked.
  29. */
  30. private focusPreviousChild() {
  31. --this.currentlyFocusedKey;
  32. if (this.currentlyFocusedKey < 0) {
  33. this.currentlyFocusedKey = this.childCount - 1;
  34. }
  35. this.focus();
  36. }
  37. private focusNextChild() {
  38. ++this.currentlyFocusedKey;
  39. if (this.currentlyFocusedKey === this.childCount) {
  40. this.currentlyFocusedKey = 0;
  41. }
  42. this.focus();
  43. }
  44. protected render() {
  45. /*
  46. The parent widget's `this.shouldFocus()` method is passed to the relevant child element
  47. that requires focus, based on the simple previous/next widget selection logic.
  48. This allows focus to be delegated to a specific child node based on higher-level logic in
  49. a container/parent widget.
  50. */
  51. return (
  52. <div>
  53. <button onclick={this.focusPreviousChild}>Previous</button>
  54. <button onclick={this.focusNextChild}>Next</button>
  55. <FocusInputChild
  56. key={0}
  57. focus={this.currentlyFocusedKey === 0 ? this.shouldFocus : undefined}
  58. onFocus={() => this.onFocus(0)}
  59. />
  60. <FocusInputChild
  61. key={1}
  62. focus={this.currentlyFocusedKey === 1 ? this.shouldFocus : undefined}
  63. onFocus={() => this.onFocus(1)}
  64. />
  65. <FocusInputChild
  66. key={2}
  67. focus={this.currentlyFocusedKey === 2 ? this.shouldFocus : undefined}
  68. onFocus={() => this.onFocus(2)}
  69. />
  70. <FocusInputChild
  71. key={3}
  72. focus={this.currentlyFocusedKey === 3 ? this.shouldFocus : undefined}
  73. onFocus={() => this.onFocus(3)}
  74. />
  75. <FocusInputChild
  76. key={4}
  77. focus={this.currentlyFocusedKey === 4 ? this.shouldFocus : undefined}
  78. onFocus={() => this.onFocus(4)}
  79. />
  80. </div>
  81. );
  82. }
  83. }