MIP 数据驱动机制

MIP 提供了一套数据驱动的机制来提升交互能力,有过 Vue/React 开发经验的同学对这套机制应该不会陌生。

首先举一个简单的例子来演示数据绑定的效果,点击按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 定义数据 --> <mip-data> <script type="application/json"> { "count": 0 } </script> </mip-data> <p> 当前按钮点击了: <!-- 定义数据绑定 --> <span m-text="count">0</span><!-- 定义点击事件触发 --> <button on="tap:MIP.setData({ count: count + 1 })">点击按钮</button> </p>

其效果如下所示:

当前按钮点击了:0

这就演示了一个最简单的数据驱动全流程。

MIP 数据驱动机制主要包含了以下三个部分:

  1. 数据定义:通过 <mip-data> 标签块实现对数据的初始化定义;
  2. 数据修改:通过 MIP.setData() 方法实现对定义数据的运算和修改;
  3. 数据绑定:通过 m-bind:m-text 等绑定表达式实现对标签属性和文本的绑定;

在本文后续的内容当中,将分别对这三块内容进行介绍。

数据定义

需要进行各种数据操作之前,首先需要在 <mip-data> 定义数据的初始值。页面可以定义多个 <mip-data>,不同的数据块在各自的数据源解析加载完成后,自动合并到同一个 store 当中,因此应该尽量避免字段重复的情况。

合并规则

具体的数据合并规则为:

  1. 当新旧属性值类型不同时,新属性值将会直接覆盖旧属性值;
  2. 当新属性值为字符串、数字、数组和 null 时,新属性值将直接替换旧属性值;
  3. 当新旧数据的属性的值均为字面量对象时,将会对子对象进行递归合并;

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<mip-data> <script type="application/json"> { "a": 1, "b": [{c: 1}], "d": { "e": 3, "f": 4 } } </script> </mip-data> <mip-data> <script type="application/json"> { a: 'abc', b: [{d: 1}], d: { f: 5, g: 6 } } </script> </mip-data>

两组数据合并后,<mip-data> 所存的数据为:

1
2
3
4
5
6
7
8
9
{ a: 'abc', b: [{d: 1}], d: { e: 3, f: 5, g: 6 } }

提示: 所以对于需要替换整个对象的这类需求,为避免对象的递归合并造成替换失败,可以将替换对象放到一个数组里,再对数组做替换操作。

数据定义有两种方式,同步数据和异步数据。

同步数据

同步数据需要在 <mip-data> 内定义 <script type="application/json"> 数据块,这些数据块需要符合 JSON 所要求的数据格式,只包括 String、Number、Object、Array、null,而 Date、RegExp、Function、undefined 这些都是不允许的。如:

1
2
3
4
5
6
7
8
9
10
11
12
<mip-data> <script type="application/json"> { "name": "张三", "age": 25, "job": { "desc": "互联网从业者", "location": "北京" } } </script> </mip-data>

异步数据

可以在 <mip-data> 定义 src 属性去指定异步加载的数据源,请求链接需要满足三个条件:

  1. 支持 HTTPS;
  2. 服务端配置 CORS 跨站访问许可;
  3. 支持 GET 请求;
  4. 返回的数据格式必须是 JSON 格式;

这是因为 MIP 页面被 MIP-Cache 抓取缓存之后,被缓存的页面会以 MIP CDN 的 URL,这个 URL 是 HTTPS 的,因此要求发送的请求也同样需要 HTTPS。(仅当使用 127.0.0.1 的本地接口进行调试时可使用 HTTP),同时在这种情况下的资源请求必然存在跨域,因此需要服务端配置 CORS 跨站访问许可

1
<mip-data src="https://path/to/your/data"></mip-data>

控制异步数据加载的属性还包括:

  1. credentials: {string} fetch 的 credentials 参数,默认值为 omit
  2. timeout: {number} 请求超时的时长,单位是 ms(毫秒),默认为 5000

例如:

1
2
3
4
5
<mip-data src="https://path/to/your/data" credentials="include" timeout=3000 ></mip-data>

当数据加载失败时(数据格式错误、超时、请求 404 等等),会抛出 fetch-error 事件,可以通过 on 表达式进行事件监听:

