美文网首页
使用MVVM升级您的React架构

使用MVVM升级您的React架构

作者: 魂斗驴 | 来源:发表于2021-05-02 11:19 被阅读0次

    您是否曾经打开过一个项目并遭受了痛苦折磨,因为您看到了即使是孤立的杆也不想触及的,难以理解且难以维护的JavaScript代码?因为如果您触摸它,一切都会崩溃,就像一个大的积木块一样。

    JavaScript很容易拿起并从编码开始,但是以错误的方式做起来甚至更容易。对于小型项目,低质量的代码不会给公司带来高风险,但是,如果一个项目规模变大,您最终将承担技术债务,这些债务将在每个截止日期前消失,并最终吞噬您。没有人会想碰这种代码。因此,在本文中,我们将看到如何将Model-View-ViewModel(MVVM)架构模式应用到React项目中,并显着提高代码质量。

    根据定义,架构模式提供了一组预定义的子系统,指定了它们的职责,并包括用于组织它们之间的关系的规则和准则。

    许多架构模式都在尝试解决与MVVM相同的挑战-使您的代码松散耦合,可维护且易于测试。

    有人可能会问:“如果我已经知道如何使用FluxRedux,为什么还要烦恼自己学习MVVM或任何其他架构模式?”
    -答案是:你不就得了!例如,如果Redux非常适合您的项目和团队,请坚持使用。另一方面,如果您不知道其他任何模式,您怎么能百分百确定Redux是您项目的理想选择呢?即使可能有更好的选择,您也将迫使Redux进入每个项目。这里唯一明智的决定是学习新的建筑模式。让我们从MVVM开始。

    了解模式的最佳方法是弄脏双手,然后尝试一下。我们将创建pokemon go演示应用阵营和MobX(在这个完整的代码)。MobX是用于简单和可扩展状态管理的库。它的作用与Redux相同,但与Redux不同,它没有为我们提供有关如何构建应用程序的准则。MobX 为我们提供了可观察的功能(观察者模式)以及一种将依赖项注入到我们的组件中的方法。它跟MVVM就像面包去与黄油。

    深入MVVM

    MVVM有四个主要模块:

    • 用户与之交互的view -UI层,
    • ViewController —可以访问ViewModel并处理用户输入,
    • ViewModel -可以访问Model 并处理业务逻辑,
    • Model -应用程序数据源

    继续阅读以了解MVVM中的这些组件如何相互关联以及它们的职责是什么。

    View

    借助React,我们正在构建用户界面,而这正是我们大多数人已经熟悉的。该view是与您的应用程序的用户的唯一接触点。用户将与您的View交互,这将根据事件(例如鼠标移动,按键等)触发ViewController方法。该View不仅用于用户输入,还用于显示输出(某些操作的结果)。
    view不能交互,是React.Component这意味着它应该只用于显示数据和从ViewController触发所述传递事件中使用的。这样,我们使组件可重复使用且易于测试。在MobX的帮助下, 我们将转向 React.Component变成反应式组件,它将观察到任何变化并相应地自动更新。

    import React from 'react'
    import PokemonList from './UI/PokemonList'
    import PokemonForm from './UI/PokemonForm'
    
    class PokemonView extends React.Component {
        render() {
            const {
                pokemons,
                pokemonImage,
                pokemonName,
                randomizePokemon,
                setPokemonName,
                addPokemon,
                removePokemon,
                shouldDisableSubmit
            } = this.props
    
            return (
                <React.Fragment>
                    <PokemonForm
                        image={pokemonImage}
                        onInputChange={setPokemonName}
                        inputValue={pokemonName}
                        randomize={randomizePokemon}
                        onSubmit={addPokemon}
                        shouldDisableSubmit={shouldDisableSubmit}
                    />
                    <PokemonList
                        removePokemon={removePokemon}
                        pokemons={pokemons}
                    />
                </React.Fragment>
            )
        }
    }
    
    export default PokemonView
    

    注意: PokemonList组件是用@observer装饰器装饰的,而不是使用常规函数的observer(class PokemonList {...})
    装饰器默认情况下不支持装饰器,因此,如果要使用它们,则需要babel插件

    ViewController

    ViewControllerview的大脑-它拥有所有查看相关逻辑和拥有的一个对应的ViewModel。该view是不知道ViewModel的,它是依靠ViewController,以通过所有必要的数据和事件。 ViewControllerViewModel之间的关系是一对多的-一个ViewController可以引用不同的ViewModel
    处理用户输入不应留给ViewModel,而应在ViewController会将干净的准备好的数据传递给ViewModel

    import React from 'react'
    import PokemonView from './PokemonView'
    
    class PokemonController extends React.Component {
        state = {
            pokemonImage: '1.gif',
            pokemonName: ''
        }
    
        setRandomPokemonImage = () => {
            const rand = Math.ceil(Math.random() * 10)
            this.setState({ pokemonImage: `${rand}.gif` })
        }
    
        setPokemonName = (e) => {
            this.setState({ pokemonName: e.target.value })
        }
    
        clearPokemonName() {
            this.setState({ pokemonName: '' })
        }
    
        savePokemon = () => {
            this.props.ViewModel.addPokemon({
                image: this.state.pokemonImage,
                name: this.state.pokemonName
            })
        }
    
        addPokemon = () => {
            this.savePokemon()
            this.clearPokemonName()
        }
    
        removePokemon = (pokemon) => {
            this.props.ViewModel.removePokemon(pokemon)
        }
    
        render() {
            const { ViewModel } = this.props
    
            return (
                <PokemonView
                    pokemons={ViewModel.getPokemons()}
                    pokemonImage={this.state.pokemonImage}
                    randomizePokemon={this.setRandomPokemonImage}
                    setPokemonName={this.setPokemonName}
                    addPokemon={this.addPokemon}
                    removePokemon={this.removePokemon}
                    pokemonName={this.state.pokemonName}
                    shouldDisableSubmit={!this.state.pokemonName}
                />
            )
        }
    }
    
    export default PokemonController
    

    ViewModel

    ViewModel是谁生产商,并不关心谁消耗的数据; 它可以是React组件,Vue组件,飞机甚至是母牛,根本不在乎。由于ViewModel只是一个常规的JavaScript类,因此可以使用不同的UI轻松地在任何地方重用。ViewModel所需的每个依赖项都将通过构造函数注入,从而使其易于测试。该ViewModel与直接交互模式,并且只要ViewModel更新它,所有的变化会自动反映回View。

    class PokemonViewModel {
        constructor(pokemonStore) {
            this.store = pokemonStore
        }
    
        getPokemons() {
            return this.store.getPokemons()
        }
    
        addPokemon(pokemon) {
            this.store.addPokemon(pokemon)
        }
    
        removePokemon(pokemon) {
            this.store.removePokemon(pokemon)
        }
    }
    
    export default PokemonViewModel
    

    Model

    Model充当数据源,即。应用程序的全局存储。它可以组合来自网络层,数据库,服务的所有数据,并以简单的方式为它们提供服务。它不应该具有任何其他逻辑,除了可以实际更新Model并且没有任何副作用的逻辑。

    import { observable, action } from 'mobx'
    import uuid from 'uuid/v4'
    
    class PokemonModel {
        @observable pokemons = []
    
        @action addPokemon(pokemon) {
            this.pokemons.push({
                id: uuid(),
                ...pokemon
            })
        }
    
        @action removePokemon(pokemon) {
            this.pokemons.remove(pokemon)
        }
    
        @action clearAll() {
            this.pokemons.clear()
        }
    
        getPokemons() {
            return this.pokemons
        }
    }
    
    export default PokemonModel
    

    注意:在上面的代码片段中,我们在View @observable将要观察的每个属性上使用decorator 。Model中更新了某些可观察值的任何代码段都应使用装饰器@action 进行装饰。

    Provider

    不在MVVM中但可以将所有内容粘合在一起的组件称为Provider。该组件将实例化ViewModel并为其提供所有必需的依赖关系。此外,ViewModel的实例通过props传递给ViewController组件。
    Provider应该是干净的,没有任何逻辑,因为其目的只是为了连接所有东西。

    import React from 'react'
    import { inject } from 'mobx-react'
    import PokemonController from './PokemonController'
    import PokemonViewModel from './PokemonViewModel'
    import RootStore from '../../models/RootStore'
    
    @inject(RootStore.type.POKEMON_MODEL)
    class PokemonProvider extends React.Component {
        constructor(props) {
            super(props)
            const pokemonModel = props[RootStore.type.POKEMON_MODEL]
            this.ViewModel = new PokemonViewModel(pokemonModel)
        }
    
        render() {
            return (
                <PokemonController ViewModel={this.ViewModel}/>
            )
        }
    }
    
    export default PokemonProvider
    

    注意:在上面的代码片段中,@inject decorator用于将所有需要的依赖项注入Provider道具。

    回顾

    借助MVVM,您可以清晰地将关注点分离开来,测试将变得像夏日的轻风一样。

    参考

    Level up your React architecture with MVVM

    相关文章

      网友评论

          本文标题:使用MVVM升级您的React架构

          本文链接:https://www.haomeiwen.com/subject/cbevlltx.html