Sciter.js 指南 - GUI 开发之 Reactor 框架

前面很多地方的文章,其实提到了这个东西,本教程会系统一点来帮助从未接触过 GUI 开发、或者熟悉 JavaScript 但未使用过 Reactor 的开发者快速上手,所以细节看文档,这会全面一些介绍大体上开发过程中用得到的知识。

什么是 Reactor?

简单来说,Sciter.js Reactor 不是一个框架或库,而是 Sciter.js 内置的一组功能,它借鉴了 ReactJS 的核心思想,让你能够用声明式的方式构建用户界面。

它主要包含两个核心特性:

  1. 原生 JSX 支持: 你可以直接在 JavaScript 代码中编写类似 HTML 的 JSX 语法,无需任何额外的编译步骤。
  2. 高效的 DOM 更新: 通过 element.patch(jsx) 方法,Reactor 能够智能地比较虚拟 DOM (JSX 表达式的结果) 和真实 DOM 的差异,并只更新必要的部分,从而实现高效的 UI 渲染。

为什么选择 Reactor?

  • 原生集成: 无需引入外部库,开箱即用。
  • 简洁高效: 语法简洁,性能优异,尤其适合桌面应用开发。
  • ReactJS 相似性: 如果你熟悉 ReactJS,你会发现很多概念是相通的,上手会更快。
  • 桌面 UI 优化: 针对桌面 UI 的特点进行了优化,例如支持窗口化弹出元素等。

核心概念

1. JSX (JavaScript XML)

JSX 允许你在 JavaScript 代码中编写 UI 结构,就像写 HTML 一样。

javascript 复制代码
// 一个简单的 JSX 表达式
const greeting = <h1>你好, Sciter!</h1>;

document.body.append(greeting); // 将 JSX 渲染到页面

关键点:

  • JSX 标签可以是标准的 HTML 标签 (如 <div>, <span>),也可以是自定义组件。
  • 可以在 JSX 中使用 {} 嵌入 JavaScript 表达式。
  • 组件名称必须以大写字母开头。

2. 组件 (Components)

组件是 Reactor 应用的基本构建块。它们是可重用的 UI 单元,可以接收输入数据 (称为 props) 并返回要在屏幕上显示的虚拟元素。

函数组件 (Function Components): 最简单的方式是使用函数定义组件。

javascript 复制代码
// 一个简单的函数组件
function Welcome(props) {
  return <h1>你好, {props.name}!</h1>;
}

// 使用组件
document.body.patch(<Welcome name="开发者" />);

类组件 (Class Components): 你也可以使用 ES6 类来定义组件。类组件通常用于需要管理自身状态或使用生命周期方法的场景。

javascript 复制代码
class Clock extends Element {
  // 构造函数,初始化状态
  constructor() {
    super();
    this.time = new Date();
  }

  // 生命周期方法:组件挂载到 DOM 后调用
  componentDidMount() {
    this.timerId = setInterval(() => {
      // 更新组件状态
      this.componentUpdate({ time: new Date() });
    }, 1000);
  }

  // 生命周期方法:组件从 DOM 卸载前调用
  componentWillUnmount() {
    clearInterval(this.timerId);
  }

  // 渲染方法:返回要显示的 JSX
  render() {
    return <p>当前时间: {this.time.toLocaleTimeString()}</p>;
  }
}

// 使用类组件
document.body.patch(<Clock />);

关键点:

  • 组件可以将 UI 拆分成独立、可复用的部分。
  • props 是从父组件传递给子组件的数据,是只读的。
  • 类组件通过 this 访问 props 和自身状态。

3. 渲染 (Rendering) 与 element.patch()

Reactor 使用 element.patch(jsx) 方法将 JSX 描述的 UI 更新到真实的 DOM 上。它会进行高效的 diff 算法,只更新变化的部分。

