前端響應(yīng)式編程的方案及其缺點(diǎn)的詳細(xì)說明(附代碼)
發(fā)表時(shí)間:2023-09-07 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]本篇文章給大家?guī)淼膬?nèi)容是關(guān)于前端響應(yīng)式編程的方案及其缺點(diǎn)的詳細(xì)介紹(附代碼),有一定的參考價(jià)值,有需要的朋友可以參考一下,希望對你有所幫助,F(xiàn)實(shí)世界有很多是以響應(yīng)式的方式運(yùn)作的,例如我們會在收到他人的提問,然后做出響應(yīng),給出相應(yīng)的回答。在開發(fā)過程中我也應(yīng)用了大量的響應(yīng)式設(shè)計(jì),積累了一些經(jīng)驗(yàn),希望...
本篇文章給大家?guī)淼膬?nèi)容是關(guān)于前端響應(yīng)式編程的方案及其缺點(diǎn)的詳細(xì)介紹(附代碼),有一定的參考價(jià)值,有需要的朋友可以參考一下,希望對你有所幫助。
現(xiàn)實(shí)世界有很多是以響應(yīng)式的方式運(yùn)作的,例如我們會在收到他人的提問,然后做出響應(yīng),給出相應(yīng)的回答。在開發(fā)過程中我也應(yīng)用了大量的響應(yīng)式設(shè)計(jì),積累了一些經(jīng)驗(yàn),希望能拋磚引玉。
響應(yīng)式編程(Reactive Programming)和普通的編程思路的主要區(qū)別在于,響應(yīng)式以推(push)的方式運(yùn)作,而非響應(yīng)式的編程思路以拉(pull)的方式運(yùn)作。例如,事件就是一個很常見的響應(yīng)式編程,我們通常會這么做:
button.on('click', () => {
// ...})
而非響應(yīng)式方式下,就會變成這樣:
while (true) {
if (button.clicked) { // ...
}
}
顯然,無論在是代碼的優(yōu)雅度還是執(zhí)行效率上,非響應(yīng)式的方式都不如響應(yīng)式的設(shè)計(jì)。
Event Emitter
Event Emitter是大多數(shù)人都很熟悉的事件實(shí)現(xiàn),它很簡單也很實(shí)用,我們可以利用Event Emitter實(shí)現(xiàn)簡單的響應(yīng)式設(shè)計(jì),例如下面這個異步搜索:
class Input extends Component {
state = { value: ''
}
onChange = e => { this.props.events.emit('onChange', e.target.value)
}
afterChange = value => { this.setState({
value
})
}
componentDidMount() { this.props.events.on('onChange', this.afterChange)
}
componentWillUnmount() { this.props.events.off('onChange', this.afterChange)
}
render() {
const { value } = this.state
return ( <input value={value} onChange={this.onChange} />
)
}
}
class Search extends Component {
doSearch = (value) => {
ajax(/* ... */).then(list => this.setState({
list
}))
}
componentDidMount() {
this.props.events.on('onChange', this.doSearch)
}
componentWillUnmount() {
this.props.events.off('onChange', this.doSearch)
}
render() {
const { list } = this.state
return ( <ul>
{list.map(item => <li key={item.id}>{item.value}</li>)} </ul>
)
}
}
這里我們會發(fā)現(xiàn)用Event Emitter的實(shí)現(xiàn)有很多缺點(diǎn),需要我們手動在componentWillUnmount里進(jìn)行資源的釋放。它的表達(dá)能力不足,例如我們在搜索的時(shí)候需要聚合多個數(shù)據(jù)源的時(shí)候:
class Search extends Component {
foo = ''
bar = ''
doSearch = () => {
ajax({
foo,
bar
}).then(list => this.setState({
list
}))
}
fooChange = value => { this.foo = value this.doSearch()
}
barChange = value => { this.bar = value this.doSearch()
}
componentDidMount() { this.props.events.on('fooChange', this.fooChange) this.props.events.on('barChange', this.barChange)
}
componentWillUnmount() { this.props.events.off('fooChange', this.fooChange) this.props.events.off('barChange', this.barChange)
}
render() { // ...
}
}
顯然開發(fā)效率很低。
Redux
Redux采用了一個事件流的方式實(shí)現(xiàn)響應(yīng)式,在Redux中由于reducer必須是純函數(shù),因此要實(shí)現(xiàn)響應(yīng)式的方式只有訂閱中或者是在中間件中。
如果通過訂閱store的方式,由于Redux不能準(zhǔn)確拿到哪一個數(shù)據(jù)放生了變化,因此只能通過臟檢查的方式。例如:
function createWatcher(mapState, callback) {
let previousValue = null
return (store) => {
store.subscribe(() => { const value = mapState(store.getState()) if (value !== previousValue) {
callback(value)
}
previousValue = value
})
}
}const watcher = createWatcher(state => {
// ...}, () => { // ...})
watcher(store)
這個方法有兩個缺點(diǎn),一是在數(shù)據(jù)很復(fù)雜且數(shù)據(jù)量比較大的時(shí)候會有效率上的問題;二是,如果mapState函數(shù)依賴上下文的話,就很難辦了。在react-redux中,connect函數(shù)中mapStateToProps的第二個參數(shù)是props,可以通過上層組件傳入props來獲得需要的上下文,但是這樣監(jiān)聽者就變成了React的組件,會隨著組件的掛載和卸載被創(chuàng)建和銷毀,如果我們希望這個響應(yīng)式和組件無關(guān)的話就有問題了。
另一種方式就是在中間件中監(jiān)聽數(shù)據(jù)變化。得益于Redux的設(shè)計(jì),我們通過監(jiān)聽特定的事件(Action)就可以得到對應(yīng)的數(shù)據(jù)變化。
const search = () => (dispatch, getState) => {
// ...}const middleware = ({ dispatch }) => next => action => {
switch action.type { case 'FOO_CHANGE': case 'BAR_CHANGE': { const nextState = next(action) // 在本次dispatch完成以后再去進(jìn)行新的dispatch
setTimeout(() => dispatch(search()), 0) return nextState
} default: return next(action)
}
}
這個方法能解決大多數(shù)的問題,但是在Redux中,中間件和reducer實(shí)際上隱式訂閱了所有的事件(Action),這顯然是有些不合理的,雖然在沒有性能問題的前提下是完全可以接受的。
面向?qū)ο蟮捻憫?yīng)式
ECMASCRIPT 5.1引入了getter和setter,我們可以通過getter和setter實(shí)現(xiàn)一種響應(yīng)式。
class Model {
_foo = ''
get foo() { return this._foo
}
set foo(value) { this._foo = value this.search()
}
search() { // ...
}
}// 當(dāng)然如果沒有g(shù)etter和setter的話也可以通過這種方式實(shí)現(xiàn)class Model {
foo = ''
getFoo() { return this.foo
}
setFoo(value) { this.foo = value this.search()
}
search() { // ...
}
}
Mobx和Vue就使用了這樣的方式實(shí)現(xiàn)響應(yīng)式。當(dāng)然,如果不考慮兼容性的話我們還可以使用Proxy。
當(dāng)我們需要響應(yīng)若干個值然后得到一個新值的話,在Mobx中我們可以這么做:
class Model {
@observable hour = '00'
@observable minute = '00'
@computed get time() { return `${this.hour}:${this.minute}`
}
}
Mobx會在運(yùn)行時(shí)收集time依賴了哪些值,并在這些值發(fā)生改變(觸發(fā)setter)的時(shí)候重新計(jì)算time的值,顯然要比EventEmitter的做法方便高效得多,相對Redux的middleware更直觀。
但是這里也有一個缺點(diǎn),基于getter的computed屬性只能描述y = f(x)的情形,但是現(xiàn)實(shí)中很多情況f是一個異步函數(shù),那么就會變成y = await f(x),對于這種情形getter就無法描述了。
對于這種情形,我們可以通過Mobx提供的autorun來實(shí)現(xiàn):
class Model {
@observable keyword = ''
@observable searchResult = [] constructor() {
autorun(() => { // ajax ...
})
}
}
由于運(yùn)行時(shí)的依賴收集過程完全是隱式的,這里經(jīng)常會遇到一個問題就是收集到意外的依賴:
class Model {
@observable loading = false
@observable keyword = ''
@observable searchResult = [] constructor() {
autorun(() => { if (this.loading) { return
} // ajax ...
})
}
}
顯然這里loading不應(yīng)該被搜索的autorun收集到,為了處理這個問題就會多出一些額外的代碼,而多余的代碼容易帶來犯錯的機(jī)會。
或者,我們也可以手動指定需要的字段,但是這種方式就不得不多出一些額外的操作:
class Model {
@observable loading = false
@observable keyword = ''
@observable searchResult = []
disposers = []
fetch = () => { // ...
}
dispose() { this.disposers.forEach(disposer => disposer())
} constructor() { this.disposers.push(
observe(this, 'loading', this.fetch),
observe(this, 'keyword', this.fetch)
)
}
}class FooComponent extends Component {
this.mode = new Model()
componentWillUnmount() { this.state.model.dispose()
} // ...}
而當(dāng)我們需要對時(shí)間軸做一些描述時(shí),Mobx就有些力不從心了,例如需要延遲5秒再進(jìn)行搜索。
相關(guān)推薦:
拼圖響應(yīng)式前端框架版響應(yīng)式后臺正式發(fā)布_html/css_WEB-ITnose
使用很簡單的響應(yīng)式前端開發(fā)框架_html/css_WEB-ITnose
以上就是前端響應(yīng)式編程的方案及其缺點(diǎn)的詳細(xì)介紹(附代碼)的詳細(xì)內(nèi)容,更多請關(guān)注php中文網(wǎng)其它相關(guān)文章!
網(wǎng)站建設(shè)是一個廣義的術(shù)語,涵蓋了許多不同的技能和學(xué)科中所使用的生產(chǎn)和維護(hù)的網(wǎng)站。