使用MVVM升级您的React架构

您是否曾经打开过一个项目并遭受了痛苦折磨,因为您看到了即使是孤立的杆也不想触及的,难以理解且难以维护的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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容