vue抽离model层的思考和实现

396 阅读2分钟

思考

当我们遇到一个复杂的页面时:
1.数据共享,父组件和子孙组件的数据共享,数据共享很多都是远程获取的数据;
2.数据处理和页面的交互未分离
昨天读了 @wangly19 的文章,对实现的方式思考才有了本文章

实现

实现方式:使用 vue.observableAPI 和 mixins,mixins主要是去做初始化和销毁
使用createModelComponent会在组件挂载useData属性,访问model里的state 和useDispatch方法,访问model里的方法

createModelComponent.js

/**
 * model层专注数据处理
 * 抽离model要实现的目标
 * 1.抽离model层业务
 * 2.数据共享
 */

import Vue from 'vue'

/**
 * 是否是被监听的对象
 * @param {object} state 
 * @returns {boolean}
 */
function isObservable(state) {
  return hasOwnProperty(state, '__ob__')
}

/**
 * useData计算属性
 * @param {object} model 
 * @returns {object}
 */
function computedUseData(model) {
  return {
    get: () => model.state,
    set() {
      throw ('do not set useData')
    }
  }
}

/**
 * 对象本身是否该属性
 * @param {object} obj 
 * @param {string} property
 * @returns {boolean}
 */
function hasOwnProperty(obj, property) {
  return Object.hasOwnProperty.call(obj, property)
}

/**
 * 分发model层actions事件
 * @param {object} model 
 * @returns {function}
 */
function useDispatch(model) {
  return function (actionName, payload) {
    if (!hasOwnProperty(model, actionName)) {
      throw (`${actionName} not defined`)
    }

    if (typeof model[actionName] !== 'function') {
      throw (`${actionName} not function`)
    }

    return model[actionName](payload)
  }
}

/**
 * 深拷贝
 * @param {object} obj 
 * @param {map} hash 
 */
function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj
  if (obj instanceof Date) return new Date(obj)
  if (obj instanceof RegExp) return new RegExp(obj)

  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== 'object') return obj

  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj)
  let cloneObj = new obj.constructor()

  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj)
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash)
    }
  }

  return cloneObj
}

/**
 * 在组件生命周期里初始化 model
 * @param {objec} model
 * @returns {object} 
 */
function initModelMixin(model) {
  return {
    beforeCreate() {
      if (model.state && !isObservable(model.state)) {
        const state = Vue.observable(model.state)
        model.state = state
      }
    },
    destroyed() {
      model.state = deepClone(model.$state)
    }
  }
}

/**
 * 创建model层组件
 * 组件挂载useData属性访问model层的state
 * @param {object} component 组件
 * @param {object} model 组件的model层
 * @returns {object}
 */
export function createModelComponent(component, model = {}) {
  let newComponent = { ...component }

  if (model.state) {
    if (!model.$state) {
      model.$state = deepClone(model.state)
    }

    newComponent.computed = {
      ...component.computed,
      useData: computedUseData(model)
    }
  }

  newComponent.methods = {
    ...component.methods,
    useDispatch: useDispatch(model)
  }

  const mixins = component.mixins ? component.mixins : []
  newComponent.mixins = [initModelMixin(model), ...mixins]

  return newComponent
}

使用

<template>
  <div class="home">
    {{ useData.userName }}
    <img alt="Vue logo" src="../assets/logo.png" />

    <button @click="handleClick">actions</button>

    <button @click="handleClick3">actions3</button>

    <HelloWorld msg="Welcome to Your Vue.js App" />
  </div>
</template>
<script>
import HelloWorld from '@/components/HelloWorld.vue'
import { createModelComponent } from '@/libs/createModelComponent'
import homeModel from './homeModel'

export default createModelComponent(
  {
    name: 'Home',
    components: {
      HelloWorld,
    },
    mounted() {
      console.log(this)
    },
    methods: {
      handleClick() {
        this.useData.userName = 'Gavin'
      },
      handleClick3() {
        this.useDispatch('getUerInfo')
      },
    },
    destroyed() {
      console.log('destory')
    },
  },
  homeModel
)
</script>

homeMdel.js

/**
 * this的指向本身对象
 * 不能访问到组件本身,这也是model层的作用,专注数据处理和数据共享
 */

export default {
  state: {
    userName: ''
  },
  async getUerInfo() {
    this.state.userName = 'Gavin 2'
  }
}

参考资料

总结我对Vue项目团队开发的一些基本配置封装分享
前端MVC、MVVM的简单实现
vuejs api
浅拷贝与深拷贝