/ 技术 / 227浏览

JSBox-Component教程——写一个TabLayout

什么是JSBox?

JSBox 是一个可以用来运行 JavaScript 脚本的 iOS 应用,你可以通过他来执行标准的 JavaScript 脚本。

这种执行不是指跑在浏览器上,而是执行在一个完全原生的环境,效率很高。

并且提供了很多 iOS 原生的接口,这意味着你可以通过他做很多事情,包括但不限于:

  • 写一个用来查询汇率的脚本;

  • 写一个用于计算小费的脚本;

  • 通过接口实现一个小小的应用,提供丰富的界面;

  • 写一个文本收藏工具,用于收藏常用的文字;

  • 写一个下载 Twitter 视频的小工具;

——引用自少数派:JSBox: 一个创造工具的工具

什么是JSBox-Component?

JSBox-Component是一个在JSBox中实现ui组件化的库,可以将页面拆分为不同的组件从而复用。

此教程将带你快速上手JSBox-Component这个库,提高开发效率😎

结果展示

IMG_0011.png

{
  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

  • 可以通过传入tabColortabSelectedColor属性指定tab未选中和选中时的颜色

  • 通过onTabChanged事件可以监听当前选中tab是否改变

  • 通过更改index属性可以改变当前选中的tab

创建项目

首先从Release中下载最新版本的component.js

PixPin_2024-02-16_17-32-25.webp

创建一个空白项目,新建一个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 ,每个格子有一个imagelabel控件,用于显示图标和文本,现在让我们把这个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转换为matrixdata属性

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);
    }
  }]
});

运行,就会看见页面上出现了刚刚编写的组件。

IMG_0011.png

改变上面代码中的indextabs的值,重启脚本,组件也会对应发生变化。

优化组件(交互部分)

下面让我们来实现组件的交互,即点击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);
      }
    })
  ]
});

注意事项

  1. render方法的返回结果必须是一个控件对象,如果组件是由多个控件组成的,请包裹至一个view控件中

  2. views 属性的值会自动加到根控件的views后面

结语

至此,你已经对jsbox-component有了一个全面的了解,其他的一些特性和用法可以到components文件夹下查看示例组件。

上一次使用JSBox这个软件还是在三年前,三年前我为JSBox写过一个名为AndStyle的库,也是可以使用各种组件。但由于当时能力有限,对前端不甚了解,因此存在诸多不完善的地方。这回终于把从前一直想实现但无从下手的功能开发出来了,也算了结一个遗憾。

本教程的全部代码在components/TabLayout.js