如何根据 RxJs 中的事件有条件地缓冲键输入
How do I conditionally buffer key input based on event in RxJs
我是 RxJs 的新手,还没有阅读过解决方案。评论中有更详细的解释,但基本上我想在按下特定键时处理组合键(我认为缓冲区会这样做)(比如按 "o" 会等待很短的时间等待其他键按下),但立即处理其他键输入(如果 "o" 未被按下,则除 "o" 之外的任何内容,或者 "o" 的 'timeout' 已通过)。
Observable.fromEvent(document, 'keydown')
// Now I want to only continue processing the event if the user pressed "o99" in series,
// as in pressed "o", followed by "9" and then another "9"
// I think I can do it with buffer
.buffer(() => Observable.timer(1000))
.map((e) => 'option-99')
// However I need to pass the keys through unbuffered if it's anything but an "o" (unless it is following an "o")
// In other words, "o99" is buffered or something, but "9" is not, and processed immediately
.map((e) => e.keyCode)
谢谢
您有一个非常常见的情况需要处理,即一个事件的相关操作取决于某个控制状态(即您有一个基本状态机)。基本上,如果您考虑这两个控制状态:SIMPLE_KEY
、COMBO
和这两个事件:keyup
和 keyupTimer
,那么:
- 处于
SIMPLE_KEY
状态
- 如果按下的键是
o
,那么你切换到COMBO
状态
- 如果不是
o
,那么我们保持相同的状态,并向下游传递密钥
- if timer event, then we pass a null value to be filtered out downstream
- 处于
COMBO
状态
- 我们累积按下的键
- 如果定时器事件,那么我们恢复到
SIMPLE_KEY
状态
因此,要将事件映射到动作,您需要知道所处的控制状态。运算符 scan
允许您根据累积状态决定如何处理事件。
可能是这样的 (https://jsfiddle.net/cbhmxaas/):
function getKeyFromEvent(ev) {return ev.key}
function isKeyPressedIsO(ev) {return getKeyFromEvent(ev) === 'o'}
const KEY = 'key';
const TIMER = 'timer';
const COMBO = 'combo';
const SIMPLE_KEY = 'whatever';
const timeSpanInMs = 1000;
const keyup$ = Rx.Observable.fromEvent(document, 'keyup')
.map(ev => ({event: KEY, data: getKeyFromEvent(ev)}));
const keyupTimer$ = keyup$.flatMapLatest(eventStruct =>
Rx.Observable.of(eventStruct)
// .tap(console.warn.bind(console, 'timer event'))
.delay(timeSpanInMs)
.map(() => ({event : TIMER, data: null}))
);
Rx.Observable.merge(keyup$, keyupTimer$)
// .tap(console.warn.bind(console))
.scan((acc, eventStruct) => {
if (acc.control === SIMPLE_KEY) {
if (eventStruct.event === KEY) {
if (eventStruct.data === `o`) {
return {control: COMBO, keyOrKeys : []}
}
else {
return {control: SIMPLE_KEY, keyOrKeys : eventStruct.data}
}
}
else {
// TIMER event
return {control: SIMPLE_KEY, keyOrKeys : null}
}
}
else {
// we only have two states, so it is COMBO state here
if (eventStruct.event === KEY) {
return {control: COMBO, keyOrKeys : acc.keyOrKeys.concat([eventStruct.data])}
}
else {
// this is a TIMER event, we only have two events
return {control: SIMPLE_KEY, keyOrKeys : acc.keyOrKeys}
}
}
}, {control: SIMPLE_KEY, keyOrKeys : null})
// .tap(console.warn.bind(console))
.filter(state => state.keyOrKeys) // TIMER event in SIMPLE_KEY state
.map (({control, keyOrKeys}) => {
// Here you associate your action
// if keyOrKeys is an array, then you have a combo
// else you have a single key
console.log('key(s) pressed', keyOrKeys)
return keyOrKeys
})
.subscribe (console.log.bind(console))
我是 RxJs 的新手,还没有阅读过解决方案。评论中有更详细的解释,但基本上我想在按下特定键时处理组合键(我认为缓冲区会这样做)(比如按 "o" 会等待很短的时间等待其他键按下),但立即处理其他键输入(如果 "o" 未被按下,则除 "o" 之外的任何内容,或者 "o" 的 'timeout' 已通过)。
Observable.fromEvent(document, 'keydown')
// Now I want to only continue processing the event if the user pressed "o99" in series,
// as in pressed "o", followed by "9" and then another "9"
// I think I can do it with buffer
.buffer(() => Observable.timer(1000))
.map((e) => 'option-99')
// However I need to pass the keys through unbuffered if it's anything but an "o" (unless it is following an "o")
// In other words, "o99" is buffered or something, but "9" is not, and processed immediately
.map((e) => e.keyCode)
谢谢
您有一个非常常见的情况需要处理,即一个事件的相关操作取决于某个控制状态(即您有一个基本状态机)。基本上,如果您考虑这两个控制状态:SIMPLE_KEY
、COMBO
和这两个事件:keyup
和 keyupTimer
,那么:
- 处于
SIMPLE_KEY
状态- 如果按下的键是
o
,那么你切换到COMBO
状态 - 如果不是
o
,那么我们保持相同的状态,并向下游传递密钥 - if timer event, then we pass a null value to be filtered out downstream
- 如果按下的键是
- 处于
COMBO
状态- 我们累积按下的键
- 如果定时器事件,那么我们恢复到
SIMPLE_KEY
状态
因此,要将事件映射到动作,您需要知道所处的控制状态。运算符 scan
允许您根据累积状态决定如何处理事件。
可能是这样的 (https://jsfiddle.net/cbhmxaas/):
function getKeyFromEvent(ev) {return ev.key}
function isKeyPressedIsO(ev) {return getKeyFromEvent(ev) === 'o'}
const KEY = 'key';
const TIMER = 'timer';
const COMBO = 'combo';
const SIMPLE_KEY = 'whatever';
const timeSpanInMs = 1000;
const keyup$ = Rx.Observable.fromEvent(document, 'keyup')
.map(ev => ({event: KEY, data: getKeyFromEvent(ev)}));
const keyupTimer$ = keyup$.flatMapLatest(eventStruct =>
Rx.Observable.of(eventStruct)
// .tap(console.warn.bind(console, 'timer event'))
.delay(timeSpanInMs)
.map(() => ({event : TIMER, data: null}))
);
Rx.Observable.merge(keyup$, keyupTimer$)
// .tap(console.warn.bind(console))
.scan((acc, eventStruct) => {
if (acc.control === SIMPLE_KEY) {
if (eventStruct.event === KEY) {
if (eventStruct.data === `o`) {
return {control: COMBO, keyOrKeys : []}
}
else {
return {control: SIMPLE_KEY, keyOrKeys : eventStruct.data}
}
}
else {
// TIMER event
return {control: SIMPLE_KEY, keyOrKeys : null}
}
}
else {
// we only have two states, so it is COMBO state here
if (eventStruct.event === KEY) {
return {control: COMBO, keyOrKeys : acc.keyOrKeys.concat([eventStruct.data])}
}
else {
// this is a TIMER event, we only have two events
return {control: SIMPLE_KEY, keyOrKeys : acc.keyOrKeys}
}
}
}, {control: SIMPLE_KEY, keyOrKeys : null})
// .tap(console.warn.bind(console))
.filter(state => state.keyOrKeys) // TIMER event in SIMPLE_KEY state
.map (({control, keyOrKeys}) => {
// Here you associate your action
// if keyOrKeys is an array, then you have a combo
// else you have a single key
console.log('key(s) pressed', keyOrKeys)
return keyOrKeys
})
.subscribe (console.log.bind(console))