# UI 框架

在 JSKit App 中,所有的页面都会使用 JS 语言来描述。JSKit App 的 UI 框架采用单线程模型,所有的代码都是同步的,运行在主线程的。

另,JSKit App 提供有定时器 Timer接口可做主线程延时调度,和多线程 Worker接口做多线程支持。

UI 模块提供了将 View 节点(VNode)树渲染成页面(VPage)的渲染器(Renderer),和控制页面跳转的路由器(Router)。

JSKit App 所实现的 UI 框架的接口比较底层,使用的流程大致是:

  • 根据预想的 VPage 页面结构(VNode 树),解构成一系列的 VNode
  • 在恰当的时机(VPageLifecycle#onLoad)多次调用 createNode 传递 VNode 给 JSKit App
  • 在使用 VPage 路由方法加载页面时,JSKit App 根据 VNode 的 pid id 两个属性对VNode 树进行重建,渲染页面显示。

UI 的核心数据结构 (opens new window)

提示

现在已经有了新的 jsk-ui 扩展能够提供更友好的 UI API。jsk-ui 扩展的底层依然是基于此框架提供的接口实现。

官方扩展仓库的所有代码,包括 jsk-ui 扩展在内,都是开源的:https://github.com/jskitapp/jskitapp-addon (opens new window) ,欢迎 PR 。

# VNode

VNode 代表基础的 View 组件。

基础的 VNode 有如下的结构:

interface VNode {
    pid?: number;
    id: number;
    style?: LayoutStyle;
}

interface VNodeGroup extends VNode {}
  • pid :父节点的 id,父节点一般为 VNodeGroup 的子类。
  • id :当前节点的 ID。
  • style :作用于当前节点的样式,详见布局与样式

pidid 建议使用本模块提供的 generateId() 来生成。

VNode 按是否可有 VNode 子节点,可分为两类:

  • 不可包含子节点的 VNode
  • 可包含子节点的 VNodeGroup

VNode 的子类 T 详细描述见 View 组件

UI 框架对调用 createNode 创建 VNode 的顺序有要求:后创建的 VNode 的 pid 指代的必须是 VNodeGroup 的子类并已经存在(之前已经使用 createNode 创建过)。

# 管理 VNode

interface VNodeManager {
    createNode<T extends VNode>(obj: T): void;
    updateNode<T extends VNode>(obj: T): void;
    removeNode(nodeId: number): void;
    invoke(nodeId: number, args: any): void;
}
  • createNode 创建 VNode。
  • updateNode 更新 VNode。
  • removeNode 根据 nodeId 删除 VNode。
  • invoke 执行 nodeId 上的方法,执行方法的信息使用 args 参数来描述

createNodeupdateNoderemoveNode 三个方法用来控制节点的属性,invoke 用来执行节点的方法。

# VPage

VPage 在 JSKit App 中表示一个页面。

# 添加 VPage

function addPage(pageId: number, plife: VPageLifecycle): VNodeManager;
  • pageId:VPage 的 唯一的 ID,同时也是 VPage 所管理的 VNode 树根 VNode的 id。
  • plife:VPageLifecycle 对象,见 Page 状态机
  • Returns:VNodeManager 对象,见管理 VNode

注意

添加 VPage 只是向 UI 框架注册了页面,添加的 VPage 并不会立刻被渲染成页面。只在调用了 VPage 路由的相关方法才会进入渲染流程。

# 获取 VPage

function getPage(pageId: number): VNodeManager;

获取 VNodeManager,见管理 VNode

# 移除 VPage

function removePage(pageId: number): void;
  • pageId:要移除的 VPage 的 ID

注意

不可移除非 UNLOAD 状态的 VPage。

# VPage 状态机

初始状态为 UNLOAD 状态,其所有的状态转换如下:

            +------+
   +------> |UNLOAD| <-------+
   |        +------+         |
   |                         |
   +         +    ^          +
             |    |
+------+ <-------------+ +------+
|HIDDEN|     |    |      |SHOWED|
+------+ +-------------> +------+
             |    |
   ^         v    +          ^
   |                         |
   |        +------+         |
   +------+ |LOADED| +-------+
            +------+

状态转换过程中会回调以下钩子方法:

interface VPageLifecycle {
    style?: LayoutStyle;
    onLoad?: (cb: VNodeManager) => void;
    onShow?: (cb: VNodeManager) => void;
    onHide?: (cb: VNodeManager) => void;
    onUnload?: (cb: VNodeManager) => void;
    onEvent?: (name: string, args: any) => boolean;
}
  • style:作用于 VPage 的根节点的样式,详见布局与样式
  • onLoad:VPage 的 VNode 树节点正在加载,应该在此回调中使用 VNodeManager#createNode 构建 VNode 树
  • onShow:VPage 已显示,当 VPage 移到 VPage 栈的栈顶或 APP 切换到前台时回调。
  • onHide:VPage 已隐藏,当 VPage 移到 VPage 栈的非栈顶或 APP 切换到后台时回调。
  • onUnload:VPage 的 VNode 树节点即将卸载时回调。
  • onEvent:来自于当前 VPage 的事件回调。

onEvent 事件:

事件 name 事件 args 返回值含义 触发场景
onBackPressed null true 表示消费了事件,false 表示未处理 按手机的返回按键时,当前栈顶的 VPage 会收到此事件

# VPage 路由

JSKit App 会维护一个 VPage 的栈结构,来实现多页面的路由跳转。

interface VPageInfo {
    id: number;
}
  • id:指定的 VPage 的 pageId
function loadPage(arg: VPageInfo): void;

加载某个页面。VPage 栈如果不为空,将会把当前栈中的所有 VPage 转换至 UNLOAD 状态,再将此指定页面转换至 SHOWED 状态。

注意

这里的状态转换,都是按照上面的 VPage 状态机表示的转换路径进行转换的。例如,将一个 VPage 从 UNLOAD 状态转换至 SHOWED 状态,按照状态机,会经过 LOADED 再到 SHOWED

function navigateTo(arg: VPageInfo): void;
  • 如果指定页面在当前栈中不存在:保留当前页面,跳转到应用内的某个页面。即,如果存在当前页面,当前页面会转至 HIDDEN 状态,然后加载指定页面到 SHOWED 状态。

  • 如果指定页面在当前栈中已存在:栈中当前页面之前的页面会转至 UNLOAD 状态,再将当前页面转至 SHOWED 状态。

function redirectTo(arg: VPageInfo): void;
  • 如果指定页面在当前栈中不存在:关闭当前页面,跳转到应用内的某个页面。即,如果存在当前页面,当前页面会转至 UNLOAD 状态,然后加载指定页面到 SHOWED 状态。

  • 如果指定页面在当前栈中已存在:栈中当前页面之前的页面会转至 UNLOAD 状态,再将当前页面转至 SHOWED 状态。

function navigateBack(arg = { delta: 1 }): void;

关闭当前页面,返回上一页面或多级页面。

参数:arg

属性 类型 默认值 是否必填 说明
delta number 1 返回的页面数,如果 delta 大于现有页面数,则返回到首页。

# VTemplate 与 VTempData

View 模版是一系列 VNode 的集合,也可称之为组件。

其中 VTemplate 的定义是:

interface VTemplate {
    id: number,
    name: 'view-temp';
    children: VNode[];
}

模版中,使用 ${+ prop +} 来标记模版中哪些属性的值是可以更改的,其中,prop 与 VTempData#data 中的 prop 需要一一对应。

VTempData 是模版的实例数据:

interface VTempData extends VNode {
    name: 'view-data';
    typeRef: number;
    data: {
        [prop: string]: any;
    }
}
  • typeRef 的值为指定模版的 id

例如:

//模版
let templateNode = {
    id: tmpNodeId,
    name: "view-temp",
    style: {
        flexGrow: 1,
        borderColor: "blue",
        borderWidth: "2dp"
    },
    children: [//注意,这是个数组
        {
            pid: tmpNodeId,
            id: ui.generateId(),
            name: "text",
            textSize: "17sp",
            gravity: "center",
            style: {
                flexGrow: 1,
                width: "100%",
                borderColor: "red",
                borderWidth: "2dp"
            },
            text: "${txtholder}",//属性的占位符
            onClick: "${onTap}"//方法属性也可被占位
        },
    ]
};

//模版数据
let templateDataNode = {
    pid: pid0,
    id: vid,
    typeRef: tmpNodeId,
    name: "view-data",
    data: {
        txtholder: "Hello Temp World",//模版数据,注意名字
        onTap: (name, obj) => {//被占位的方法的实现
            //
            return true;
        }
    }
}

# 添加 VTemplate

添加的模版是全局的,能够在所有 VPage 里共用。

function addTemplate(obj: VTemplate): void;

# 移除 VTemplate

可以通过模版 id 移除对应的 VTemplate:

function removeTemplate(id: number): void;

# VPageNode

VPageNode 是一个特殊的 VNode,它让一个 VPage 可以像一个 VNode 一样组织进另一个 VPage。这填补了 VTemplate 不能动态管理的设计空白。

interface VPageNode extends VNode {
    name: 'page-ref';
    typeRef: number;
}
  • typeRef 的值为指定 VPage 的 id

一个 VPageNode 节点逻辑上连结了两个 VPage:我们可以把 VPageNode 所在的 VPage 叫做宿主 VPage;把 VPageNode 引用的 VPage 叫做子 VPage

注意,子 VPage在同一时刻只能被一个 宿主 VPage 引用,被引用时将不允许使用 VPage 路由的相关方法再次加载;子 VPage的生命周期将与宿主 VPage保持同步。

例如:

let pageRefNode = {
    pid: homePageId,
    id: tabContentId,
    name: "page-ref",
    style: {
        flexGrow: 1,
    },
    typeRef: page1, // page1 为另一个 VPage 的 pageId
};

# 获取环境常量

function getEnv(key: string): any;

提供了获取环境常量的接口,获取诸如 API 版本号,Android 系统版本等信息,来判断接口可用性,做适配性的兼容操作等。

key 返回值类型 说明
jsk_version string jskit 的 version name
jsk_code number jskit 的 version code
android_sdk_int number 手机 andorid sdk level
args string | null 项目启动的参数

# ID 生成器

function generateId(): number;

生成一个唯一 ID。此方法能在一定范围内保证生成的数的唯一性。

# Toast 消息

function toast(msg: string): void;

显示一条 Toast 消息。

# 显示 Console

ui.invoke({method: 'showConsole'})

显示 Console 页面。