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 组件!
相关推荐
乌夷6 分钟前
axios结合AbortController取消文件上传
开发语言·前端·javascript
晓晓莺歌29 分钟前
图片的require问题
前端
码农黛兮_461 小时前
CSS3 基础知识、原理及与CSS的区别
前端·css·css3
水银嘻嘻1 小时前
web 自动化之 Unittest 四大组件
运维·前端·自动化
(((φ(◎ロ◎;)φ)))牵丝戏安1 小时前
根据输入的数据渲染柱形图
前端·css·css3·js
wuyijysx2 小时前
JavaScript grammar
前端·javascript
溪饱鱼2 小时前
第6章: SEO与交互指标
服务器·前端·microsoft
咔_2 小时前
LinkedList详解(源码分析)
前端
逍遥德3 小时前
CSS可以继承的样式汇总
前端·css·ui
读心悦3 小时前
CSS3 选择器完全指南:从基础到高级的元素定位技术
前端·css·css3