xinglie / xinglie.github.io

blog
https://xinglie.github.io
153 stars 22 forks source link

升级到Magix5 #81

Open xinglie opened 3 years ago

xinglie commented 3 years ago

2015~2018 magix3目标是项目统一,2018以后,magix5目标是性能及所有开发细节的完善

magix5核心思路

大到应用小到组件,均由数据描述驱动,由开发者同步或异步操作这份描述数据,再由magix5异步的更新界面。 这种方案尽可能的把浏览器资源优先用于处理重要的数据,而非界面更新。 比如上万条的列表,不用任何优化手段,全部渲染到界面上来,同时要进行全选,反选等选中操作。 首先用数据把这个列表表示出来,然后交与magix5渲染界面。如果进行全选、反选,也是操作数据进行相应的选中表示,再由magix5更新界面,如果要获取哪些选中行,也是通过数据进行查找获取。

即:一个功能纯靠数据进行实现,ui仅仅是把描述数据进行可视化。浏览器操作数据是快速的,这一步或同步,或异步进行操作。后续magix5则是通过异步、确保浏览器不卡的情况下更新界面,在magix5更新的过程中,用户依然可以继续操作这份数据,magix5最终会把界面渲染到与这份数据一致。

弊端:因为更新是异步的,如果界面更新量大,用户在后续操作时,数据变化不能及时反馈到界面上。当数据量大时,依然需要其它手段进行优化,不能单靠magix5

全局通用升级

at规则升级,由原来的@升级为@:。样式、模板及代码中所有使用@的地方均需要改写为@:

升级原因是现在npm包支持如@ali/sub-module这样的形式,之前的@很容易把这样的字符串识别为路径,故升级规则

magix-combine升级为magix-composer

因为和magix有关,magix5重新实现了模板、view嵌套渲染等机制,为了抛弃历史编译工具的包袱,直接建立新的编译工具来打包编译。

样式升级

删除global@path/to/style

全局样式需要使用新的规则来书写,同时为了减轻编译工作量,全局样式不再由编译工具处理。可阅读magix-composer升级章节来获取进一步的信息

如果需要,可使用如

let styleString=`style@:./path/to/style`;
Magix.applyStyle('@:moduleId'+'ext.style',styleString)

代替。其中style@:./path/to/style是编译器提供的替换器,更多其它替换器可查看magix-composer升级章节

其它使用升级

  1. css中保持选择器不被变换::global(.selector){}@global{.selector{}}
  2. css中引用样式:["ref@:./path.css:name"]{}
  3. 引用全局局部样式@:scoped.style:name
  4. html中引用样式:@:$(name)。只能引用当前文件有关联的样式,不能引用其它样式
  5. html中引用变量:@:$var(--name)。只能引用当前文件有关联的样式,不能引用其它样式
  6. html中引用@规则:@:$keyframes(scale)@:$font-face(scale)。只能引用当前文件有关联的样式,不能引用其它样式
  7. js中引用css3中的变量@:./path.css:--name;
  8. css中引用其它css文件中的变量color:var("ref@:./path.css:--name");
  9. js或样式中引用@规则 @:./path.css:@font-face(name) @:./path.css:@keyframes(name)
  10. 引用其它可能变化的字符串counter("ref@:./path.css#counter-name")
  11. 一半编译.user--name @keyframe anim--1,即只编译从后向前两个连接线--前面的部分,后面的保留,方便在模板中实现如<div class="user--{{=userType}}">带变量的场景
  12. css变量不编译,需要配置cssGlobalVarPrefixes这个数组,默认为['--mx-','--magix-']
  13. css变量全局编译,默认--scoped-前缀,在js中需要使用时@scoped.style:--scoped-var-name
  14. css变量其它正常写--var-name,引用其它文件中的color:var("ref@:./path.css--var-name")

