什么是 JSBox?
JSBox 是一个可以用来运行 JavaScript 脚本的iOS 应用,你可以通过他来执行标准的 JavaScript 脚本。
这种执行不是指跑在浏览器上,而是执行在一个完全原生的环境,效率很高。
并且提供了很多 iOS 原生的接口,这意味着你可以通过他做很多事情,包括但不限于:
-
写一个用来查询汇率的脚本;
-
写一个用于计算小费的脚本;
-
通过接口实现一个小小的应用,提供丰富的界面;
-
写一个文本收藏工具,用于收藏常用的文字;
-
写一个下载 Twitter 视频的小工具;
—— 引用自少数派:JSBox: 一个创造工具的工具
什么是 JSBox-Component?
JSBox-Component是一个在 JSBox 中实现 ui 组件化的库,可以将页面拆分为不同的组件从而复用。
:::link-card {url = “https://github.com/Aziteee/jsbox-component”} :::
此教程将带你快速上手 JSBox-Component 这个库,提高开发效率😎
结果展示

{ type: TabLayout, props: { index: 1, tabs: [ { title: "Home", icon: "102" }, { title: "Favorite", icon: "061" }, { title: "Settings", icon: "002" } ] }, layout(make, view) { make.bottom.right.left.equalTo(view.super.safeArea); make.height.equalTo(50); }, events: { onTabChanged: (index, tab) => { console.log(index, tab); } }}通过以上组件对象在屏幕上创建一个TabLayout ,并拥有以下功能:
-
传入一个
tabs属性指定每一个 tab 的文本和图标 -
可以通过传入
index属性指定一开始选中的 tab -
可以通过传入
tabColor和tabSelectedColor属性指定 tab 未选中和选中时的颜色 -
通过
onTabChanged事件可以监听当前选中 tab 是否改变 -
通过更改
index属性可以改变当前选中的 tab
创建项目
首先从Release中下载最新版本的component.js

创建一个空白项目,新建一个lib文件夹,把component.js 放到里面
接着创建一个components文件夹,里面存放该项目的自定义组件
jsbox-tablayout-demo││ main.js│ config.json│├─assets│├─components│├─lib│ component.js│├─scripts│└─strings编写组件(显示部分)
在components目录下新建TabLayout.js
首先从component.js 导入defineComponent方法,这个方法可以用来定义一个组件
const { defineComponent } = require("../lib/component");接着导出这个组件
const { defineComponent } = require("../lib/component");
module.exports = defineComponent();现在就可以开始编写组件了
给defineComponent传入一个对象,可以包含以下属性:
name
name属性就是这个组件的名字,和文件名保持一致即可
props
props 属性定义这个组件接收哪些属性值,这里就是index, tabs, tabColor, tabSelectedColor这四项,后面跟上这些属性的默认值。
index默认为 0,代表默认第一个 tab 被选中
events
events 是一个数组,包含了该组件的事件名,这里就只有onTabChanged事件。
methods
methods 定义了组件在页面上的实例可以被调用的方法,这里只有一个changeTab方法,用来改变选中的 tab,具体的函数之后再写。
watch
watch 可以监听属性值的变化,这里暂时用不着。
const { defineComponent } = require("../lib/component");
module.exports = defineComponent({ name: "TabLayout", props: { index: 0, // 默认选中第一项 tabs: [], tabColor: $color("gray"), // 默认未选中色为灰色 tabSelectedColor: $color("black") // 默认选中色为黑色 }, events: ["onTabChanged"], methods: { changeTab() { // 留位置给之后写具体逻辑 } },});编写 render 方法
render方法是一个组件的核心,它将输入的组件对象转换成一个 JSBox原生控件对象并返回,这样才能够在页面上渲染出来组件。
首先我们写一个普通的TabLayout ,是这样子的
{ type: "matrix", props: { columns: 1, itemHeight: 60, spacing: 0, scrollEnabled: false, bgcolor: $color("clear"), template: [ { type: "image", props: { id: "icon", bgcolor: $color("clear") }, layout(make, view) { make.centerX.equalTo(view.super); make.width.height.equalTo(25); make.top.inset(7); } }, { type: "label", props: { id: "title", font: $font(10) }, layout(make, view) { make.centerX.equalTo(view.prev); make.bottom.inset(13); } } ], data: [{ icon: { icon: $icon(102, $color("black"), $size(50, 50)) }, title: { text: "首页", textColor: $color("black"), } }] }}其实就是一个matrix ,每个格子有一个image和label控件,用于显示图标和文本,现在让我们把这个matrix封装进render方法里面。
首先每行的格子数也就是columns属性要变为传入的tabs属性的长度。
可以通过this访问到这个组件对象,用this.props.tabs 就可以拿到tabs这个属性的值了,如果有传入则会得到传入的值,没有就会得到前面定义的默认值。
render() { return { type: "matrix", props: { columns: this.props.tabs.length, itemHeight: 60, spacing: 0, scrollEnabled: false, bgcolor: $color("clear"), template: [ ... ], data: ... } } }然后将tabs转换为matrix的data属性
render() { // map函数内this的指向会改变,因此先把要props提取出来 const props = this.props; const data = props.tabs.map(function (item, index) { const color = index === props.index ? props.tabSelectedColor : props.tabColor; return { icon: { icon: $icon( item.icon, color, $size(50, 50) ) }, title: { text: item.title, textColor: color, } }; }); return { type: "matrix", props: { columns: 1, itemHeight: 60, spacing: 0, scrollEnabled: false, bgcolor: $color("clear"), template: [ { type: "image", props: { id: "icon", bgcolor: $color("clear") }, layout(make, view) { make.centerX.equalTo(view.super); make.width.height.equalTo(25); make.top.inset(7); } }, { type: "label", props: { id: "title", font: $font(10) }, layout(make, view) { make.centerX.equalTo(view.prev); make.bottom.inset(13); } } ], data: data } }}至此组件的显示就完成了。
渲染组件
在main.js中,从component.js导入render函数,再导入刚刚编写的TabLayout组件。
const { render } = require("./lib/component");
const TabLayout = require("./components/TabLayout");使用render函数创建一个页面,使用方法和$ui.render一致,不同的是这个函数能够识别自定义组件。
在views中写入组件对象,组件的type设置为刚导入的TabLayout ,再编写一下属性值和布局。
render({ views:[{ type: TabLayout, props: { index: 1, tabs: [ { title: "Home", icon: "102" }, { title: "Favorite", icon: "061" }, { title: "Settings", icon: "002" } ] }, layout(make, view) { make.bottom.right.left.equalTo(view.super.safeArea); make.height.equalTo(50); } }]});运行,就会看见页面上出现了刚刚编写的组件。

改变上面代码中的index和tabs的值,重启脚本,组件也会对应发生变化。
优化组件(交互部分)
下面让我们来实现组件的交互,即点击 tab 会切换,并且可以通过外部代码控制此组件。
组件内部状态管理
可以看到,该组件的所有变化都是通过index 定义的,只要index改变,组件的显示就应该跟着改变。因此index属性表示了该组件的内部状态。
当用户点击 tab,或是外部代码控制组件切换 tab,实质上应改变组件内部的index属性值,组件应该监听index的变化从而在页面上做出变化。
开始实现
给matrix添加一个点击事件。触发时应将index变为被点击的 tab 的index 。
events: { didSelect(_, indexPath) { this.props.index = indexPath.item; }}编写changeTab事件,设置index为传入的值。
methods: { changeTab(tabIndex) { this.props.index = tabIndex; }}再添加一个watch 属性,监听index的变化。当属性发生变化时会触发对应的函数,传入新值和旧值。
通过this.view** 拿到该组件在页面上的实例 **,将新的index对应的 tab 的颜色变为tabSelectedColor 的颜色值,将旧的 tab 颜色变为tabColor 的值。
接着再通知onTabChanged 事件,告诉它当前选中 tab 已更改。
watch: { index(newIndex, oldIndex) { const data = this.view.data; data[newIndex].title.textColor = this.props.tabSelectedColor; data[newIndex].icon.icon = $icon( this.props.tabs[newIndex].icon, this.props.tabSelectedColor, $size(50, 50) ); data[oldIndex].title.textColor = this.props.tabColor; data[oldIndex].icon.icon = $icon( this.props.tabs[oldIndex].icon, this.props.tabColor, $size(50, 50) ); this.view.data = data; this.events.onTabChanged(newIndex, this.props.tabs[newIndex]); }}至此整个组件的编写工作就结束了。
现在再运行一遍项目,发现点击 tab 会切换了。
通过外部代码修改选中 tab
现在我们来实现一个页面创建 3 秒后使第二个 tab 被选中的效果。
要实现此效果就是要改变组件的内部状态,即index属性。
首先给组件加上属性id叫"tablayout1",然后导入get函数。
get 函数的用法
get函数的用法和$("id")一致,都是通过 id 获取组件在页面上的实例,但此函数可以识别自定义组件。
获取实例后可以直接修改属性值,也可以调用组件methods里的方法。
const { render, get } = require("./lib/component");
setTimeout(() => { // 直接修改属性值 get("tablayout1").index = 1;
// 调用方法实现 // get("tablayout1").changeTab(1);}, 3000);Q&A
哪里可以获取this对象?
-
methods属性内 -
watch属性内 -
render方法内 -
render方法返回的控件对象的events内
this对象包括哪些属性?
props, events, methods, view
如何手动转换一个组件
使用trans函数
const { trans } = require("./lib/component");
const TabLayout = require("./components/TabLayout");
$ui.render({ views: [ trans({ type: TabLayout, props: { index: 1, tabs: [ { title: "Home", icon: "102" } ] }, layout(make, view) { make.bottom.right.left.equalTo(view.super.safeArea); make.height.equalTo(50); } }) ]});注意事项
-
render方法的返回结果必须是一个控件对象,如果组件是由多个控件组成的,请包裹至一个 view 控件中 -
views属性的值会自动加到根控件的views后面
结语
至此,你已经对jsbox-component有了一个全面的了解,其他的一些特性和用法可以到 components 文件夹下查看示例组件。
上一次使用 JSBox 这个软件还是在三年前,三年前我为 JSBox 写过一个名为 AndStyle 的库,也是可以使用各种组件。但由于当时能力有限,对前端不甚了解,因此存在诸多不完善的地方。这回终于把从前一直想实现但无从下手的功能开发出来了,也算了结一个遗憾。
对的,我就是那个在 18-20 年左右写了一堆脚本的人,哈哈哈,以前我的 id 还叫 Hhhd,不知道有没有人记得我🤣
当时我天天在 JSBox 的 tg 群水群,还认识了不少大佬,比如 Ryan(记得是一个北理大佬,很 nb)、Neurogram(狗哥)(英语很好的一个北外佬)、Fndroid(后来我才知道这位原来是 cfw 作者😲)
本教程的全部代码在 components/TabLayout.js
Comments