Angular Material 18+ 高级教程 – CDK Accessibility の ListKeyManager
介绍
ListKeyManager 的作用是让我们通过 keyboard 去操作 List Items。
一个典型的例子:Menu

有 4 个步骤:
tab to menu
enter 打开 menu list
按上下键选择 item
enter 选中 item
ListKeyManager 主要是负责第三个步骤,按上下键的功能。
ListKeyManager,ActiveDescendantKeyManager,FocusKeyManager
ListKeyManager 只是一个普通的 class,它不是 Dependency injection Provider 哦,所以使用它的方式是 new ListKeyManager()。

ActiveDescendantKeyManager 是 ListKeyManager 的派生类,它也没有什么特别的,只是重载了 ListKeyManager 的一个方法而已

从方法名字可以猜得出来,在按上下键时,target item 会被 set active styles。
FocusKeyManager 也是 ListKeyManager 的派生类

从方法名字可以猜得出来,在按上下键时,target item 会被 focus。
总结:
主要处理上下键核心功能的是 ListKeyManager
ActiveDescendantKeyManager 只是多了一个 set item active styles 而已
FocusKeyManager 只是多了一个 focus item 而已
ListKeyManager
我们直接看代码学习吧,毕竟它的原理太简单了。
初始化 ListKeyManager

初始化 ListKeyManager 需要传入 item list,它可以是一个 QueryList 或者 Array。
提醒:这个接口目前 v17.3.0 还没有跟上 Angular 新潮流 -- Signal。QueryList 在 Signal-based Query 中已经不公开了,我们根本拿不到 QueryList。
相关 Github Issue – ListKeyManager: Support Signal-based Query。对 Query 不熟悉的朋友,可以看这篇 Angular 17+ 高级教程 – Component 组件 の Query Elements。
更新:v17.3.2 已经支持 Signal-based Query 了。

由于要想监听 Signal value change 只能透过 effect,而 effect 依赖 Dependancy Injection,所以我们必须传入 Injector,这比使用 QueryList 繁琐了一些。
好,回到主题。
list item 必须是一个对象,同时实现 ListKeyManagerOption 接口。

下面我会用 ActiveDescendantKeyManager 作为例子,所以我们也看看 ActiveDescendantKeyManager 的 constructor。

item 必须实现 Highlightable 接口。
好,搞清楚类型后,我们来初始化一个 ActiveDescendantKeyManager。
下面这个是 Item class,它依要求实现了 Highlightable 接口。
class Item implements Highlightable {
active = false;
setActiveStyles(): void {
this.active = true;
}
setInactiveStyles(): void {
this.active = false;
}
}
初始化 ActiveDescendantKeyManager
export class AppComponent {
constructor() {
// 1. 创建 Item List
const items = [new Item(), new Item(), new Item(), new Item()];
// 2. 初始化 ActiveDescendantKeyManager
const keyManager = new ActiveDescendantKeyManager(items);
}
}
setActiveItem
通过 ListKeyManager 的一些方法,我们可以选择要 active 哪一个 item
console.log(items[0].active); // false
keyManager.setFirstItemActive();
console.log(items[0].active); // true
调用 setFirstItemActive,它内部会执行 items[0].setActiveStyles,然后 items[0].active 就变成 true 了。
keyManager.setNextItemActive();
console.log(items[0].active); // false
console.log(items[1].active); // true
setNextItemActive 会 active 下一个 item,同时把当前的 inactive。
有 next 就有 prev,有 first 就有 last
keyManager.setPreviousItemActive(); // 前一个
keyManager.setLastItemActive(); // 最后一个
当然也有直接指定第几个 index 要 active 的
keyManager.setActiveItem(2); // active item index 2
skip disabled
Item 实现的接口 ListKeyManagerOptions 有一个 optional 的属性 -- disabled