1
2
3
4
5
6
7
<mip-data src="https://path/to/your/404/data" on="fetch-error:MIP.setData({ msg: '数据加载出错' })" ></mip-data> <!-- 打印错误提示 --> <p m-text="msg"></p>

同时在配置了 src 属性的情况下,<mip-data> 还支持 refresh 方法对数据进行重新加载,重新加载的数据会根据前面提到的合并规则与原数据进行合并:

1
2
3
4
5
6
<mip-data id="userInfo" src="https://path/to/your/data" ></mip-data> <button on="tap:userInfo.refresh">点击刷新</button>

范围数据

<mip-data> 支持定义范围数据,具体做法是配置 idscope 属性进行范围声明。其中 id 是用来标识数据的命名空间,scope 是用来声明

1
2
3
4
5
6
7
<mip-data id="userInfo" scope> <script type="application/json"> { "name": "Li,Lei" } </script> </mip-data>

这样,这个 <mip-data> 数据块的内容将会全部放到 userInfo 字段下面。比如在数据绑定表达式里面,就可以通过 userInfo.name 获取数据:

1
<span m-text="'你好,' + userInfo.name"></span>

注意: <mip-data>id 属性应该与变量命名要求保持一致,推荐采用驼峰命名法。比如中横线命名、中文命名的 id 将会导致数据无法通过数据绑定表达式获取。

数据修改

MIP 提供了 MIP.setData 方法进行数据修改。MIP.setData 传入的参数同样要求是个 Object,因此会遵循同样的数据合并规则与原数据进行合并:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<mip-data id="userInfo" scope> <script type="application/json"> { "name": "Li,Lei", "from": "Shanghai" } </script> </mip-data> <button on="tap:MIP.setData({ userInfo: { name: 'Han,MeiMei' }, age: 18 })">点我</button>

最终得到的数据为:

1
2
3
4
5
6
7
{ userInfo: { name: 'Han,MeiMei', from: 'Shanghai' }, age: 18 }

可以在 MIP.setData() 表达式区域通过属性运算符(. 点运算符、['xxx'] 计算属性)进行数据访问:

1
2
3
4
5
6
<button on="tap:MIP.setData({ text1: userInfo.name, text2: userInfo['from'] })" >点我</button>

MIP.setData 支持写少量的表达式来进行数据运算,比如:

1
2
3
4
5
6
7
<button on="tap:MIP.setData({ wellcome: 'Hello ' + userInfo.name, firstName: userInfo.name.split(',')[0], lastName: userInfo.name.slice(userInfo.name.indexOf(',')) })" >点我</button>

更具体的表达式说明,清参考 表达式 小节进行学习。

数据绑定

通过数据绑定表达式,可以将数据作用到 HTML 元素节点上,从而实现数据驱动视图。数据绑定同样支持写少量的表达式进行数据运算,请参考 表达式 小节进行学习。

提示: 数据绑定的定位是 MIP 交互机制的扩展,负责响应用户进行页面交互行为。从性能和搜索抓取的角度考虑,定义了数据绑定的节点应该同时定义好对应属性的默认值。

1
2
3
4
5
<!-- 绑定文字的同时,应定义好初始值 "Li,Lei" --> <span m-text="userInfo.name">Li,Lei</span> <!-- 绑定 a 链的 href 需定义好默认的 href --> <a m-bind:href="reactive.link" href="https://www.baidu.com"></a>

m-text 绑定文字

前面的各种例子当中都有使用到 m-text 属性进行演示,其作用是将节点的 innerText 与数据进行绑定,这样就可以通过操作数据来修改节点显示的文案了:

1
2
3
4
5
6
7
8
<!-- 普通绑定 --> <p m-text="userInfo.name">Li,Lei</p> <!-- 绑定表达式 --> <p m-text="'您好,' + userInfo.name">您好,Li,Lei</p> <!-- 点击修改数据测试绑定效果 --> <button on="tap:MIP.setData({ userInfo: { name: '韩梅梅' } })">修改</button>

效果如下所示:

Li,Lei

您好,Li,Lei

m-bind 绑定属性