javascript 复制代码
function tick() {
  const element = (
    <div>
      <h1>你好, 世界!</h1>
      <h2>现在是 {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  document.body.patch(element); // 高效更新 DOM
}

setInterval(tick, 1000);

4. 状态 (State) 与 componentUpdate()

对于类组件,可以使用 this 来存储组件的内部状态。当状态需要改变时,必须 调用 this.componentUpdate(newState) 方法来通知 Reactor 更新 UI。

javascript 复制代码
class Counter extends Element {
  count = 0; // 初始化状态

  increment() {
    // 使用 componentUpdate 更新状态
    this.componentUpdate({ count: this.count + 1 });
  }

  render() {
    return (
      <div>
        <p>点击次数: {this.count}</p>
        <button onclick={() => this.increment()}>点击我</button>
      </div>
    );
  }
}

document.body.patch(<Counter />);

关键点:

  • 不要直接修改 this 上的状态属性 (除了在 constructor 中初始化)。
  • componentUpdate() 会触发 render() 方法重新渲染。
  • componentUpdate() 是异步的,并且会将多次更新合并为一次。

5. 生命周期 (Lifecycle)

类组件有一系列特殊的"生命周期方法",允许你在组件的不同阶段执行代码:

  • constructor(props, kids): 组件实例创建时调用,用于初始化状态和绑定方法。
  • this(props, kids): 组件接收新的 props 或 kids 时调用。
  • render(): 必须的方法,返回要渲染的 JSX。当状态或 props 改变时会被调用。
  • componentDidMount(): 组件首次挂载到真实 DOM 后调用。适合执行需要 DOM 节点的操作,如网络请求、设置定时器等。
  • componentWillUnmount(): 组件从 DOM 中卸载前调用。适合执行清理操作,如清除定时器、取消网络请求等。
  • componentDidUpdate(): 在 componentUpdate() 触发的更新完成后调用。

参考示例:上面的 Clock 组件就使用了 componentDidMountcomponentWillUnmount

6. 事件处理 (Event Handling)

在 Reactor 中处理事件非常直观。可以直接在 JSX 中使用 on<EventName> 属性绑定事件处理器。

javascript 复制代码
function ActionButton() {
  function handleClick(event) {
    console.log('按钮被点击了!', event);
  }

  return <button onclick={handleClick}>点我</button>;
}

对于类组件,通常在类中定义事件处理方法:

javascript 复制代码
class Search extends Element {

  handleSearchClick() {
    const query = this.$('input').value; // 获取输入框的值
    console.log('搜索:', query);
    // 可以触发一个自定义事件,通知父组件
    this.post(new Event('do-search', { data: query }));
  }

  render() {
    return (
      <search>
        <input|text placeholder="输入搜索内容" />
        <button onclick={() => this.handleSearchClick()}>搜索</button>
      </search>
    );
  }
}

Sciter 还支持更高效的类事件处理器语法 (详见 component-styles-events.md), 我个人是非常非常喜欢这种写法的,所见所得,不用自己到处去绑定,好象是 ES2020 的新语法:

javascript 复制代码
class Search extends Element {
  // ... render 方法 ...

  // 使用特殊语法处理按钮点击
  ["on click at button"](evt, button) {
    const query = this.$('input').value;
    console.log('搜索:', query);
    this.post(new Event('do-search', { data: query }));
  }
}

7. 样式 (Styling)

你可以像在普通 HTML 中一样使用 CSS 来为组件添加样式。

全局样式: 在 HTML 文件中通过 <style> 标签或链接外部 CSS 文件。

组件级样式 (styleset): 为了更好地封装组件样式,避免全局污染,Reactor 推荐使用 styleset 属性。

  • 外部 CSS 文件:

    javascript 复制代码
    // component.js
    class MyComponent extends Element {
      render() {
        // 使用 __DIR__ 获取当前脚本文件所在目录
        return <div styleset={__DIR__ + "styles.css#my-component"}>内容</div>;
      }
    }
    css 复制代码
    /* styles.css */
    @set my-component {
      :root { /* :root 指向组件根元素 */
        display: block;
        border: 1px solid red;
        padding: 10px;
      }
      :root p {
        color: blue;
      }
    }
  • 内联 CSS (使用 CSS.set):

    javascript 复制代码
    // component.js
    const myComponentStyles = CSS.set`
      :root {
        display: block;
        border: 1px solid green;
        padding: 8px;
      }
    `;
    
    class MyComponent extends Element {
      render() {
        return <div styleset={myComponentStyles}>内联样式内容</div>;
      }
    }

开始第一个 Reactor 应用

让我们创建一个简单的"计数器"应用。

  1. 创建 HTML 文件 (counter.htm):

    html 复制代码
    <html>
    <head>
        <title>Reactor 计数器</title>
        <style>
          body { padding: 20px; font-family: sans-serif; }
          button { margin-left: 10px; padding: 5px 10px; }
        </style>
        <script type="module">
          // 导入或定义 Counter 组件 (见下一步)
          import { Counter } from './counter.js';
    
          // 将 Counter 组件渲染到 body
          document.body.patch(<Counter initialCount={0} />);
        </script>
    </head>
    <body>
      <!-- 组件将渲染在这里 -->
    </body>
    </html>
  2. 创建 JavaScript 文件 (counter.js):

    javascript 复制代码
    export class Counter extends Element {
      // 从 props 获取初始值
      constructor(props) {
        super();
        this.count = props.initialCount || 0;
      }
    
      increment() {
        this.componentUpdate({ count: this.count + 1 });
      }
    
      decrement() {
        this.componentUpdate({ count: this.count - 1 });
      }
    
      render() {
        return (
          <div>
            <span>当前计数: {this.count}</span>
            <button onclick={() => this.increment()}>+</button>
            <button onclick={() => this.decrement()}>-</button>
          </div>
        );
      }
    }
  3. 运行: 使用 Sciter 加载 counter.htm 文件。

现在你应该能看到一个可以增加和减少计数的简单应用了!

最佳实践

  • 组件化: 将 UI 拆分成小的、可复用的组件。

  • 单一职责: 每个组件最好只做一件事。

  • Props 向下传递: 数据流应该是单向的,从父组件通过 props 传递给子组件。

  • 状态最小化: 只在必要时使用组件内部状态。尽可能将状态提升到共同的父组件中管理。

  • 使用 Keys: 当渲染列表时,为每个列表项提供一个唯一的 key 属性,帮助 Reactor 识别哪些项发生了变化。

    javascript 复制代码
    function ItemList(props) {
      const listItems = props.items.map((item) =>
        <li key={item.id}>{item.text}</li>
      );
      return <ul>{listItems}</ul>;
    }
  • 善用生命周期: 在合适的生命周期方法中执行副作用(如 API 调用、定时器设置)。

  • 组件命名: 组件名称(函数或类)始终使用大写字母开头。

  • 样式封装: 优先使用 styleset 来封装组件样式。

示例代码参考

Sciter SDK 提供了丰富的 Reactor 示例,强烈建议查看:

  • samples.reactor/basic/: 包含基础概念的示例,如时钟 (component-clock.htm)、组件更新 (component-update.htm)。
  • samples.reactor/form/: 表单处理示例。
  • samples.reactor/todo/: 一个经典的 TODO 应用示例。
  • samples.reactor/styling/: 不同的样式定义方式。
  • samples.reactor/tabs/: Tabs 组件示例。

结语

本教程介绍了 Sciter.js Reactor 的基础知识。Reactor 提供了一种现代、高效的方式来构建桌面应用程序的用户界面。通过实践和参考 SDK 中的示例,你将能够更快地掌握它,但不要忘记原生的文档哦。可以直接去 docs.sciter.com 网站上看。

下一步的建议:

  • 深入阅读官方文档 docs/md/Reactor/ 目录下的其他文件。
  • 动手尝试修改和运行 samples.reactor/ 目录下的示例。
  • 开始构建你自己的 Reactor 组件!
相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端