項目簡介
本次運用了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,能夠瀏覽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 中心觀點之Subject,30 天通曉 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)}