IIFE 视图模型似乎未定义

IIFE View Model seems to be undefined

我正在使用 Mithril.JS,看起来我的虚拟机未定义,而之前没有。

我四处搜索了一下 mithril.js。

代码:

var app = {};

var apiData;

app.getData = function () {
  m.request({
    method: 'GET',
    url: '/api/stocks',
  }).then(function(data){
    data = apiData;
  })
};

app.App = function(data){ // model class
  this.plotCfg = {
    chart: {
        renderTo: "plot"
    },
    rangeSelector: {
        selected: 4
    },
    yAxis: {
        labels: {
            formatter: function () {
                return (this.value > 0 ? ' + ' : '') + this.value + '%';
            }
        },
        plotLines: [{
            value: 0,
            width: 2,
            color: 'silver'
        }]
    },

    plotOptions: {
        series: {
            compare: 'percent',
            showInNavigator: true
        }
    },

    tooltip: {
        pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
        valueDecimals: 2,
        split: true
    },

      series: [{
          name: 'Kyle\'s Chart',
          data: apiData
      }]
  };
};
app.controller = function() { // controller
  this.apk = new app.App();
  this.cfg = this.apk.plotCfg;
};
app.plotter = function(ctrl) { // config class
  return function(elem,isin) {
      if(!isin) {
        m.startComputation();
        var chart = Highcharts.StockChart(ctrl.cfg);
        m.endComputation();
      }
  };
};
app.view = function(ctrl) { // view
  return m("#plot[style=height:400px]", {config: app.plotter(ctrl)})
};

app.Stock = function(data) {
  this.date_added = m.prop(new Date());
  this.symbol = m.prop(data.symbol);
  this.id = m.prop(data.id)
};

app.SymbolList = Array;

app.vm = (function() {
    var vm = {}
    vm.init = function() {
        //a running list of todos
        vm.list = new app.SymbolList();
        //a slot to store the name of a new todo before it is created
        app.parseData = function (data) {
          for (var i =0; i< list.length ;i++) {
            console.log(list[i].stock);
            var stockSymbol = data[i].stock;
            vm.list.push(new app.Stock({symbol : stockSymbol}));
        }
        app.parseData(apiData);
        vm.symbol = m.prop("");
        //adds a todo to the list, and clears the description field for user convenience
        vm.add = function() {
            var data = vm.symbol();
            if (vm.symbol()) {
                data = {'text': data.toUpperCase()};
                m.request({method: 'POST',
                            url: '/api/stocks',
                            data: data,
                          }).then(function(list) {
                            vm.list = [];
                            for (var i =0; i< list.length ;i++) {
                              console.log(list[i].stock);
                              var stockSymbol = list[i].stock;
                              vm.list.push(new app.Stock({symbol : stockSymbol}));
                            }
                            return;
                          })
                vm.symbol("");
            }
        };
    }
    return vm
  }
}())

app.controller2 = function() {
  app.vm.init();
}
app.view2 = function() {
  return [
      m('input', { onchange: m.withAttr('value', app.vm.symbol),  value: app.vm.symbol()}),
      m('button.btn.btn-active.btn-primary', {onclick: app.vm.add}, 'Add Stock'),
      m('ul', [
        app.vm.list.map(function(item , index) {
          return m("li", [
            m('p', item.symbol())
          ])
        })
      ])
  ]
};
m.mount(document.getElementById('chart'), {controller: app.controller, view: app.view}); //mount chart
m.mount(document.getElementById('app'), {controller: app.controller2, view: app.view2}); //mount list
<div id="app"></div>
<div id="chart"></div>
<script src="https://cdn.rawgit.com/lhorie/mithril.js/v0.2.5/mithril.js"></script>
<script src="https://code.highcharts.com/stock/highstock.js"></script>

chrome中弹出的错误是这样的:

app.js:119 Uncaught TypeError: Cannot read property 'init' of undefined
    at new app.controllerT (app.js:119)
    at ea (mithril.js:1408)
    at Function.k.mount.k.module (mithril.js:1462)
    at app.js:135

在我添加第二个挂载点、视图和控制器之前没问题。

有什么想法吗?

问题是 app.vm 没有公开 init

app.vm 的当前代码如下所示:

app.vm = (function(){
  var vm = {}
  vm.init = function(){
    /* lots of stuff... */

    return vm
  }
}())

这意味着内部 vm.init returns vm,但是 app.vm IIFE 没有 return 任何东西。应该是:

app.vm = (function(){
  var vm = {}
  vm.init = function(){
    /* lots of stuff... */
  }
  return vm
}())

阅读您的应用程序结构非常困难,因为它充满了各种似乎没有用的奇特模式。诚然,'vm' 闭包是 Mithril 指南中引入的一种模式,但我认为如果我们避免所有这些闭包、初始化调用、构造函数、嵌套对象和命名空间,那么编写、推理和调试应用程序会容易得多。

'view models' 背后的想法来自 Mithril 最初发布(2014 年初)时的网络应用程序开发状态,当时 front-end 应用程序开发的主要问题之一是人们认为缺乏结构,而 Mithril 认为有必要向人们展示如何构建对象。但是这种形式的结构只有在阐明意图时才有用——在上面的代码中它会混淆事物。例如,app.getData 没有在任何地方调用,它在处理它之前将一个空的全局变量分配给它自己的参数。如果我们有 less 个对象,这种事情会更容易推理。