当 KeyManager 选择 active item 时,它会避开 disabled item。
修改 class Item
class Item implements Highlightable {
// 1. 添加 init value
constructor(item?: Partial<Item>) {
Object.assign(this, item);
}
active = false;
// 2. 添加一个 disabled 属性
disabled = false;
setActiveStyles(): void {
this.active = true;
}
setInactiveStyles(): void {
this.active = false;
}
}
调用
// 1. first, last, middle items 都是 disabled
const items = [
new Item({ disabled: true }),
new Item(),
new Item({ disabled: true }),
new Item(),
new Item({ disabled: true }),
]; const keyManager = new ActiveDescendantKeyManager(items);
keyManager.setFirstItemActive();
console.log(items.map(item => item.active)); // [false, true, false, false, false]
由于第一个 item 是 disabled,所以 setFirstItemActive 选了第二个 item 作为 first。
setLastItemActive 也是如此
keyManager.setLastItemActive();
console.log(items.map(item => item.active)); // [false, false, false, true, false]
setNextItemActive 也是如此
keyManager.setFirstItemActive();
keyManager.setNextItemActive();
console.log(items.map(item => item.active)); // [false, false, false, true, false]
setPreviousItemActive 也是如此
keyManager.setLastItemActive();
keyManager.setPreviousItemActive();
console.log(items.map(item => item.active)); // [false, true, false, false, false]
setActiveItem 则不同,不管 item 是否是 disabled,它都照样 set active。
keyManager.setActiveItem(0);
console.log(items.map(item => item.active)); // [true, false, false, false, false]
所以使用 setActiveItem 我们需要自己注意 disabled。
另外 -1 代表全部不选。
keyManager.setActiveItem(-1);
console.log(items.map(item => item.active)); // [false, false, false, false, false]
skipPredicate
disabled item 之所以会被 skip 掉是因为在 set first / last / next / prev active item 时,ListKeyManager 会做一个检测


如果 item 是 disabled 那 index 就会累加 / 累减去下一个或前一个。
如果想自定义 skip 机制,可以透过 ListKeyManager.skipPredicate 方法,把 skipPredicateFn 放进去。

keyManager.skipPredicate(item => {
// 判断是否要 skip 掉这个 item
return true; // return true 就 skip
});
提醒:setActiveItem 内部没有调用 _setActiveItemByIndex 方法,所以它没有 skipPredicate 机制。
withWrap 循环
如果当前 active item 已经在最后一个,而我们继续 setNextItemActive 会怎样?
const items = [new Item(), new Item(), new Item(), new Item(), new Item()]; const keyManager = new ActiveDescendantKeyManager(items);
keyManager.setLastItemActive();
keyManager.setNextItemActive();
console.log(items.map(item => item.active)); // [false, false, false, false, true]
答案是原地不动
如果我们想它 rotate 循环回到第一个,只需要调用 KeyManagerList.withWrap 方法就可以了
const keyManager = new ActiveDescendantKeyManager(items).withWrap(); // 加一个 withWrap keyManager.setLastItemActive();
keyManager.setNextItemActive();
console.log(items.map(item => item.active)); // [true, false, false, false, false]
change Observable
想监听 active item 的变化,可以透过 change 属性
keyManager.change.subscribe(activeItemIndex => {
console.log(activeItemIndex); // 0, 1
});
keyManager.setFirstItemActive();
keyManager.setNextItemActive();
它是一个 RxJS Subject。
updateActiveItem
updateActiveItem 是 setActiveItem 的底层调用

updateActiveItem 比 setActiveItem 少了一个 change 事件发布。
另外,如果是 ActiveDescendantKeyManager 的话

