响应式编程 php,前端响应式编程的方案及其缺点的详细介绍(附代码)

本篇文章给大家带来的内容是关于前端响应式编程的方案及其缺点的详细介绍(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

现实世界有很多是以响应式的方式运作的,例如我们会在收到他人的提问,然后做出响应,给出相应的回答。在开发过程中我也应用了大量的响应式设计,积累了一些经验,希望能抛砖引玉。

响应式编程(Reactive Programming)和普通的编程思路的主要区别在于,响应式以推(push)的方式运作,而非响应式的编程思路以拉(pull)的方式运作。例如,事件就是一个很常见的响应式编程,我们通常会这么做:button.on('click', () => {

// ...})

而非响应式方式下,就会变成这样:while (true) {

if (button.clicked) { // ...

}

}

显然,无论在是代码的优雅度还是执行效率上,非响应式的方式都不如响应式的设计。

Event Emitter

Event Emitter是大多数人都很熟悉的事件实现,它很简单也很实用,我们可以利用Event Emitter实现简单的响应式设计,例如下面这个异步搜索: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 (

)

}

}

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 (

{list.map(item =>

{item.value})}

)

}

}

这里我们会发现用Event Emitter的实现有很多缺点,需要我们手动在componentWillUnmount里进行资源的释放。它的表达能力不足,例如我们在搜索的时候需要聚合多个数据源的时候: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() { // ...

}

}

显然开发效率很低。

Redux

Redux采用了一个事件流的方式实现响应式,在Redux中由于reducer必须是纯函数,因此要实现响应式的方式只有订阅中或者是在中间件中。

如果通过订阅store的方式,由于Redux不能准确拿到哪一个数据放生了变化,因此只能通过脏检查的方式。例如: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)

这个方法有两个缺点,一是在数据很复杂且数据量比较大的时候会有效率上的问题;二是,如果mapState函数依赖上下文的话,就很难办了。在react-redux中,connect函数中mapStateToProps的第二个参数是props,可以通过上层组件传入props来获得需要的上下文,但是这样监听者就变成了React的组件,会随着组件的挂载和卸载被创建和销毁,如果我们希望这个响应式和组件无关的话就有问题了。

另一种方式就是在中间件中监听数据变化。得益于Redux的设计,我们通过监听特定的事件(Action)就可以得到对应的数据变化。const search = () => (dispatch, getState) => {

// ...}const middleware = ({ dispatch }) => next => action => {

switch action.type { case 'FOO_CHANGE': case 'BAR_CHANGE': { const nextState = next(action) // 在本次dispatch完成以后再去进行新的dispatch

setTimeout(() => dispatch(search()), 0) return nextState

} default: return next(action)

}

}

这个方法能解决大多数的问题,但是在Redux中,中间件和reducer实际上隐式订阅了所有的事件(Action),这显然是有些不合理的,虽然在没有性能问题的前提下是完全可以接受的。

面向对象的响应式

ECMASCRIPT 5.1引入了getter和setter,我们可以通过getter和setter实现一种响应式。class Model {

_foo = ''

get foo() { return this._foo

}

set foo(value) { this._foo = value this.search()

}

search() { // ...

}

}// 当然如果没有getter和setter的话也可以通过这种方式实现class Model {

foo = ''

getFoo() { return this.foo

}

setFoo(value) { this.foo = value this.search()

}

search() { // ...

}

}

Mobx和Vue就使用了这样的方式实现响应式。当然,如果不考虑兼容性的话我们还可以使用Proxy。

当我们需要响应若干个值然后得到一个新值的话,在Mobx中我们可以这么做:class Model {

@observable hour = '00'

@observable minute = '00'

@computed get time() { return `${this.hour}:${this.minute}`

}

}

Mobx会在运行时收集time依赖了哪些值,并在这些值发生改变(触发setter)的时候重新计算time的值,显然要比EventEmitter的做法方便高效得多,相对Redux的middleware更直观。

但是这里也有一个缺点,基于getter的computed属性只能描述y = f(x)的情形,但是现实中很多情况f是一个异步函数,那么就会变成y = await f(x),对于这种情形getter就无法描述了。

对于这种情形,我们可以通过Mobx提供的autorun来实现:class Model {

@observable keyword = ''

@observable searchResult = [] constructor() {

autorun(() => { // ajax ...

})

}

}

由于运行时的依赖收集过程完全是隐式的,这里经常会遇到一个问题就是收集到意外的依赖:class Model {

@observable loading = false

@observable keyword = ''

@observable searchResult = [] constructor() {

autorun(() => { if (this.loading) { return

} // ajax ...

})

}

}

显然这里loading不应该被搜索的autorun收集到,为了处理这个问题就会多出一些额外的代码,而多余的代码容易带来犯错的机会。

或者,我们也可以手动指定需要的字段,但是这种方式就不得不多出一些额外的操作: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()

} // ...}

而当我们需要对时间轴做一些描述时,Mobx就有些力不从心了,例如需要延迟5秒再进行搜索。

相关推荐:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值