Skip to content
This repository was archived by the owner on Nov 12, 2024. It is now read-only.
This repository was archived by the owner on Nov 12, 2024. It is now read-only.

如何开发一个view #47

@xinglie

Description

@xinglie

通过继承Magix.View来实现自己的view

通过Magix.View.extend方法来实现

let Magix = require('magix');
module.exports = Magix.View.extend({
    tmpl:'@demo.html',
    render(){
        console.log('render ui')
    }
});

view的生命周期

显式的init、render方法

每个view默认都有一个init(初始化时调用,只会调用一次)和render(需要更新界面时被调用,可能会调用多次)

let Magix = require('magix');
module.exports = Magix.View.extend({
    tmpl:'@demo.html',
    init(data){
        console.log('init data',data);
    },
    render(){
        console.log('render ui')
    }
});

隐式的destroy,domready等事件接口

为了便于插件及监控的开发,每个view会在适当的时候派发这些事件

let Magix = require('magix');
module.exports = Magix.View.extend({
    tmpl:'@demo.html',
    init(data){
        console.log('init data',data);
        this.on('destroy',()=>{
            console.log('view destroy');
        });
        this.on('domready',()=>{
            console.log('ui ready');
        });
    },
    render(){
        console.log('render ui')
    }
});

全局的htmlchanged事件

除了view自身派发的事件外,任意view对html的修改,均会通过document派发htmlchanged事件

view的使用及分类

自身完整型

我们以一个calendar组件为例来说明,先看使用时的html代码

<--我们通过自定义标签的形式来使用magix-gallery中的calendar组件-->
<mx-calendar
    selected="2018-01-01"
    mx-change="showDate()"/>

对于calendar组件来说,输入是定义好的类似selected这样的参数,输出则是自身日期有变化时,通过change事件向外派发数据。

在外部来看要想改变calendar组件,只能通过参数,没有其它的途径

calendar的实现来看(view组成的三大件:js、html、css,其中js是必有的),html也是要有的,因为这样才方便构建带有ui的组件

内部必有js及html,且只接收参数控制显示的我们把它定义为自身完整型组件

附加行为型

我们以一个拖动排序dragsort为例来说明,先看使用时的html代码

<ul mx-view="app/gallery/mx-dragsort/index" class="left fl" view-selector="span">
    <li><span>move</span>123</li>
    <li><span>move</span>456</li>
    <li><span>move</span>123</li>
    <li><span>move</span>456</li>
    <li><span>move</span>123</li>
    <li><span>move</span>456</li>
    <li><span>move</span>123</li>
    <li><span>move</span>456</li>
</ul>

对于dragsort组件,它自身没有html,不向界面输出任何的html,而是利用页面上原有的节点。如该示例中,dragsort组件并不关心dom节点是什么,处于该节点下的所有子节点均可以被拖动

内部没有html,且对现有dom节点做增强的组件,我们把它定义为附加行为型组件

混合型

一些组件如dropdown,即接收一个list参数来表示要渲染的下拉列表,同时也可以读取已渲染的dom节点来做为下拉框显示的数据源,如

<mx-dropdown
    searchbox="true"
    empty-text="请选择用户"
    text-key="name"
    value-key="id"
    selected="<%@ userSelected %>"
    list="<%@ userList %>"
    mx-change="showUser()"
    class="fl" style="width:200px;">
</mx-dropdown>

<mx-dropdown
    searchbox="true"
    empty-text="请选择日期"
    mx-change="showWeek()"
    class="fl" style="width:150px;">
    <mx-dropdown.item value="mon">周一</mx-dropdown.item>
    <mx-dropdown.item value="wed">周三</mx-dropdown.item>
    <mx-dropdown.item value="thu">周四</mx-dropdown.item>
    <mx-dropdown.item value="fri">周五</mx-dropdown.item>
    <mx-dropdown.item value="sat">周六</mx-dropdown.item>
</mx-dropdown>

差异化更新

当数据有变化,界面也要更新时,magix目前有2种差异化更新的实现方式

html片断拆分的形式

该方式需要配合magix-combine工具对开发者编写的html模板做线下分析,自动把模板拆分成n多子模板片断,每个片断关联着对应的数据key,当数据有变化时,会找到相应的子模板片断,最小化的更新界面