updateActiveItem 比 setActiveItem 还少了 setInactiveStyles 和 setActiveStyles 的调用。
activeItem & activeItemIndex
顾名思义,就是获取当前的 active item 和 index,如果没有它会返回 null 和 -1。
console.log([keyManager.activeItem, keyManager.activeItemIndex]); // [null, null] keyManager.setLastItemActive(); console.log(keyManager.activeItemIndex); // 4
console.log(keyManager.activeItem!.active); // true
onKeydown
List Key Manager,我们上面都在讲 List 的部分,这里开始讲 Key 的部分。
Key 是 keyboard 的 key。
我们学习了许多操作 active item 的方法 -- first, last, prev, next
这些方法要结合 keydown 事件才会发光发热,比如按 ArrowDown 键要调用 setNextItemActive,按 ArrowUp 键要调用 setPreviousItemActive。
看代码
const keyManager = new ActiveDescendantKeyManager(items).withWrap(); keyManager.setFirstItemActive();
console.log(items.map(item => item.active)); // [true, false, false, false, false] // 1. 模拟一个 keydown
const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', keyCode: 40 });
keyManager.onKeydown(downEvent);
console.log(items.map(item => item.active)); // [false, true, false, false, false]
KeyListManager.onKeydown 会依据 event.key 和 keyCode 做出相应的操作。
比如 ArrowDown 它会 setNextItemActive,所以 item[1] 变成了 active。
withVerticalOrientation & withHorizontalOrientation
by default,onKeydown 指处理上下键,左右键是不处理的。
如果我们要支持左右键,那需要设置 withHorizontalOrientation。
在没有设置 withHorizontalOrientation 的情况下,按左右键 item active 不会产生任何变化。
const keyManager = new ActiveDescendantKeyManager(items).withWrap();
keyManager.setFirstItemActive();
const rightEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', keyCode: 39 });
keyManager.onKeydown(rightEvent);
console.log(items.map(item => item.active)); // [true, false, false, false, false]
加上 withHorizontalOrientation
const keyManager = new ActiveDescendantKeyManager(items).withWrap().withHorizontalOrientation('ltr');
ltr 是 left to right 的缩写,rtl 是 right to left 的缩写,它用来表达用户操作习惯 (比如说有些国家的字是从右边读到左边,有些则是左边读到右边,keyboard 的操作也有这样分类)
keyManager.setFirstItemActive();
const rightEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', keyCode: 39 });
keyManager.onKeydown(rightEvent);
console.log(items.map(item => item.active)); // [false, true, false, false, false]
ltr 情况下,ArrowRight 键代表 next。
我们也可以关掉 default 的上下键处理
const keyManager = new ActiveDescendantKeyManager(items).withWrap().withVerticalOrientation(false); // 关掉上下键处理
keyManager.setFirstItemActive();
const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', keyCode: 40 });
keyManager.onKeydown(downEvent);
console.log(items.map(item => item.active)); // [true, false, false, false, false] 原封不动
withHomeAndEnd
by default,Home 键和 End 键是不处理的。我们可以通过 withHomeAndEnd 将它开启
const keyManager = new ActiveDescendantKeyManager(items).withWrap().withHomeAndEnd();
Home 键是 setFirstItemActive,End 键是 setLastItemActive
keyManager.setFirstItemActive();
const endEvent = new KeyboardEvent('keydown', { key: 'End', keyCode: 35 });
keyManager.onKeydown(endEvent);
console.log(items.map(item => item.active)); // [false, false, false, false, true] const homeEvent = new KeyboardEvent('keydown', { key: 'Home', keyCode: 36 });
keyManager.onKeydown(homeEvent);
console.log(items.map(item => item.active)); // [true, false, false, false, false]
withPageUpDown
by default,PageUp 键和 PageDown 键是不处理的。我们可以通过 withPageUpDown 将它开启
const keyManager = new ActiveDescendantKeyManager(items).withWrap().withPageUpDown(true, 2);

第一个参数是 enabled,第二参数 delta 是指每按一下要跳多少个 item,我写 2 就表示每按一下 PageDown 会执行 2 次 next。
keyManager.setFirstItemActive();
const pageDownEvent = new KeyboardEvent('keydown', { key: 'PageDown', keyCode: 34 });
keyManager.onKeydown(pageDownEvent);
console.log(items.map(item => item.active)); // [false, false, true, false, false]
const pageUpEvent = new KeyboardEvent('keydown', { key: 'PageUp', keyCode: 33 });
keyManager.onKeydown(pageUpEvent);
console.log(items.map(item => item.active)); // [true, false, false, false, false]
另外,不管有没有设置 withWrap 循环,PageDown 和 PageUp 都不会循环。
keyManager.setFirstItemActive();
const pageDownEvent = new KeyboardEvent('keydown', { key: 'PageDown', keyCode: 34 });
keyManager.onKeydown(pageDownEvent);
keyManager.onKeydown(pageDownEvent);
keyManager.onKeydown(pageDownEvent);
console.log(items.map(item => item.active)); // [false, false, false, false, true] 依然在最后一个,因为已经到底了
withAllowedModifierKeys
by default,按键如果搭配 modifier keys (Control, Alt, Shift, Meta / Windows) 是不处理的
keyManager.setFirstItemActive();
const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', keyCode: 40, altKey: true }); // ArrowDown + Alt Key
keyManager.onKeydown(downEvent);
console.log(items.map(item => item.active)); // [true, false, false, false, false] 原封不动
我们可以通过 withAllowedModifierKeys 让它处理。
const keyManager = new ActiveDescendantKeyManager(items)
.withWrap()
.withAllowedModifierKeys(['ctrlKey', 'altKey', 'shiftKey', 'metaKey']);
不一定要四个都 allow,也可以指令部分 allow。
keyManager.setFirstItemActive();
const downEvent = new KeyboardEvent('keydown', { key: 'ArrowDown', keyCode: 40, altKey: true });
keyManager.onKeydown(downEvent);
console.log(items.map(item => item.active)); // [false, true, false, false, false] 会动了
tabOut
KeyManagerList 对 Tab 键是没有 List Item 处理的,它只是会转发事件而已。


