码迷,mamicode.com
首页 > 其他好文 > 详细

我们用5分钟写了一个跨多端项目

时间:2020-12-24 12:14:06      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:引入   内容   依据   bpa   条件   逻辑   能力   对应关系   编辑   

我们用5分钟写了一个跨多端项目

技术图片
作者|许国栋(chameleon 成员)
编辑|覃云
cml 作为真正让一套代码运行多端的框架,提供标准的 MVVM 模式,统一开发各类终端。同时,拥有各端独立的运行时框架 (runtime)、数据管理 (store)、组件库 (ui)、接口 (api)。此外,cml在跨端能力加强、能力统一、表现一致等方面做了许多工作。
今天,为了让大家的项目优雅升级,快速接入,给你带来一份丰盛的 cml 迁移指南~

源码地址:https://github.com/jalonjs/cml-first-demo

目录结构

和微信小程序一样,cml 包含一个描述整体程序的 app 和多个描述各自页面的 page。
小程序目录结构

.
├── components // 包含各个组件
├── pages // 包含各个页面
├── app.js // 包含各个组件
├── app.js  // 应用启动入口
├── app.json // 全局配置
├── app.wxss // 全局样式
└── project.config.json // 项目配置文件

cml 目录结构

.
├── dist // 各个端构建结果
│   ├── alipay 
│   ├── baidu 
│   ├── wx 
│   ├── web  
│   ├── weex 
│   └── config.json // 跨端配置 map 映射表
├── node_modules // 第三方库
├── mock // 模拟 接口数据 和 模板数据
├── src  // 源代码开发目录
│   ├── app // 应用启动入口
│   ├── assets // 静态资源
│   ├── components // 包含组件
│   ├── pages  // 包含页面
│   ├── store // 数据管理
│   └── router.config.json // 路由配置文件
├── chameleon.config.js // 项目配置文件
└── package.json // npm 包配置文件

如何修改配置

在小程序项目里面,分为:
小程序 —— 项目配置
可以在项目根目录使用 project.config.json 文件对项目进行配置。
配置示例:

{
  "miniprogramRoot": "./src",
  "debugOptions": {}
}

小程序 —— 全局配置
小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。
配置示例:

{
  "pages": ["pages/index/index", "pages/logs/index"],
  "window": {
    "navigationBarTitleText": "Demo"
  },
  "networkTimeout": {
    "request": 10000,
    "downloadFile": 10000
  }
}

小程序 —— 页面配置
每一个小程序页面也可以使用 .json 文件来对本页面的窗口表现进行配置。
页面的配置只能设置 app.json 中部分 window 配置项的内容,页面中配置项会覆盖 app.json的 window 中相同的配置项。
配置示例:

{
  "navigationBarBackgroundColor": "#ffffff",
  "navigationBarTextStyle": "black",
  "navigationBarTitleText": "微信接口功能演示",
  "backgroundColor": "#eeeeee",
  "backgroundTextStyle": "light"
}

同样,在 cml项目里面,分为以下几种配置方案。
cml —— 项目配置
chameleon.config.js 为项目的配置文件,你可以定制化构建,比如是否带 hash,是否压缩等等。
配置示例:

// 设置静态资源的线上路径
const publicPath = ‘//www.static.chameleon.com/static‘;
// 设置 api 请求前缀
const apiPrefix = ‘https://api.chameleon.com‘;
// 合并配置
cml.config.merge({
  wx: {
    build: {apiPrefix}
  },
  alipay: {
    build: {apiPrefix}
  },
  baidu: {
    build: {apiPrefix}
  },
  web: {
    dev: {
      hot: true,
      console: true
    },
    build: {
      publicPath: `${publicPath}/web`,
      apiPrefix
    }
  },
  weex: {
    build: {
      publicPath: `${publicPath}/weex`,
      apiPrefix
    }
  }
})

cml —— 全局配置
cml 项目 app 目录下的 app.cml 文件的 <script cml-type="json" /> 用来对 cml应用 进行全局配置,具有 跨端配置 和 差异化 的能力
配置示例:

