mvc_in_fe.md 4.06 KB

电商前端最佳实践讨论

目前我们yohobuy的pc、wap站的前端js代码,不同的页面代码结构都不相同,复杂的页面有上千的代码,但是很难去阅读其中的逻辑。所以我们需要一个适合电商前台的最佳实践。

使用MVC

MVC是一种最经典的业务逻辑分离、代码组织的设计规范。那么在我们的前端中,是不是也适合mvc, 那么如果使用mvc来组织我们的代码,哪些是model,哪些是controller,哪些是view呢?

Model

在我们的页面中 js里面的数据一般通过两种方式获得: 一、来自页面直出时,dom上的data-* ; 二、通过ajax返回

对于dom上面的数据,建议放在view中初始化

  1. 我们是否需要定义 view-object?

    在MVVM的一些框架中,他们专门为每个view去定义了对应的object。通过监听vo的值,来实现数据绑定。在我们的页面中,大部分页面中的数据都是直出的,很少有直接改变数据。

  2. model层的代码是否独立文件编写?

    有必要。部分ajax操作、查询在多个页面中是共用的,比如收藏,搜索等。

  3. ajax support promise?

    建议使用。 https://github.com/taylorhakes/promise-polyfill

View

我们的页面是html,但是dom对象以及事件应该是属于View。 在View中,我们应该处理事件绑定和dom操作。

职责:

  • 每一个View是独立的,View包含子dom的选择,dom事件的监听,暴露自定义事件,提供dom操作的方法给Controller使用
  • View只可以操作自己的dom
  • 多个View之间的行为事件,通过Controller转发

Controller

连接Model和View。实现页面组件化。

职责:

  • 组件初始化入口,初始化多个View、子组件
  • 监听多个View的自定义事件,事件传播、处理
  • 调用model请求接口

Code

新页面js结构

--js
  --module1                        // 模块
    --page1                        // 页面
      --index.js                   // 页面入口(webpack)文件
      --controller.js              // 控制器逻辑
      --view.js                    // 页面逻辑
   ...
   models.js                       // 模块下 ajax操作
  --model2
  ...

封装jQuery ajax 支持Promise/+

统一处理异常情况,tracer日志, 跳转登录等逻辑

function http(options) {
    let ajax = $.ajax(options);

    ajax.then = ajax.done.bind(ajax);
    ajax.catch = ajax.fail.bind(ajax);
    ajax.finally = ajax.always.bind(ajax);
    return ajax;
}

Simple Model

brand-collect.js

module.exports = {
    // 收藏品牌
    collect(id) {},
    // 取消收藏品牌
    uncollect(id) {}
};

Simple View & Controller

import {collect, uncollect} from 'model/brand-collect';

const hbs = require('../brand.hbs');

class BrandListBannerView extend View {
    constructor() {
        super('brand-list-page-head'); // 对外层的dom selector

        this.$collectBtn = $('#collect-btn');
        this.brandId = this.$collectBtn.data('id');

        // 绑定事件委托
        this.on('click', 'collect-btn', this.onToggleCollect);


    }

    render(data) {
        let html = hbs(data);
        this.$base.append(html);
    }

    onToggleCollect(e) {
        this.emit('collect-toggle', this.view.brandId);
    }

    success() {
        this.$collectBtn.toggleClass('collected');
    }
}

class BrandListBannerController extend Controller {
    constructor() {
        supper();  // the view will be init.

        this.view = new BrandListBannerView();
        this.view.on('collect-toggle', this.doToggleCollect);
    }

    doToggleCollect(id) {
        collect(id).then(ret => {
            this.view.success();
        })
    }

    set(data) {
        this.view.render(data);
    }

    get() {

    }
}

module.exports = BrandListBannerController;

Big View & Contoller

// require some component controller
const BrandListBannerController = require('components/brand-list-banner-controller');

let brandListBannerController = new BrandListBannerController();