您是否曾经打开过一个项目并遭受了痛苦折磨,因为您看到了即使是孤立的杆也不想触及的,难以理解且难以维护的JavaScript代码?因为如果您触摸它,一切都会崩溃,就像一个大的积木块一样。
JavaScript很容易拿起并从编码开始,但是以错误的方式做起来甚至更容易。对于小型项目,低质量的代码不会给公司带来高风险,但是,如果一个项目规模变大,您最终将承担技术债务,这些债务将在每个截止日期前消失,并最终吞噬您。没有人会想碰这种代码。因此,在本文中,我们将看到如何将Model-View-ViewModel(MVVM)架构模式应用到React项目中,并显着提高代码质量。
根据定义,架构模式提供了一组预定义的子系统,指定了它们的职责,并包括用于组织它们之间的关系的规则和准则。
许多架构模式都在尝试解决与MVVM相同的挑战-使您的代码松散耦合,可维护且易于测试。
有人可能会问:“如果我已经知道如何使用Flux和Redux,为什么还要烦恼自己学习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
该ViewController是view的大脑-它拥有所有查看相关逻辑和拥有的一个对应的ViewModel。该view是不知道ViewModel的,它是依靠ViewController,以通过所有必要的数据和事件。 ViewController和ViewModel之间的关系是一对多的-一个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,您可以清晰地将关注点分离开来,测试将变得像夏日的轻风一样。