<script cml-type="json">
{
  "base": {
    "window": {
      "navigationBarTitleText": "各个端共同 title",
    },
    "permission": {
      "scope.userLocation": {
        "desc": "你的位置信息将用于小程序位置接口的效果展示"
      }
    }
  },
  "wx": {
    "window": {
      "backgroundTextStyle":"light",
      "navigationBarBackgroundColor": "#fff",
      "navigationBarTitleText": "差异化 title",
      "navigationBarTextStyle":"black"
    }
  },
  "baidu": {
    "window": {
      "backgroundTextStyle": "light"
    }
  },
  "alipay": {
      "window": {
        "defaultTitle": "Chameleon"
      }
  }
}
</script>

cml —— 页面 / 组件配置
通过 usingComponents 配置 组件路径 注册引用的组件。
配置示例:

<script cml-type="json">
{
  "base": {
    "usingComponents": {
      "navi": "/components/navi/navi",
      "navi-npm": "cml-test-ui/navi/navi"
    }
  },
  "wx": {
  },
  "alipay": {
  },
  "baidu": {
  },
  "web": {
  },
  "weex": {
  }
}
</script>

如何使用路由能力?

小程序配置路由
app.json 配置项列表的 pages 字段用于指定小程序由哪些页面组成,每一项都对应一个页面的 路径 + 文件名 信息。
数组的第一项代表小程序的初始页面(首页)。新增 / 减少页面,需要对 pages 数组进行修改。
如果项目有 pages/index/index.wxml、pages/logs/logs.wxml 两个页面,则需要在 app.json 中写。

{
  "pages": ["pages/index/index", "pages/logs/logs"]
}

cml 配置路由
src/router.config.json是路由的配置文件,cml 内置了一套各端统一的路由管理方式。相应有 cml 路由配置映射如下:

{
  "mode": "history",
  "domain": "https://www.chameleon.com",
  "routes":[
    {
      "url": "/cml/h5/index",
      "path": "/pages/index/index",
      "mock": "index.php"
    },
    {
      "url": "/cml/h5/logs",
      "path": "pages/logs/logs",
      "mock": "logs.php"
    }
  ]
}

文件名不需要写文件后缀,cml框架会自动去寻找对于位置的 .cml 文件进行处理。
小程序使用路由

class App {
data = {
store,
routerConfig
}
created(res) {
}
}

export default new App();
</script>