如模板

<div>
    <%for(let i=0;i<list.length;i++){%>
        <span><%=list[i]%></span>
    <%}%>
</div>
<div>
    <%for(let i=0;i<list1.length;i++){%>
        <span><%=list1[i]%></span>
    <%}%>
</div>

会被处理成这样的片断对象

 {
   "html": "<div mx-guid=\"g0\u001f\">1\u001d</div><div mx-guid=\"g1\u001f\">2\u001d</div>",
   "subs": [{
     "keys": ["list"],
     "path": "div[mx-guid=\"g0\u001f\"]",
     "tmpl": "<%for(let _=0;_<$$.list.length;_++){%><span><%=$$.list[_]%></span><%}%>",
     "s": "1\u001d"
   }, {
     "keys": ["list1"],
     "path": "div[mx-guid=\"g1\u001f\"]",
     "tmpl": "<%for(let a=0;a<$$.list1.length;a++){%><span><%=$$.list1[a]%></span><%}%>",
     "s": "2\u001d"
   }]
 }

这样如果只有list这个数据发生变化时,只有第一个div会被重新渲染
这种方式受dom结构的影响,无法再做进一步的细粒度的拆分,有时候刷新区域仍然较大

真实dom节点比对更新

主流的前端开发框架都有虚拟dom,通过虚拟dom比对前后的差异变化,从而最小化的更新界面。

因为magix一直使用的是字符串模板,如果直接转成虚拟dom的形式,要想性能最优,jsx是最理想的方式(通过工具直接把类似字符串的形式转成方法的调用)。这样一来开发者需要做很大的转变,再一个目前也没有人和精力来做这个事情。

关于dom diff网上的方案非常多,虚拟和虚拟的,虚拟和真实的,真实和真实等。考虑到成本问题,目前采用的是真实dom与真实dom的对比(1.不需要考虑浏览器兼容2.不需要做转换3.不需要自己实现),只做好diff即可。https://github.com/patrick-steele-idem/morphdom

差异化更新与组件的问题

组件销毁、重建问题

考虑这样的html代码

<mx-calendar
    selected="<%=currentDate%>"
    mx-change="showDate()"/>

mx-calendar组件的日期选中受currentDate的控制,如果第一次currentDate是2018-01-01,数据变化后currentDate是2018-04-01

magix在差异化更新时,由于组件所在的节点是一个特殊节点,比较到该节点时,因为不知道组件会如何变化,所以会销毁旧组件,更新完dom节点后,再实例化新组件。

这样一来因为一个小小的数据变化,需要销毁旧组件,再渲染新组件,显得比较笨重了。

magix中节点diff的步骤如下:

  1. 如果新旧节点一样(即同样的标签) [是2否13]
  2. 如果旧节点是一个组件所在的节点 [是3否11.12]
  3. 如果旧节点上一次渲染的innerHTML与本次渲染的一致 [是4否6]
  4. 如果旧节点上的组件上一次的数据与本次一致 [是5否6]
  5. 如果旧节点上的组件是不带模板(即附加行为型)的组件 [是6]
  6. 如果旧节点上的组件和新节点上的待渲染的组件一样(即同类型的组件),且有assign方法 [是7否9]
  7. 调用组件的assign方法,如果返回值为true则继续调用组件的render方法 [是8]
  8. 跳过更新
  9. 销毁旧组件
  10. 渲染新组件
  11. 更新节点属性
  12. 更新子节点
  13. 替换节点

流程中重要的一点是组件即view有没有assign方法,如果有该方法,则调用该方法把数据传递进去,组件在该方法内完成数据的更新,如果该方法的返回值是true,则再调用组件的render方法完成更新,从而不需要销毁组件

自己添加的属性被删除问题

因为是真实dom的比对,开发者如果不通过view提供的updater来更新界面,而是自己改变了dom节点上的属性,则界面在下次刷新时,这些自己添加的属性会被删除。目前的解决方案是最好不要自己操作dom,如果要操作dom,则写一个带有assign方法的组件来操作dom,因为magix遇到带有assign方法的组件所在的节点时,全权交与组件处理。

其它问题

dom比对的方式千万不要自己创建、删除任何节点!!!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions