用RxJS和react開闢mac地點輸入框

項目簡介

本次運用了RxJS和react開發了一個mac地點輸入框,主要完成的功用有限定輸入相符前提的字符1-9,a-f,並每隔兩位能夠自動增加用於支解的冒號。項目屏障了react的事宜處置懲罰,同時運用setSelectionRange來手動掌握光標。能夠檢察項目的demo,項目地點

RxJS簡介

RxJS 是 Reactive Extensions 在 JavaScript 上的完成,具體來說是一系列東西庫,包含事宜處置懲罰,函數撙節,延時等函數,RxJS運用了’流‘的頭腦,同時具有事宜和時候的觀點。RxJS也能夠用於處置懲罰異步流程,比起Promise具有可作廢和可耽誤,重試等長處。Promise vs Observable
RxJS中有兩個比較主要的觀點,分別是Observable和observer。Observable能夠運用create,of,from,fromEvent等要領來發生流,而Observer能夠對流舉行視察。末了二者經由過程subscribe來連繫,例子以下:


var Observable = Rx.Observable.create(observer => {
    observer.next(2);
    observer.complete();
    return  () => console.log('disposed');
});

var Observer = Rx.Observer.create(
    x => console.log('Next:', x),
    err => console.log('Error:', err),
    () => console.log('Completed')
);

var subscription = Observable.subscribe(Observer);

來自構建流式運用—RxJS詳解

更多關於RxJS,能夠瀏覽Introduction | RxJS – Javascript library for functional reactive programming.

項目構造


    // 監聽事宜,提議流和處置懲罰流
    componentDidMount () {
    this.t = ReactDOM.findDOMNode(this.refs.t)
    let keydownValue = Rx.Observable.fromEvent(this.t,'keydown').map(e => e.key.toUpperCase())
    this.sa = keydownValue.filter(value => value.length === 1 && value.match(/[0-9A-F]/)).subscribe(value => {this.setColon('before');this.insertValue(value); this.setColon();this.setDomValue()})
    // 省略相似的部份
    }
    // 作廢定閱
    componentWillUnmount() 
    this.sa.dispose()
    // 相似的部份省略
    }
    
    // 一些用到的要領,這裏省略
    
    
    // 作廢原生的事宜監聽
    render() {
      return (
        <div className="App">
          <input type="text" onKeyDown={e => e.preventDefault()}  ref="t"/>
        </div>
      );
    }  
    

項目詳解

起首運用Rx.Observable.fromEvent來監聽輸入框的按鍵事宜,並獵取按鍵的key值,保存為keydownValue

 let keydownValue = Rx.Observable.fromEvent(this.t,'keydown')
 .map(e => e.key.toUpperCase())

接着起首斟酌輸入字符的狀況,在這裏,顯現挑選出按鍵相符要求的狀況,接着在subscribe中對數據舉行處置懲罰。在插進去新的字符之前和以後,都須要推斷是不是在前面加上冒號,末了運用setDomValue來讓保存在state中的value顯現到輸入框上。

    this.sa = keydownValue
        .filter(value => value.length === 1 && value.match(/[0-9A-F]/))
        .subscribe(value => {
          this.setColon('before');
          this.insertValue(value); 
          this.setColon();
          this.setDomValue()
        })

推斷是不是須要插進去冒號的函數setColon,須要消除前面沒有字符和四周已有冒號的狀況。

  setColon = type => this.state.value.length && 
      (type !== 'before' ? !this.isNearColon() : !this.isLastColon()) && 
      !(this.state.value.slice(0, this.state.pos).replace(/:/g, '').length%2) && 
      this.insertValue(':')

插進去新字符的函數。在紀錄的光標位置pos值上插進去新的字符,然後轉變光標位置。假如在字符末端有未完成的字符對(即1f:的情勢)又在中心插進去新的字符串且字符對已抵達六個,則刪掉末了一個字符對。

  insertValue = value => {
    if (this.state.value.length !== 17) {
      this.setState({
      ...this.state,
      value: this.state.value.slice(0, this.state.pos) + 
        value + this.state.value.slice(this.state.pos, this.state.value.length)
      })
      this.setPos(this.state.pos + 1)
      if (this.state.value.split(':').length === 7) {
        this.setState({
        ...this.state, 
        value: this.state.value.slice(0, this.state.value.lastIndexOf(':'))
        })
      }
  }}

接着是解說關於刪除的流,挑選按鍵值為’BACKSPACE’的流,實行deleteValue要領和setDomValue

    this.sb = keydownValue.filter(value => value === 'BACKSPACE')
    .subscribe(() => {
      this.deleteValue()
      this.setDomValue()
    })