细心的你会发现,小程序中app.json app.js app.wxss和 src/app/app.cml的对应关系如下:
![](https://s4.51cto.com/images/blog/202012/19/e208d308fd586d6d63823dfb169774dd.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
** 小程序注册页面**
在小程序项目里面,Page(Object) 函数用来注册一个页面。接受一个 Object 类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。
**示例代码:**

// index.js
Page({
data: {
text: ‘This is page data.‘
},
changeText: function(e) {
// sent data change to view
this.setData({
text: ‘CML‘
})
}
})


** cml 注册页面
示例代码:**

<script>
class Index {
data = {
text: ‘Chameleon‘
}
methods = {
changeText: function(e) {
// sent data change to view
this.text = ‘CML‘;
}
}
computed = {}
watch = {}
};
export default new Index();
</script>

** 小程序注册组件**
在小程序项目里面,Component(Object) 构造器可用于定义组件,调用 Component 构造器时可以指定组件的属性、数据、方法等。
**示例代码:**

Component({
properties: {
myProperty: { // 属性名
type: String, // 类型(必填)
value: ‘‘ // 属性初始值(可选)
},
myProperty2: String // 简化的定义方式
},
data: {
text: ‘‘
}, // 私有数据,可用于模板渲染

// 生命周期函数,可以为函数,或一个在 methods 段中定义的方法名
attached() { },
ready() { },
methods: {
onMyButtonTap() {
this.setData({
// 更新属性和数据的方法与更新页面数据的方法类似
text: ‘wx‘
})
}
}
})

** cml 注册组件
示例代码:**

<script>
class MyComponent {
props = {
myProperty: { // 属性名
type: String, // 类型(必填)
default: ‘‘ // 属性初始值(可选)
},
myProperty2: String // 简化的定义方式
}
data = {
text: ‘‘
} // 私有数据,可用于模板渲染

beforeMount() {}
mounted() {}
methods = {
onMyButtonTap() {
this.text = ‘cml‘
}
}
computed = {}
watch = {}
};
export default new MyComponent();
</script>


### 如何声明生命周期
统一各端应用生命周期的定义,是跨端框架的重要组成,也是迁移的必经之路。
** 小程序声明生命周期**
可以在 App(Object)、Page(Object)、Component(Object) 传入Object参数,其指定小程序的生命周期回调等。
**代码示例:**

// index.js
Page({
onLoad(options) {
// Do some initialize when page load.
},
onReady() {
// Do something when page ready.
},
onShow() {
// Do something when page show.
},
onHide() {
// Do something when page hide.
},
onUnload() {
// Do something when page close.
},
onShareAppMessage() {
// return custom share data when user share.
}
})

** cml 声明生命周期**
在.cml 文件 <script /> 代码块返回的对象实例,其指定生命周期回调
**示例代码:**

<script>
class Index {
beforeCreate(query) {
// data 数据挂载到 this 根节点上之前,以及 methods 所有方法挂载到实例根节点之前
// 注意:只用页面的 beforeCreate 钩子 会返回页面 query
console.log(‘App beforeCreate: 打开当前页面路径中的参数是 ‘, query)
}
created() {
// data,methods 里面的这些 events 挂载完成
console.log(‘App created‘)
}
beforeMount() {
// 开始挂载已经编译完成的 cml 到对应的节点时
console.log(‘App beforeMount‘)
}
mounted() {
// cml 模板编译完成, 且渲染到 dom 中完成, 在整个生命周期中只执行一次
console.log(‘App mounted‘)
}
beforeDestroy() {
// 实例销毁前
console.log(‘App beforeDestroy‘)
}
destroyed() {
// 实例销毁后
console.log(‘App destroyed‘)
}
};
export default new Index();
</script>

** App 生命周期映射**
小程序 app.js中的生命周期 -> cml src/app/app.cml
![](https://s4.51cto.com/images/blog/202012/19/ca56272cbc536f1d16ee992c53bad9cc.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
** Page 生命周期映射**
小程序 Page()中的生命周期 -> cml src/pages/mypage/mypage.cml
![](https://s4.51cto.com/images/blog/202012/19/a3c11899259d01b37ccac434b806223d.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
** Page 生命周期映射**
小程序 Page()中的生命周期 -> cml src/pages/mypage/mypage.cml
![](https://s4.51cto.com/images/blog/202012/19/ae866c7906bfdd9dd0c4d93cde996418.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
** Component 生命周期 映射**
小程序 Component()中的生命周期 -> cml src/components/mycomponent/mycomponent.cml
![](https://s4.51cto.com/images/blog/202012/19/f3d7752e4f73e7d2eecdaf16ddd88594.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
** 生命周期总结**
每个 cml 实例 (App、Page、Component) 在被创建时都要经过一系列的初始化过程。
例如,需要设置数据监听、编译模板、将实例挂载到 CML 节点 并在数据变化时更新 CML 节点等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给开发者在不同阶段添加自己的代码的机会。
cml 为App、页面 Page、组件 Component 提供了一系列生命周期事件,保障应用有序执行。
另外,如果你想使用某个端特定的生命周期,你可以从业务出发使用生命周期多态:
https://cmljs.org/doc/logic/lifecycle.html#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%A4%9A%E6%80%81
### 数据如何响应到视图
如今,双向数据绑定 & 单向数据流 已深入开发者日常,MVMM 开发模式算是框架标配。
数据绑定、条件渲染、列表渲染 是如何书写的呢?
**示例代码**
** 小程序使用数据响应**

<!--wxml-->
<view class="scroller-wrap">
<!-- 数据绑定 -->
<view>{{message}}</view>
<!-- 条件渲染 -->
<view wx:if="{{view == ‘WEBVIEW‘}}">WEBVIEW</view>
<view wx:elif="{{view == ‘APP‘}}">APP</view>
<view wx:else="{{view == ‘MINA‘}}">MINA</view>
<!-- 列表渲染 -->
<view wx:for="{{array}}" wx:for-index="index" wx:for-item="item">{{item}}</view>
</view>

// page.js
Page({
data: {
message: ‘Hello MINA!‘,
view: ‘MINA‘,
array: [1, 2, 3, 4, 5]
},
onLoad() {
this.setData({
message: ‘wx‘
})
}
})

** cml 使用数据响应**

<template>
<!--index.cml-->
<view class="scroller-wrap">
<!-- 数据绑定 -->
<view>{{message}}</view>
<!-- 条件渲染 -->
<view c-if="{{view == ‘WEBVIEW‘}}">WEBVIEW</view>
<view c-else-if="{{view == ‘APP‘}}">APP</view>
<view c-else="{{view == ‘MINA‘}}">MINA</view>
<!-- 列表渲染 -->
<view c-for="{{array}}" c-for-index="index" c-for-item="item">{{item}}</view>
</view>
</template>
<script>
class Index {
data = {
message: ‘Hello MINA!‘,
view: ‘MINA‘,
array: [1, 2, 3, 4, 5]
}

beforeCreate () {
this.message = ‘cml‘
}
};
export default new Index();
</script>

** 数据响应总结**
cml运行时框架 提供了跨端响应式数据绑定系统 (Data binding),当做数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新。
只需要将 view<-->model 交互部分逻辑,作简单迁移,便可使它成为跨多端的数据响应系统。
### 事件交互
cml 支持一些基础的 事件,保障各端效果 (类型、绑定、事件对象) 一致运行。
**示例代码**
** 小程序使用事件**

<!--wxml-->
<view id="tapTest" data-hi="WeChat" bindtap="tapName">Click me!</view>

// page.js
Page({
tapName(event) {
console.log(event)
}
})

** cml 使用事件**

<template>
<view id="tapTest" data-hi="WeChat" c-bind:tap="tapName">
<text>Click me!</text>
</view>
</template>
<script>
class Index {
methods = {
tapName(e) {
// 打印事件对象
console.log(‘事件对象:‘, e);
}
}
}
export default new Index();
</script>

** 事件使用总结**
同时,还支持 自定义事件,用于父子组件之间的通信。
另外,如果你想要使用某个端特定的事件,cml 并不会限制你的自由发挥,你可以从业务出发使用 组件多态 或者 接口多态 差异化实现功能。
### 布局和外观
各端描述 布局和外观的层叠样式表 (CSS) 实现存在差异,包括不限于 布局、盒模型、定位、文本。
所以, cml 框架内置跨端一致性基础样式能力。
并且,定义了用于描述页面的样式规范 CMSS(Chameleon Style Sheet):
https://cmljs.org/doc/view/cmss.html
** 如何导入外部样式**
使用 @import 语句可以导入外联样式表,@import 后跟需要导入的外联样式表的相对路径,用 ;表示语句结束。
** 小程序导入外部样式**

示例代码:
/ common.wxss /
.small-p {
padding:5px;
}

/ app.wxss /
@import "common.wxss";
.middle-p {
padding:15px;
}

** cml 导入外部样式**
详细文档:https://cmljs.org/doc/api/runtime/@import.html
**示例代码:**

/ common.css /
.small-p {
padding: 5px;
}

<!-- app.cml -->
<style>
@import ‘./common.css‘;
.middle-p {
padding:15 cpx;
}
</style>

** 样式使用总结**
同时,为了统一多端尺寸单位,呈现效果一致,同时页面响应式布局,cml 项目统一采用 cpx 作为尺寸单位,规定以屏幕 750px(占满屏幕)视觉稿作为标准。
而且,各端样式表拥有的能力 不尽相同,是项目迁移的主要阵地之一。
另外,如果你想要使用某个端特定的样式能力,cml 并不会限制你的自由发挥,你可以从业务出发使用 样式多态
**注意:由于 chameleon 应用是 跨多端web native 小程序框架,如果需要跨native,必须使用 flexbox 进行样式布局!!!**

### 自定义组件
开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用。自定义组件在使用时与基础组件非常相似。
** 小程序创建自定义组件**
**代码示例:**

Component({
properties: {
// 这里定义了 innerText 属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: ‘default value‘,
}
},
data: {
// 这里是一些组件内部数据
someData: {}
},
methods: {
// 这里是一个自定义方法
customMethod() {}
}
})

** cml 创建自定义组件
示例代码**

<script>
class MyComponent {
props = {
// 这里定义了 innerText 属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: ‘default value‘,
}
}
data = {
// 这里是一些组件内部数据
someData: {}
}
methods = {
// 这里是一个自定义方法
customMethod() {}
}
computed = {}
watch = {}
};
export default new MyComponent();
</script>

** 如何使用自定义组件**
使用已注册的自定义组件前,首先要进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径。
**代码示例:**
在 page.json 中进行引用声明

{
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}

在 page.wxml 中使用

<view>
<!-- 以下是对一个自定义组件的引用 -->
<component-tag-name inner-text="Some text"></component-tag-name>
</view>

** cml 使用自定义组件
代码示例:**
在 page.cml中<script cml-type=‘json‘ />进行引用声明

<script cml-type="json">
{
"base": {
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}
}
</script>

在 page.cml中<template />使用:

<template>
<view>
<!-- 以下是对一个自定义组件的引用 -->
<component-tag-name inner-text="Some text"></component-tag-name>
</view>
</template>

### 如何实现父子组件事件通信
事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的 事件,引用组件的页面可以监听这些事件。
** 小程序组件通信
代码示例:**

<!-- 页面 page.wxml -->
<view>
<my-component bindcustomevent="onMyEvent"></my-component>
</view>

// 页面 page.js
Page({
methods: {
onMyEvent(e) {
console.log(e.detail) // 自定义组件触发事件时提供的 detail 对象
}
}
})

<!-- 组件 my-component.wxml -->
<view>
  <button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>
</view>
// 组件 my-component.js
Component({
  methods: {
    onTap() {
      this.triggerEvent(‘customevent‘, {}) // 触发 自定义组件事件
    }
  }
})

cml 组件通信
代码示例:

<!-- 页面 page.cml -->
<template>
  <view>
    <my-component c-bind:customevent="onMyEvent"></my-component>
  </view>
</template>
<script>
class Index {
  methods = {
    // 这里是一个自定义方法
    onMyEvent(e) {
      console.log(e.detail) // 自定义组件触发事件时提供的 detail 对象
    }
  }
};
export default new Index();
</script>
<script cml-type="json">
{
  "base": {
      "usingComponents": {
        "my-component": "path/to/the/custom/component"
      }
  }
}
</script>
<!-- 页面 path/to/the/custom/component.cml -->
<template>
  <view>
    <button c-bind:tap="onTap">点击这个按钮将触发“myevent”事件</button>
  </view>
</template>
<script>
class MyComponent {
  methods = {
    // 这里是一个自定义方法
    onTap() {
      this.$cmlEmit(‘customevent‘, {}) // 触发 自定义组件事件
    }
  }
};
export default new MyComponent();
</script>
<script cml-type="json">
{}
</script>

组件使用总结

和小程序一样,cml 框架 提供了大量 内置组件 和 扩展组件,抹平多端差异,便于开发者通过组合这些组件,创建出强大的应用程序。
扩展组件需要额外引入。如:

<script cml-type="json">
{
  "base": {
      "usingComponents": {
        "c-dialog": "cml-ui/components/c-dialog/c-dialog"
      }
  }
}
</script>

在执行 cml build 构建打包时,cml 框架 会按需打包引用的内置组件和扩展组件,为代码瘦身。
内置组件 和 扩展组件 都是支持跨多端的,对于一些没有提供的某个端的组件,可以通过 组件多态来实现。
如果希望使用小程序端的原生组件,那么可以在原生标签前加上 origin-*,cml框架会渲染原生组件 参考
*注意:origin- 只能在灰度区文件中使用。**
如在 map.wx.cml 文件中使用原生地图组件 <map/>:

<!-- map.wx.cml -->
<template>
  <origin-map
    id="map"
    longitude="113.324520"
    latitude="23.099994"
    controls="{{controls}}"
    bindcontroltap="controltap"
    style="width: 100%; height: 300px;"
  ></origin-map>
</template>

如何调用平台接口能力

在小程序里面,可以通过微信原生 API,调起如获取用户信息,本地存储,支付功能等。
示例代码:

try {
  wx.setStorageSync(‘name‘, ‘Hanks‘)
} catch (e) {
  console.error(e)
}

同样,在 cml 项目里面可以这样调用:
示例代码:

import cml from ‘chameleon-api‘
cml.setStorage(‘name‘, ‘Hanks‘).then((res)=>{
  console.log(res)
},function(e){
  console.error(e)
})

接口使用总结
cml 框架提供了丰富的多态接口,可以调起各端提供的原生能力,如系统信息、元素节点信息、动画效果、本地存储、网络请求、地理位置等。请参考 API 文档。
chameleon-api提供的接口都是支持跨多端的,对于一些没有提供的某个端的原生接口,可以通过 接口多态 来调用。

迁移实例

下面给出各端 (vue、weex、小程序) 迁移 cml 指南 以及 cml 导出组件到各端指南的具体迁移文档:

我们用5分钟写了一个跨多端项目

标签:引入   内容   依据   bpa   条件   逻辑   能力   对应关系   编辑   

原文地址:https://blog.51cto.com/15057848/2567658

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!