这是相同的代码,但有一些额外的修复和替代结构。此重构中的工作原则:

  1. 我们不再编写任何自己的构造函数或闭包,从而减少动态代码执行并避免出现 app.vm.init
  2. 等错误的可能性
  3. 我们不再将事物附加到对象上,除非该结构有用或有意义,并且如果它们只使用一次,则使用简单变量或在使用时声明事物,从而减少引用和结构复杂性
  4. 我们使用对象字面量 - var x = { y : 'z' } 而不是 var x = {}; x.y = 'z' 因此我们可以看到整体结构,而不必在头脑中解释代码执行来确定对象将如何在运行时构建
  5. 我们没有使用一个大的泛型 app.vm 来存储所有内容,而是将我们的应用程序模型分离到它们相关的地方,并使用函数将值从一个地方传递到另一个地方,从而使我们能够拆分我们的复杂性.我会在显示代码后详细说明:

// Model data
var seriesData = []

// Model functions
function addToSeries(data){
  seriesData.push.apply(seriesData,data)
}

function getData( symbol ){
  m.request( {method: 'POST',
    url: '/api/stocks',
    data: { text : symbol.toUpperCase() },
  } ).then(function(list) {
    return list.map(function( item ){
      return makeStock( { symbol : item.stock } )
    } )
  } )
}

function makeStock( data ) {
  return {
    date_added : new Date(),
    symbol     : data.symbol,
    id         : data.id
  }
}

// View data
var chartConfig = {
  rangeSelector: {
    selected: 4
  },
  yAxis: {
    labels: {
      formatter: function () {
        return (this.value > 0 ? ' + ' : '') + this.value + '%';
      }
    },
    plotLines: [{
      value: 0,
      width: 2,
      color: 'silver'
    }]
  },

  plotOptions: {
    series: {
      compare: 'percent',
      showInNavigator: true
    }
  },

  tooltip: {
    pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.change}%)<br/>',
    valueDecimals: 2,
    split: true
  },

  series: [{
    name: 'Kyle\'s Chart',
    data: seriesData
  }]
}

// Components
var chartComponent = {
  view : function(ctrl) {
    return m("#plot[style=height:400px]", {
      config: function(elem,isin) {
        if(!isin)
          Highcharts.StockChart(elem, chartConfig)
      }
    })
  }
}

var todosComponent = {
  controller : function(){
    return {
      symbol : m.prop('')
    }
  },

  view : function( ctrl ){
    return [
      m('input', {
        onchange: m.withAttr('value', ctrl.symbol),
        value: ctrl.symbol()
      }),

      m('button.btn.btn-active.btn-primary', {
        onclick: function(){
          if( ctrl.symbol() )
            getData( ctrl.symbol() )
              .then( function( data ){
                addToSeries( data )
              } )

          ctrl.symbol('')
        }
      }, 'Add Stock'),

      m('ul',
        todos.map(function(item) {
          return m("li",
            m('p', item.symbol)
          )
        })
      )
    ]
  }
}

// UI initialisation
m.mount(document.getElementById('chart'), chartComponent)
m.mount(document.getElementById('app'), todosComponent)

没有更多的app,或vm,或list。这些最终变得毫无用处,因为它们是如此模糊和通用,以至于它们被用来存储所有东西——当一个对象包含所有东西时,你还不如免费获得这些东西。

核心动态数据列表现在称为seriesData。它只是一个数组。为了与之交互,我们有 3 个简单的函数来改变系列数据、获取新数据以及从输入创建新数据点。这里不需要构造函数,也不需要 props - props 是一种 Mithril 实用程序,可以方便地从输入中读取和写入数据 - 它们在任何情况下都与 Highcharts API 不兼容。

这就是我们需要的所有模型数据。接下来我们有特定于 UI 的代码。 Highcharts 配置对象引用 seriesData,但除此之外,它是一个为符合 Highcharts API 而编写的深奥对象。我们省略了 renderTo,因为这是由我们的秘银动态决定的 UI。

接下来是组件,我们将其编写为对象文字,而不是稍后将它们拼凑在一起——组件控制器仅在与其视图相关时才有意义。 chartComponent 实际上不需要控制器,因为它没有状态,只是读取先前定义的模型和视图数据。我们在 config 函数中直接向 Highcharts API 提供元素引用。因为这个函数只在一个地方使用一次,所以我们将它声明为内联而不是在一个地方定义它并在其他地方绑定它。 start/endComputation 是不必要的,因为该过程是同步的,并且在此过程中无需停止 Mithril 渲染。

我不太明白 'todos' 模型是如何工作的,但我假设第二个组件旨在提供数据点的替代视图并允许用户输入定义和获取更多数据。我们在这里将 'symbol' 属性存储在控制器中,因为它是一个仅由视图使用的有状态 属性。这是我们唯一与此组件相关的有状态 属性,因此这就是我们在控制器中定义的所有内容。早些时候我们简化了 model-related 函数 - 现在在视图中我们与这些函数交互,显式传递符号数据而不是在其他地方定义它并在另一个地方检索它。我们还重置了此处的值,因为这是此组件逻辑的一个方面,而不是整体数据模型。