通过 m-bind 前缀可以声明属性的数据绑定,比如 m-bind:href,这些属性的更改会根据不同节点和属性的作用,表现出不同的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<mip-data id="testBind" scope> <script type="application/json"> { "link": "https://www.baidu.com", "name": "百度首页" } </script> </mip-data> <!-- 原先的 a 链接默认点击跳转百度首页 --> <a m-bind:href="testBind.link" href="https://www.baidu.com" target="_blank" m-text="'点击跳转至' + testBind.name" >点击跳转至百度首页</a> <!-- 点击下方按钮之后,将变成点击跳转 mipengine.org --> <button on="tap:MIP.setData({ testBind: { link: 'https://www.mipengine.org', name: 'MIP 官网首页' } })">点击更换 href</button>

效果如下所示:

点击跳转至百度首页

这类属性绑定除了可以作用在普通的 HTML 元素之外,还可以作用到 MIP 组件上,可以查阅相关组件文档,查看对应组件的哪些属性支持数据绑定。

下面的例子演示了修改 MIP 组件 mip-img 图片 src 的效果:

1
2
3
4
5
6
7
8
9
10
11
<mip-data> <script type="application/json"> { "imgSrc": "https://www.mipengine.org/static/img/sample_01.jpg" } </script> </mip-data> <mip-img m-bind:src="imgSrc" src="https://www.mipengine.org/static/img/sample_01.jpg"></mip-img> <button on="tap:MIP.setData({ imgSrc: 'https://mip-doc.cdn.bcebos.com/mipengine-org/assets/mipengine/logo.jpeg' })">点击更换图片</button>

效果如下所示:

m-bind:class 绑定 class

class 属性绑定语法跟 Vue 类似,通过对象语法和数组语法进行 class 绑定,绑定 class 只会影响到 m-bind:class 表达式当中声明的 class,其他未声明的将不受影响:

当使用对象语法的时候,只要对象的值为真(!!obj === true),对应的属性名就会写入节点的 class 当中。

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 对象语法, 支持简单运算 --> <div m-bind:class="{ 'example-active': isActive, 'info-example-wrapper': type === 'info', 'warn-example-wrapper': type === 'warn', 'error-example-wrapper': type === 'error' }" class="example-basic" >对象语法</div> <!-- 也可以直接将 object 直接绑定到 class 上 --> <div m-bind:class="classObject">对象语法</div>
1
2
3
4
5
<!-- 数组语法,支持简单运算 --> <div m-bind:class="[{ 'example-active': isActive }, type + '-example-wrapper']" class="example-basic" >数组语法</div>

效果都是一样的:

对象语法
数组语法

m-bind:style 绑定 style

提示: 一般情况下不推荐使用 style 绑定,请使用 class 绑定替代。只有当 class 绑定无法满足条件时,再使用 style 绑定。

style 绑定与 class 绑定类似,同样支持对象语法和数组语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<mip-data> <script type="application/json"> { "clickCount": 0 } </script> </mip-data> <!-- 对象语法 --> <div class="example-block" m-bind:style="{ transform: 'translateX(' + (clickCount % 4 / 4) * 100 + '%)' }" ></div> <!-- 点击触发 div 位置移动 --> <button on="tap:MIP.setData({ clickCount: clickCount + 1 })" m-text="'点了 ' + clickCount + ' 下'" ></div>

效果如下所示:

数组语法也一样,会首先将列表中多个对象合并后,再计算出对应的样式:

1
2
<!-- 数组语法 --> <div m-bind:style="[styleObj1, styleObj2]"></div>

m-bind:value 绑定 value

当绑定表达式绑到了表单元素的 value 属性上的时候,将自动获得双向绑定的能力,操作数据能影响表单元素的 value,填写表单,则会自动更新对应绑定的数据。

提示: 表单元素(input、textarea、select)的主要作用是表单提交,因此现有的 MIP 校验规则里面,表单元素必须包含在 <mip-form> 组件内部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<mip-data> <script type="application/json"> { "bindingText": "初始文字" } </script> </mip-data> <p m-text="'当前输入内容:' + bindingText">当前输入内容:初始文字</p> <!-- 绑定表单元素 value,注意表单元素需要 mip-form 包起来 --> <mip-form url="https://www.mipengine.org/api"> <input m-bind:value="bindintText" type="text"> </mip-form> <!-- 点击按钮操控数据 --> <button on="tap:MIP.setData({ bindingText: '' })">点击清空</button>

效果如下所示:

当前输入内容:初始文字