模板升级

  1. 删除{{! html }}输出html的功能,使用<tag x-html="{{=html}}">代替
  2. each支持by ascby desc进行排序,支持step指定循环步长
  3. view参数传递必须写*号,如<mx-vframe src="./path/to/view" *user-id="{{=userId}}"/>
  4. 引用参数由原来的{{@ expr }} 升级为 {{# expr }}。这里主要考虑之前@升级为@:,为了保持统一和简单,这里不升级为{{@: expr }}而是直接改为{{# expr }}
  5. 模板中保留的变量 $viewId(用于获取当前view的id)。$slots(用于获取所有<mx-slot>)

更多不支持场景及其它细节修改可参考:https://github.com/thx/magix-composer/issues/1

代码升级

全新实现的渲染、事件、分片任务机制

如果不能直接使用esm,则模块标识符为magix5,如

import Magix from 'magix5'

vframe升级

由原来的dom id与vframe一一映射改为节点与vframe一一映射

  1. 原来通过vframe.idview.id可查出dom id,并通过document.getElementById获取dom节点,现在vframe.idview.id仅是内部全局唯一的一个标识,不再与dom节点有关联。如果需要通过vframeview对象反查对应的dom节点,可使用vframe.rootview.root获取所在的dom节点
  2. Magix.Vframe.get方法升级为Magix.Vframe.byNode通过dom节点对象获取它上面的vframeMagix.Vframe.byId通过前面提到的vframe.idview.id查找vframe
  3. 新增Magix.Vframe.root()获取根节点
  4. 删除vframe实例上的mountZoneunmountZonemountViewunmountView方法,原则上减少手动渲染view,全部走数据驱动界面的方案。
  5. 升级vframe实例上的mountVframeunmountVframemountunmount,同时第一个参数由字符串改为dom对象
  6. vframe实例上的invoke修改为异步,使用时需要let result = await vframe.invoke('viewMethod')
  7. vframe实例上新增unloadTest方法,允许view阻止卸载,实现高级场景的功能,如页面切换时,内容未保存提示。
  8. 删除created altered事件,高频且用处较少,后续可能会插件支持

删除view对象上的updater对象。

由原来的操作updater改为操作view

this.updater.get('userId')
//升级为
this.get('userId')

view更新改为异步

this.updater.digest();
console.log(Magix.node(`dom_`+this.id));

//升级为
await this.digest();
console.log(Magix.node(`dom_`+this.id));

view新增root对象

console.log(this.root);

view新增finale方法

用于在其它方法内确保异步更新的view完成更新。

export default Magix.View.extend({
    render() {
        this.digest();
    },
    async 'someEvent<click>'() {
        await this.finale();
        // your code
    }
})

支持全部事件

magix5在实现时,因为是全局统一绑定的事件,所以把所有事件都绑定在了捕获阶段。但之前所有版本都使用冒泡阶段,因此为了减少修改为捕获阶段带来的风险,目前仍采用冒泡方案,而对于需要捕获处理的,可参考以下代码


`div<scroll>&{capture:true}`(){

}

增加事件`指示器`:`&{capture:true}`,目前支持`capture`为`true`,把该事件升级为捕获阶段,及`&{passive:flase}`,允许在事件内部使用`preventDefault`。注意:浏览器并不是把所有事件的`passive`都默认为`true`,只有影响性能的如`mousewheel`等默认为`true`,如果遇到调用`preventDefault`调试工具有提示时,才需要写上`&{passive:flase}`

可合写为`&{capture:true,passive:false}`

https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

#### 强制assign方法

magix5强制所有view都必须实现`assign`方法,同时只有在该方法返回`false`时阻止更新当前`view`,其它值则更新
该方法的第2个参数升级为当前节点的`innerHTML`字符串。

#### 删除mixins,统一merge替代
```ts
import Dragdrop from '../gallery/mx-dragdrop/index';
import Dialog from '../gallery/mx-dialog/index';
export default Magix.View.extend({
    minins: [Dragdrop, Dialog],
    render() {
        this.digest();
    }
});
//升级为
import Dragdrop from '../gallery/mx-dragdrop/index';
import Dialog from '../gallery/mx-dialog/index';
export default Magix.View.extend({
    render() {
        this.digest();
    }
}).merge(Dragdrop,Dialog)

精简Magix.State

未来考虑使用数据中介来处理跨view的复用数据,正式版本Magix.State将考虑删除,请不要再使用。

增加task lowTask taskFinale lowTaskFinale实现分片任务

import {task,lowTaskFinale} from 'magix'
let collectRects = (nodes) => {
    //process nodes client rectangle
};
let walk = nodes => {
    for (let e of nodes) {
        if (e.nodeType == 3) {
            task(collectRects, [e]);
        } else if (e.nodeType == 1) {
            if (e.childNodes.length) {
                task(walk, e.childNodes);
            } else if (e.tagName.toUpperCase() != 'TD') {
                task(collectRects, [e]);
            }
        }
    }
};
walk(nodes);
await lowTaskFinale();

magix-composer升级

大多数配置兼容magix-combine的

大纲性的编译深入了解可阅读:magix-composer模板编译阶段分析 magix-composer整体编译阶段分析

升级#开头的编译指令

#开头的编译指令升级为magix-composer#,更清楚的表达这是为什么服务的

https://github.com/thx/magix-composer/issues/5

替换器

方便在代码中以字符串的形式引用其它文件内容

let base64Image='base64@:./path/to/test.jpg';

内置的替换器有

  1. str以字符串存储文件内容,如str@:./path/to/test.css
  2. base64以base64存储文件内容,如base64@:./path/to/test.jpg
  3. style以字符串存储样式文件内容,Magix.applyStyle支持的样式及处理过程该style均支持
  4. html以字符串存储html文件内容,该方法会压缩html

扩展替换器。

let composer = require('magix-composer');
composer.config({
    fileReplacerPrefixes:['action1','action2'],
    fileReplacerProcessor(action,file){
        console.log(file,action);
        if(action=='action1'){
             return 'action1 test';
        }
        return 'action2 test'
    }
});

mx-属性升级

模板中所有使用mx-属性的地方会被编译工具自动升级为mx5-

<div mx-click="test()"></div>
<!--编译渲染后为-->
<div mx5-click="test()"></div>

该过程是自动的,开发时仍然写mx-,需要知道有这个转换过程。

这里主要为同一个页面存在多个magix做准备,版本不同,mx-mx5-mx6-前缀不同,彼此间不会影响。

mx-key指定节点标识

如果想进一步提升渲染性能,可对如each列表指定key,magix5会在数据移动时,直接移动节点替换逐个更新节点。新手请忽略该配置

{{each list as e}}
<div mx-key="{{=e.id}}">
{{=e.id}}
</div>
{{/each}}

mx-updateby更新view

magix-composer会自动分析出渲染mx-view所依赖的代码中的根数据,如

<mx-vframe src="./path/to/view" *user-id="{{# user.id }}" *card="{{# list[0].cards[1]}}"/>

./path/to/view只与根数据userlist有关联,如果这2项数据未更新,则view不会触发更新。

如果有以下代码更新界面

this.digest({
    coupons:[]
});

./path/to/view会跳过更新。

可通过mx-updateby强制view与某些数据关联,如

<mx-vframe src="./path/to/view" *user-id="{{# user.id }}" *card="{{# list[0].cards[1]}}" mx-updateby="user,list,coupons"/>

新手请忽略该配置

全局样式

全局样式书写规则为mx-前缀,如.mx-tag-input,只有类选择器,其它一律禁止使用。禁止标签、ID、多级嵌套等。

全局样式的引入可通过入口页面link标签引入,或在入口代码中使用

let styleString=`style@:./path/to/style`;
Magix.applyStyle('@:moduleId'+'ext.style',styleString);

引入。

可通过magix-composer的配置项对某些规则进行关闭检测,如

composer.config({
    checker: {//代码检测
        /**
         * 模板中class的代码检测
         * @param {string} selector 模板中使用到的样式选择器
         */
        tmplClassCheck(selector) {
            return selector &&
                !selector.startsWith('mx-') &&
                !selector.startsWith('magix-');
        }
    }
});

$viewId $slots

由编译工具注入,能在模板中直接使用的变量使用$开头。

$viewId获取当前view对象的id,模板中的$viewId等同于代码中的this.id

如模板

<!-- index.html -->
<button id="{{= $viewId }}_btn">click here</button>

代码

import Magix from 'magix5';
export default Magix.View.extend({
    async render() {
        await this.digest();
        let btnNode = Magix.node(`${this.id}_btn`);
        console.log(btnNode)
    }
})

试验中的片断传递


<mx-slot name="list" fn="$list">
list fn:
{{if list}}
{{each $list as item index}}
{{=index+1}}:{{=item.title}}
{{/each}}
{{/if}}
{{=outerVariable}}
</mx-slot>

<mx-vframe src="./x" *ref="{{# $slots.list }}"/>

带`fn`属性的`mx-slot`会被编译成函数,不带`fn`的则为普通变量

现阶段在当前`view`中声明并使用`mx-slot`没有任何问题,但通过参数的形式传递给其它`view`有可能在`view`渲染传递数据上有问题,请先在简单场景下使用。

使用`mx-slot`
```html
<mx-slot use="list"/>

<mx-slot use="list" params="a,b,c"/>

fn允许有默认值,如

<mx-slot name="test" fn="num=20,arr=['array'],obj={},str='string'">
     partial content {{=array}}
</mx-slot>

内嵌在mx-vframe直接作为参数

<mx-vframe src="./index">
   <mx-slot name="header">
        header content
   </mx-slot>
</mx-vframe>

./index的模板里使用

<mx-slot use="header">
       default content
</mx-slot>

不要套娃,如

<mx-vframe src="./c">
    <mx-slot name="a">
        <div id="slot-test">
            <mx-vframe src="./a">
                <mx-slot name="test" fn="abc">
                    inner "a" group abc value:{{=abc}}
                </mx-slot>
            </mx-vframe>
        </div>
    </mx-slot>
    <mx-slot name="header">
        some text here;{{=abc}}<button mx-click="change({data:'{{#abc}}'})"></button>
    </mx-slot>
</mx-vframe>

mx-slot遵循先声明后使用,同时声明不能嵌套的规则,如

<!-- 不支持嵌套 -->
<mx-slot name="outer">
    <mx-slot name="inner">
        aaa
    </mx-slot>
</mx-slot>

<!-- 不支持先使用后声明 -->
<mx-slot use="a1"/>
<mx-slot name="a1">
    a1 content
</mx-slot>

<!-- 可以在slot内部引用其它slot -->

<mx-slot name="a1">
    a1 content
</mx-slot>

<mx-slot name="a2">
  a2 content
  <mx-slot use="a1"/>
  a2 content
</mx-slot>

mx-slot为试验用法,不用或减少使用