deleteValue,在value和位置都大於零時才實行,假如刪除后字符后,新的末了一個字符是冒號,則自動刪掉該冒號。

  deleteValue = () => {
    if (this.state.value.length && this.state.pos) {
      this.setState({
      ...this.state, 
      value: this.state.value.slice(0, this.state.pos - 1) + 
      this.state.value.slice(this.state.pos, this.state.value.length)
      })
      this.setPos(this.state.pos - 1)
      if (this.isLastColon()) {
        this.deleteValue()
      }
    }
  }

接着是定閱了擺布方向鍵挪動的流,比較簡單,就不細緻詮釋了。

    this.sc = keydownValue
        .filter(value => value === 'ARROWLEFT')
        .subscribe(() => this.moveLeft())
    this.sd = keydownValue
        .filter(value => value === 'ARROWRIGHT')
        .subscribe(() => this.moveRight())
 
      moveLeft = () => this.state.pos > 0 && 
      this.setState({...this.state, pos: this.state.pos - 1})
      moveRight = () => this.state.pos !== this.state.value.length && 
      this.setState({...this.state, pos: this.state.pos + 1})

末了是讓光標跳到pos的處置懲罰,setSelectionRange本用於筆墨的挑選,但假如前兩個參數為一樣的數值,能夠到達讓光標跳到指定位置的結果。

    this.se = keydownValue.subscribe(() => this.goPos())
    goPos = () => this.t.setSelectionRange(this.state.pos, this.state.pos)

170624更新

底本的形式跟react關聯較少,因而修正調解了一下,主要的變化是啟用了Subject,setStateAsync,在這裏先引見一下。

Rx.Subject

Subject繼承於Obserable和Observer,因而同時具有Obserable和Observer二者的要領。經由過程來自於Observable的multicast要領能夠掛載subject,並取得具有雷同實行環境的多路的新的Observable,關於他的定閱現實上是掛載在subject上。末了須要手動connect。 RxJS 中心觀點之Subject30 天通曉 RxJS(24): Observable operators – multicast, refCount, publish, share

var source = Rx.Observable.from([1, 2, 3]);
var multicasted = source.multicast(new Rx.Subject())

// 經由過程`subject.subscribe({...})`定閱Subject的Observer:
multicasted.subscribe({
  next: (v) => console.log('observerA: ' + v)
});
multicasted.subscribe({
  next: (v) => console.log('observerB: ' + v)
});

// 讓Subject從數據源定閱最先見效:
multicasted.connect();

實在能夠用refCount來防止connect,用publish來替代 multicast(new Rx.Subject()),末了用share替代publish 和 refCount,因而代碼能夠寫成

var multicasted = source.share()
setStateAsync

組件改成受控組件以後,setState中的異步特徵展現了出來,setState后的下一步獵取setState並非最新的state,影響了順序的一般運用。
比方之前的新增函數的定閱。背面的inserValue和setColon都是須要應用最新的state來舉行推斷的。

    this.sa = keydownValue
      .filter(value => value.length === 1 && value.match(/[0-9A-F]/))
        .subscribe(value => {
          this.setColon('before');
          this.insertValue(value); 
          this.setColon();
          this.setDomValue()
        })

能夠在setState的第二個參數中傳入回調函數來處理這個題目,因而函數變成了如許,一層又一層的回調,非常不美觀

this.sa = keydownValue
  .filter(value => value.length === 1 && value.match(/[0-9A-F]/))
    .subscribe(value => {
      this.setColon('before', () => {
        this.insertValue(value, () => {
          this.setColon()
        })
      })
    })
    

接着在網上找到了setStateAsync的函數,道理就是將setState轉換成promise的情勢,接着就可以興奮的運用async await的語法來修正state了。React中setState同步更新戰略

  setStateAsync = state => new Promise(resolve => this.setState(state,resolve))
現實的調解

在componentDidMount中把keydownValue設置為同時具有Observable和Observe的要領的Subject,他一方面能夠運用Observer的onNext要領來增加新的數據,另一方面能夠繼承運用Observable的操作符來對數據舉行處置懲罰。

this.keydownValue = new Rx.Subject()
let multicasted = this.keydownValue.map(e => e.key.toUpperCase()).share()
this.sa = multicasted
  .filter(value => value.length === 1 && value.match(/[0-9A-F]/))
    .subscribe(async value => {
    await this.setColon('before')
    await this.insertValue(value)
    await this.setColon()
    this.goPos()
  })
//下略    

組件的render函數修正為

  <div className="App">  
    <input type="text" onKeyDown={this.handleE} value={this.state.value} ref="t"/>
  </div>

handleE函數繼承製止默許事宜,調用了新設置的Subject(keydownValue)的onNext要領,能夠使得綁定在keydownValue上的定閱取得數據

  handleE = e => {e.preventDefault();this.keydownValue.onNext(e)}
    原文作者:hpoenixf
    原文地址: https://segmentfault.com/a/1190000014378730
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