搜索
首页web前端html教程前端响应式编程的方案及其缺点的详细介绍(附代码)

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

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

响应式编程(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 (            <input value={value} onChange={this.onChange} />
        )
    }
}
class Search extends Component {  
    doSearch = (value) => {
        ajax(/* ... */).then(list => this.setState({
            list
        }))
    }
    componentDidMount() {
        this.props.events.on(&#39;onChange&#39;, this.doSearch)
    }
    componentWillUnmount() {
        this.props.events.off(&#39;onChange&#39;, this.doSearch)
    }
    render() {
        const { list } = this.state
        return (            <ul>
                {list.map(item => <li key={item.id}>{item.value}</li>)}            </ul>
        )
    }
}

这里我们会发现用Event Emitter的实现有很多缺点,需要我们手动在componentWillUnmount里进行资源的释放。它的表达能力不足,例如我们在搜索的时候需要聚合多个数据源的时候:

class Search extends Component {  
    foo = &#39;&#39;
    bar = &#39;&#39;
    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(&#39;fooChange&#39;, this.fooChange)        this.props.events.on(&#39;barChange&#39;, this.barChange)
    }
    componentWillUnmount() {        this.props.events.off(&#39;fooChange&#39;, this.fooChange)        this.props.events.off(&#39;barChange&#39;, 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 &#39;FOO_CHANGE&#39;:        case &#39;BAR_CHANGE&#39;: {            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 = &#39;&#39;
    get foo() {        return this._foo
    }
    set foo(value) {        this._foo = value        this.search()
    }
    search() {        // ...
    }
}// 当然如果没有getter和setter的话也可以通过这种方式实现class Model {  
    foo = &#39;&#39;
    getFoo() {        return this.foo
    }
    setFoo(value) {        this.foo = value        this.search()
    }
    search() {        // ...
    }
}

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

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

class Model {  
    @observable hour = &#39;00&#39;
    @observable minute = &#39;00&#39;
    @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 = &#39;&#39;
    @observable searchResult = []    constructor() {
        autorun(() => {            // ajax ...
        })
    }
}

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

class Model {  
    @observable loading = false
    @observable keyword = &#39;&#39;
    @observable searchResult = []    constructor() {
        autorun(() => {            if (this.loading) {                return
            }            // ajax ...
        })
    }
}

显然这里loading不应该被搜索的autorun收集到,为了处理这个问题就会多出一些额外的代码,而多余的代码容易带来犯错的机会。 或者,我们也可以手动指定需要的字段,但是这种方式就不得不多出一些额外的操作:

class Model {  
    @observable loading = false
    @observable keyword = &#39;&#39;
    @observable searchResult = []
    disposers = []
    fetch = () => {        // ...
    }
    dispose() {        this.disposers.forEach(disposer => disposer())
    }    constructor() {        this.disposers.push(
            observe(this, &#39;loading&#39;, this.fetch),
            observe(this, &#39;keyword&#39;, this.fetch)
        )
    }
}class FooComponent extends Component {  
    this.mode = new Model()
    componentWillUnmount() {        this.state.model.dispose()
    }    // ...}

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

相关推荐:

拼图响应式前端框架版响应式后台正式发布_html/css_WEB-ITnose

使用很简单的响应式前端开发框架_html/css_WEB-ITnose


以上是前端响应式编程的方案及其缺点的详细介绍(附代码)的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
HTML标签的目的是什么?HTML标签的目的是什么?Apr 28, 2025 am 12:02 AM

htmltagsareessentialforsenteringwebpages,增强辅助性,seo和Performance.1)他们areAnclosedInangleBracketSandInpairStocrateAteAhierArchical.2)samantictagsictagsatagslikslikroikreveuseerexperienceencperienceandseo.3)

什么是自我关闭标签?举一个例子。什么是自我关闭标签?举一个例子。Apr 27, 2025 am 12:04 AM

self-closingtagsinhtmlandxmlaretagsthatclosethem hexpthementneedingAseparateCloseTag,SightifyingmarkingmarkupStrupupStruptoReanDenhancingCodingsifice.1)shemeSsentialInxmmllforelementsswithcontentsswithcontent contentcontent,可确保wellwell-formedDocuments.2)Inhtmlible5,inhtmlibut forfix

超越HTML:网络开发的基本技术超越HTML:网络开发的基本技术Apr 26, 2025 am 12:04 AM

要构建一个功能强大且用户体验良好的网站,仅靠HTML是不够的,还需要以下技术:JavaScript赋予网页动态和交互性,通过操作DOM实现实时变化。CSS负责网页的样式和布局,提升美观度和用户体验。现代框架和库如React、Vue.js和Angular,提高开发效率和代码组织结构。

HTML中的布尔属性是什么?举一些例子。HTML中的布尔属性是什么?举一些例子。Apr 25, 2025 am 12:01 AM

布尔属性是HTML中的特殊属性,不需要值即可激活。1.布尔属性通过存在与否控制元素行为,如disabled禁用输入框。2.它们的工作原理是浏览器解析时根据属性的存在改变元素行为。3.基本用法是直接添加属性,高级用法可通过JavaScript动态控制。4.常见错误是误以为需要设置值,正确写法应简洁。5.最佳实践是保持代码简洁,合理使用布尔属性以优化网页性能和用户体验。

如何验证您的HTML代码?如何验证您的HTML代码?Apr 24, 2025 am 12:04 AM

HTML代码可以通过在线验证器、集成工具和自动化流程来确保其清洁度。1)使用W3CMarkupValidationService在线验证HTML代码。2)在VisualStudioCode中安装并配置HTMLHint扩展进行实时验证。3)利用HTMLTidy在构建流程中自动验证和清理HTML文件。

HTML与CSS和JavaScript:比较Web技术HTML与CSS和JavaScript:比较Web技术Apr 23, 2025 am 12:05 AM

HTML、CSS和JavaScript是构建现代网页的核心技术:1.HTML定义网页结构,2.CSS负责网页外观,3.JavaScript提供网页动态和交互性,它们共同作用,打造出用户体验良好的网站。

HTML作为标记语言:其功能和目的HTML作为标记语言:其功能和目的Apr 22, 2025 am 12:02 AM

HTML的功能是定义网页的结构和内容,其目的在于提供一种标准化的方式来展示信息。1)HTML通过标签和属性组织网页的各个部分,如标题和段落。2)它支持内容与表现分离,提升维护效率。3)HTML具有可扩展性,允许自定义标签增强SEO。

HTML,CSS和JavaScript的未来:网络开发趋势HTML,CSS和JavaScript的未来:网络开发趋势Apr 19, 2025 am 12:02 AM

HTML的未来趋势是语义化和Web组件,CSS的未来趋势是CSS-in-JS和CSSHoudini,JavaScript的未来趋势是WebAssembly和Serverless。1.HTML的语义化提高可访问性和SEO效果,Web组件提升开发效率但需注意浏览器兼容性。2.CSS-in-JS增强样式管理灵活性但可能增大文件体积,CSSHoudini允许直接操作CSS渲染。3.WebAssembly优化浏览器应用性能但学习曲线陡,Serverless简化开发但需优化冷启动问题。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版