keyManager.tabOut.subscribe(() => console.log('tab out')); // 监听 Tab 键事件
keyManager.setFirstItemActive();
const tabEvent = new KeyboardEvent('keydown', { key: 'Tab', keyCode: 9 });
keyManager.onKeydown(tabEvent); // 调用 tabOut.next()
console.log(items.map(item => item.active)); // [true, false, false, false, false] 原封不动
它只是一个小方便而已,我们也可以自己对 keyboard event 进行判断,并不是一定要依靠 KeyListManager 的 tabOut + onKeydown。
withTypeAhead
withTypeAhead 是用来做原生 DOM <select> typing select option 体验的。
我们 focus select,然后连续打几个字,select 就会去选择字母开头的 option。

首先每个 item 需要有一个 getLabel 方法来表示 item 是什么字。
这个是 ListKeyManagerOption 的接口

因为 withTypeAhead 是 optional 所以 getLabel 也是 optional,如果我们开启 withTypeAhead 要记得声明 getLabel 方法。

// 1. 每个 item 都有 label
const items = [
new Item({ label: 'toyota' }),
new Item({ label: 'tesla' }),
new Item({ label: 'triumph' }),
new Item({ label: 'tata' }),
new Item({ label: 'honda' }),
]; const keyManager = new ActiveDescendantKeyManager(items).withTypeAhead(); // 2. 开启 withTypeAhead
keyManager.setFirstItemActive();
// 3. 连续 keydown 几个字母
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 't', keyCode: 84 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 'e', keyCode: 69 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 's', keyCode: 83 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 'l', keyCode: 76 })); window.setTimeout(() => {
console.log(items.map(item => item.active)); // 4. [false, true, false, false, false] 变成 index 1 active 了
}, 300);
withTypeAhead 默认会有一个 200 milliseonds 的 RxJS debounce time

在这 200ms 内所有 keydown 的字母会把缓存起来,一直到停止 keydown 后的 200ms,ListKeyManager 才会把字母合并成字然后去匹配 item label。
匹配方式是 startsWith & ignorecase,匹配到第一个就 active 那个 item。

相关源码在 list-key-manager.ts
isTyping
isTyping 指的是 withTypeAhead 在 debounce time 的那段期间。
console.log(keyManager.isTyping()); // false
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 't', keyCode: 84 }));
console.log(keyManager.isTyping()); // true keyManager.onKeydown(new KeyboardEvent('keydown', { key: 'e', keyCode: 69 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 's', keyCode: 83 }));
keyManager.onKeydown(new KeyboardEvent('keydown', { key: 'l', keyCode: 76 })); window.setTimeout(() => {
console.log(items.map(item => item.active));
console.log(keyManager.isTyping()); // false
}, 300);
cancelTypeahead
cancelTypeahead 的作用是把 withTypeAhead typing 时累积的字母清掉。

destroy
KeyListManager 会监听 item list change (for QueryList 和 Signal 的话),如果已经不需要 KeyListManager 了的话,可以调用 destroy 方法,这样它会取消监听。
Items changes
假如原本 active 的是 item index 3,
然后 items 发生了变化,active item 变成了 index 5,
那么 activeItemIndex 会自动更新

