如何根据RxJ中的事件有条件地缓冲键输入 [英] How do I conditionally buffer key input based on event in RxJs

查看:159
本文介绍了如何根据RxJ中的事件有条件地缓冲键输入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很接近RxJs,没有看到这个解决方案。更详细的解释是在评论中,但基本上我想处理一个组合键(我认为缓冲区会这样做)当一个特定的按键被按下(如按o将等待短时间内按其他键) ,但是如果没有按o,或者o的超时已经过去,则立即处理密钥输入(除了o之外的任何内容)。

  Observable.fromEvent(document,'keydown')
//现在我只想继续处理事件,如果用户按o99串联,
//在按o,后跟9,然后按9
//我想我可以用缓冲区
.buffer(()=> Observable.timer (1000))
.map((e)=>'option-99')
//但是,如果它只是一个o,我需要通过无缓冲的密钥(除非是跟随一个o)
//换句话说,o99被缓冲或是某些东西,但是9不是,并且立即处理
.map((e)=> e.keyCode )

谢谢

解决方案

你有一个非常常见的情况来处理,这是一个事件的相关行为取决于一些控制状态(即你有一个基本的状态机)。基本上,如果您考虑这两个控制状态: SIMPLE_KEY COMBO ,以及这两个事件: keyup keyupTimer ,然后:




  • SIMPLE_KEY 状态


    • 如果按下的键是 o ,然后切换到 COMBO 状态

    • 如果不是 o ,那么我们保持在相同的状态,并且通过下游的密钥

    • 如果定时器事件,那么我们传递一个空值,以便在下游被过滤出来

    • <在 COMBO 状态


      • 我们积累了ul>

      • 按下

      • 如果计时器事件,则我们恢复为 SIMPLE_KEY 状态




    所以,要将你的事件映射到一个动作,你需要知道你所处的控制状态。运算符扫描允许您根据累积状态决定如何处理事件。



    可能是某事像这样( https://jsfiddle.net/cbhmxaas/ ):

      function getKeyFromEvent(ev){return ev.key} 
    函数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 {
    //我们只有两个状态,所以它是COMBO状态
    if(eventStruct.event === KEY){
    return {control:COMBO,keyOrKeys:acc.keyOrKeys.concat([eventStruct .data])}
    }
    else {
    //这是一个TIMER事件,我们只有两个事件
    return {control:SIMPLE_KEY,keyOrKeys:acc.keyOrKeys}
    }
    }
    },{控制:SIMPLE_KEY,keyOrKeys:null})
    // .tap(console.warn.bind(console))
    .filter(state => state.keyOrKeys)// SIMPLE_KEY状态下的TIMER事件
    .map(({control,keyOrKeys})=> {
    //这里你关联你的动作
    //如果keyOrKeys是数组,那么你有一个组合
    //否则你有一个键
    console.log('key(s )ressed',keyOrKeys)
    return keyOrKeys
    })
    .subscribe(console.log.bind(console))


    I'm pretty new to RxJs and haven't read a solution to this. More detailed explanation is in the comments, but basically I want to process a key combination (I think buffer would do this) when a specific key is pressed (like pressing "o" will wait for a short time for other keys to be pressed), but immediately process the key input otherwise (anything other than "o" if "o" hasn't been pressed, or the 'timeout' for "o" has passed).

    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)
    

    Thanks

    解决方案

    You have a very common case to deal with, which is that of an event whose associated action depends on some control state (i.e. you have a basic state machine). Basically, if you consider those two control states : SIMPLE_KEY, COMBO, and those two events : keyup, and keyupTimer, then :

    • in the SIMPLE_KEY state
      • if the key pressed is o, then you switch to COMBO state
      • if not o, then we remain in same state, and pass the key downstream
      • if timer event, then we pass a null value to be filtered out downstream
    • in the COMBO state
      • we accumulate key pressed
      • if timer events, then we revert to SIMPLE_KEY state

    So, to map your event to an action, you need to know the control state you are in. The operator scan allows you to decide how to process an event depending on an accumulated state.

    It could be something like this (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))
    

    这篇关于如何根据RxJ中的事件有条件地缓冲键输入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