但有一点要特别注意,假如 active item 没有在 new items 中,KeyListManager 并不会把 active item 设置成 null 哦。
如果这时我们对 active item 做操作的话,很可能会出现 item 指令已经 destroy 的 error。
KeyListManager with real DOM
上面例子用的是模拟 keydown event 主要是想让大家看得清楚 how it work。
这里我补上一个 real DOM 的例子,不过我就不再过多讲解了。
App Template
<ul class="item-list" appItemList>
<li class="item" appItem>Item1</li>
<li class="item" appItem>Item2</li>
<li class="item" appItem>Item3</li>
<li class="item" appItem disabled>Item4</li>
<li class="item" appItem>Item5</li>
<li class="item" appItem>Item6</li>
<li class="item" appItem>Item7</li>
<li class="item" appItem>Item8</li>
<li class="item" appItem>Item9</li>
<li class="item" appItem>Item10</li>
</ul>
appItemList 和 appItem 是指令。
App Styles
:host {
display: block;
padding: 56px;
}
.item-list {
display: flex;
flex-direction: column;
gap: 1px;
width: 256px;
border: 4px solid black;
/* 1. item list focused 会红框 */
&:focus {
border-color: red;
}
.item {
padding: 8px 12px;
cursor: pointer;
user-select: none;
/* 2. item disabled 会灰色 */
&[disabled] {
background-color: lightgray;
}
/* 3. active item 会是粉色 */
&.active {
background-color: pink;
color: red;
}
}
}
App 组件
@Component({
selector: 'app-root',
standalone: true,
imports: [ItemListDirective, ItemDirective],
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {}
只要 imports 指令就可以了,因为逻辑都写在指令里。
Item 指令
import type { Highlightable } from '@angular/cdk/a11y';
import { booleanAttribute, Directive, ElementRef, inject, input, signal } from '@angular/core';
@Directive({
selector: '[appItem]',
standalone: true,
host: {
// 2. 当 active 时添加 CSS class active
'[class.active]': 'isActive()',
},
})
// 1. 实现 Highlightable 接口
export class ItemDirective implements Highlightable {
readonly isActive = signal(false);
readonly disabledInput = input(false, { alias: 'disabled', transform: booleanAttribute });
setActiveStyles() {
this.isActive.set(true);
}
setInactiveStyles() {
this.isActive.set(false);
}
get disabled(): boolean {
return this.disabledInput();
}
readonly hostElement: HTMLElement = inject(ElementRef).nativeElement;
getLabel(): string {
// 3. label 拿节点的 text 就好了
return this.hostElement.textContent ?? '';
}
}
Item List 指令
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
import { afterNextRender, contentChildren, DestroyRef, Directive, inject } from '@angular/core';
import { ItemDirective } from './item.directive';
@Directive({
selector: '[appItemList]',
standalone: true,
host: {
// 1. 设置成 tabbable 不然没有地方给 user keydown
tabindex: '0',
'(keydown)': 'handleKeydown($event)',
},
})
export class ItemListDirective {
// 2. query items
readonly items = contentChildren(ItemDirective);
private keyManager!: ActiveDescendantKeyManager<ItemDirective>;
constructor() {
const destroyRef = inject(DestroyRef);
afterNextRender(() => {
// 3. 创建 KeyListManager
this.keyManager = new ActiveDescendantKeyManager(this.items())
.withWrap()
.withTypeAhead(500)
.withPageUpDown(true, 3)
.withHomeAndEnd();
// 5. 当指令销毁时,销毁 KeyListManager。
destroyRef.onDestroy(() => this.keyManager.destroy());
});
}
handleKeydown(event: KeyboardEvent) {
// 4. 接收 keydown event 然后转交给 ListKeyManager 处理
this.keyManager.onKeydown(event);
}
}
效果
上下键,withWrap 循环,disabled item

Home 键,End 键,PageUp 键,PageDown 键

withTypeAhead,typing item5 and then item7

FocusKeyManager 小知识

focus 有 origin 的概念,这个我们在上一篇有讲解过。
这里值得注意的是,FocusKeyManager 的 focus origin 默认是 'program',而且 KeyListManager 在任何时候都不会去改变它。
比如说 KeyListManager 的 onKeydown 方法,虽然它肯定是一个 keyboard event,但是 focus origin 依然是 ’program‘。
所以,我们有责任去维护这个 origin,在调用 KeyListManager.onKeydown 方法之前,先调用 FocusKeyManager.setFocusOrigin 方法,把 origin 换成 'keyboard',完成后再修改回去。
总结
ListKeyManager 是一个很常见的功能,Angular Material 的 List,Menu,Stepper 组件都用到了 ListKeyManager。
有兴趣的朋友可以自己去逛一下源码,这几个组件都挺复杂的,它们在 ListKeyManager 基础上有扩展了许多功能。
目录
上一篇 Angular Material 18+ 高级教程 – CDK Accessibility の Focus
下一篇 Angular Material 18+ 高级教程 – CDK Overlay
想查看目录,请移步 Angular 18+ 高级教程 – 目录
喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding
Angular Material 18+ 高级教程 – CDK Accessibility の ListKeyManager的更多相关文章
- Angular 学习笔记 ( CDK - Accessibility )
@angular/ckd 是 ng 对于 ui 组建的基础架构. 是由 material 团队开发与维护的, 之所以会有 cdk 看样子是因为在开发 material 的时候随便抽象一个层次出来给大家 ...
- Angular Material 教程之布局篇
Angular Material 教程之布局篇 (一) : 布局简介https://segmentfault.com/a/1190000007215707 Angular Material 教程之布局 ...
- Angular Material TreeTable Component 使用教程
一. 安装 npm i ng-material-treetable --save npm i @angular/material @angular/cdk @angular/animations -- ...
- Angular Material design设计
官网: https://material.io/design/ https://meterial.io/components 优秀的Meterial design站点: http://material ...
- Material使用11 核心模块和共享模块、 如何使用@angular/material
1 创建项目 1.1 版本说明 1.2 创建模块 1.2.1 核心模块 该模块只加载一次,主要存放一些核心的组件及服务 ng g m core 1.2.1.1 创建一些核心组件 页眉组件:header ...
- Siki_Unity_2-9_C#高级教程(未完)
Unity 2-9 C#高级教程 任务1:字符串和正则表达式任务1-1&1-2:字符串类string System.String类(string为别名) 注:string创建的字符串是不可变的 ...
- Angular Material Starter App
介绍 Material Design反映了Google基于Android 5.0 Lollipop操作系统的原生应用UI开发理念,而AngularJS还发起了一个Angular Material ...
- 关于 Angular引用Material出现node_modules/@angular/material/button-toggle/typings/button-toggle.d.ts(154,104): error TS2315: Type 'ElementRef' is not generic.问题
百度了好久 ,,,最后谷歌出来了.. 该错误可能来自于您将@ angular / material设置为6.0.0, 但所有其他Angular包都是5.x.您应该始终确保Material主要版本与An ...
- Angular Material & Hello World
前言 Angular Material(下称Material)的组件样式至少是可以满足一般的个人开发需求(我真是毫无设计天赋),也是Angular官方推荐的组件.我们通过用这个UI库来快速实现自己的i ...
- angular使用@angular/material 出现"export 'ɵɵinject' was not found in '@angular/core'
WARNING in ./node_modules/@angular/cdk/esm5/a11y.es5.js 2324:206-214 "export 'ɵɵinject' was not ...
随机推荐
- Three光源Target位置改变光照方向不变的问题及解决方法
0x00 楔子 在 Three.js 中,光源的目标(target)是一种用于指定光源方向的重要元素.在聚光灯中和定向光(DirectionalLight)中都有用到. 有时我们可能会遇到光源目标位置 ...
- 学习笔记--Java中的数据类型
Java中的数据类型 /** * Java中的数据类型: * 程序当中有很多的数据,每一个数据拥有与之相关的类型. * * * 1. 数据类型的作用: * 不同类型的数据占用的空间大小不同,数据类型的 ...
- linux部署Python UI自动化项目过程
1.安装chrome浏览器 下载 访问谷歌中文网站:Google Chrome 网络浏览器. 将页面滑到最下面,点击其他平台, 在弹出的页面选择linux 选择对应的系统版本进行下载. 下载后的deb ...
- Dubbo日志链路追踪TraceId选型
一.目的 开发排查系统问题用得最多的手段就是查看系统日志,但是在分布式环境下使用日志定位问题还是比较麻烦,需要借助 全链路追踪ID 把上下文串联起来,本文主要分享基于 Spring Boot + Du ...
- Charles 4.6 小茶杯 网络抓包工具
下载官网: https://www.charlesproxy.com/download 破解网站: Charles破解工具 (zzzmode.com)
- 【Java】PDF模板生成PDF文档
一.需求背景 客户要求一份文书,文书内容有一些表单项,例如: 1.基本的是和否 (单选框或复选框) 2.备注内容(纯文本信息) 3.单位,机构组织,人员,字典项(下拉选择) 4.用户数字签名(图片信息 ...
- 【转载】科研写作入门 —— 聊聊Science Research Writing for non-native Speakers of English这本书
原地址: https://zhuanlan.zhihu.com/p/623882027 平行侠: 今天我们聊一聊Science Research Writing for non-native Spea ...
- 一个简单的例子测试numpy和Jax的性能对比
参考: https://baijiahao.baidu.com/s?id=1725356123619612187&wfr=spider&for=pc 个人认为如果把Jax作为一款深度学 ...
- 区块链共识机制 —— PoW共识的Python实现
原始实现(python2 版本) https://github.com/santisiri/proof-of-work 依据python3特性改进后: #!/usr/bin/env python # ...
- 从baselines库的common/vec_env/vec_normalize.py看reinforcement learning算法中的reward shape方法
参考前文:https://www.cnblogs.com/devilmaycry812839668/p/15889282.html 2. REINFORCE算法实际代码中为什么会对一个episode ...