Sciter.js 指南-核心概念:GUI应用程序项目结构、视图切换与组件化

当在 GUI 界面的时候,通常是左右布局, 或上下布局。当点击左边的内容的时候,右边是怎么样变化的?这可能是写 GUI 刚学的人的一个难题。 如果没写过前端的同学,更加会难以理解。另外,就右边内容中是怎么更新整个桌面 GUI 的这个显示,也是本文介绍的重点。

本文档旨在帮助初学者,特别是拥有前端或其他编程语言背景的开发者, 因为这个和传统浏览器开发差别比较大,快速了解如何使用 Sciter.js 构建桌面 GUI 应用程序的结构。整体 GUI 界面怎么样实现切换。单独的一个布局怎么样更新显示。

1. 项目基本结构

一个典型的 Sciter.js 项目通常包含以下部分:

  • HTML 文件 (.htm): 定义应用程序窗口的基本结构和布局。通常有一个主 HTML 文件作为入口点。
  • CSS 文件 (.css): 负责应用程序的样式和外观。
  • JavaScript 文件 (.js): 实现应用程序的逻辑、交互和组件。

示例 (samples.app/classic/main.htm):

html 复制代码
<html window-resizable>
    <head>
        <title>Test</title>
        <style src="main.css" />
        <script type="module">
          import {Application} from "application.js";
          // 启动应用
          document.body.patch(<Application/>);
        </script>
    </head>
    <body></body>
</html>
  • <style src="main.css" />: 引入 CSS 文件。
  • <script type="module">: 引入 JavaScript 模块。现代 Sciter.js 推荐使用 ES6 模块。
  • document.body.patch(<Application/>): 这是 Reactor 的核心用法,将 <Application/> 组件渲染到 <body> 元素中。

2. 路由(整体应用GUI层面)与视图(组件层面)切换

路由可以理解成整体应用GUI层面的切换, Sciter.js 没有内置像 React Router 那样的标准路由库,但可以通过不同的方式实现视图切换,其实也是组件,只是这为了区别,我们叫了路由。

局部的界面修改,就是局部的组件内部状态更新来切换不同的显示。

2.1. 界面内基于 href 的路由 (示例: samples.reactor/routing)

这种方法通过定义特殊的 href 属性(如 route:routeName)和中心化的路由逻辑来实现。我们要先创建几个对应的组件的 js, 如例子中的 views/initial.js, views/quick.js, views/special.js:

然后在 main.htm 中引入这些组件, 并定义路由逻辑:

main.htm:

javascript 复制代码
import {Initial} from "views/initial.js";
import {Quick} from "views/quick.js";
import {Special} from "views/special.js";

// 定义路由名称到视图组件的映射
const routes = { 
  "initial" : <Initial/>,
  "quick" : <Quick/>,
  "special" : <Special/>,
}; 

class App extends Element {
  routeName = "initial";
  routeView = routes["initial"];

  render() {
    return <body>
      <nav>
        {/* 导航链接 */}
        <a href="route:initial">Home</a>
        <a href="route:quick">Quick</a>
        <a href="route:special">Special</a>
      </nav>
      {/* 显示当前路由对应的视图, 如上面的几个 initial quick  special*/}
      {this.routeView}
    </body>;
  }

  // 导航逻辑
  // 当点击导航链接时, 调用这个方法, 会改变让主应用程序组件的状态, 从而切换显示的视图
  // 如点击 quick 会切换到 quick 视图, 点击 special 会切换到 special 视图
  navigateTo(routeName) {
    let routeView = routes[routeName];
    if(routeView) {
      this.componentUpdate({ routeView, routeName });
      return true;
    }
  }

  // 监听导航链接点击事件
  ["on click at [href^='route:']"](event, hyperlink) {
    const routeName = hyperlink.attributes["href"].substr(6);
    return this.navigateTo(routeName);
  }
}

document.body.patch(<App/>);
  • routes 对象映射路由名称和对应的 JSX 组件。
  • App 组件维护当前路由名称 (routeName) 和视图 (routeView)。
  • navigateTo 方法根据路由名称更新 App 组件的状态,从而切换显示的视图。
  • 通过监听 a[href^='route:'] 的点击事件来触发导航。
  • 各个视图组件(如 Initial, Quick)可以包含自己的导航按钮 (<button href="route:...">)。

2.2. 组件内基于状态的条件渲染 (示例: samples.app/classic)

这种方法不显式使用"路由"概念,而是通过改变主应用程序组件的状态来控制显示哪些子视图。这适用于固定布局、部分区域内容切换的场景。

javascript 复制代码
class Application extends Element {
   navigationViewShown = true;
   propertiesViewShown = true;
   
   render() {
      return <body>
         {/* ... 固定组件 ... */}
         <main>         
            {/* 根据状态显示/隐藏视图 */}
            { this.navigationViewShown && <NavigationView /> }
            <ContentView />
            { this.propertiesViewShown && <PropertiesView /> }
         </main>
         {/* ... 固定组件 ... */}
      </body>;
   }

   // 通过事件处理改变状态
   ["on click at .view-toggle[name=navigation]"]() {
      this.componentUpdate({navigationViewShown: !this.navigationViewShown});
   }
   // ...
}
  • Application 组件维护 navigationViewShown 等状态。
  • render 方法中使用条件渲染 (&&) 来决定是否挂载 NavigationViewPropertiesView
  • 点击工具栏或菜单按钮时,调用 componentUpdate 改变状态,从而实现视图的显示/隐藏切换。
  • 这种方式下,通常布局是固定的(如 MenuBar, ToolBar, StatusBar, ContentView 始终存在),只有部分区域(NavigationView, PropertiesView)根据状态变化。

4. 组件化开发 (Reactor)

上面提到的组件,可能很多人还不理解是什么东西,这有一个简单教学。Sciter.js 的 Reactor 支持类似 React 的组件化开发模式,主要有两种组件类型:

4.1. Class Component (类组件)

类组件继承自 Element,并实现 render() 方法来返回组件的 JSX 结构。它们可以拥有自己的状态 (state) 和生命周期方法。

示例 (samples.app/classic/application.js):

javascript 复制代码
import {MenuBar} from "menu/menu-bar.js";
// ... 其他导入 ...

export class Application extends Element {

   // 应用状态
   navigationViewShown = true;
   propertiesViewShown = true;
   
   render() {
      // 使用 JSX 定义界面结构
      return <body>
         <MenuBar app={this} />
         {/* ... 其他组件 ... */}
         <main>         
            {/* 条件渲染:根据状态决定是否显示导航视图 */}
            { this.navigationViewShown && <NavigationView app={this} /> }
            <ContentView app={this}/>
            { this.propertiesViewShown && <PropertiesView app={this} /> }
         </main>
         {/* ... 其他组件 ... */}
      </body>;
   }

   // 事件处理,更新状态
   ["on click at .view-toggle[name=navigation]"]() {
      // 使用 componentUpdate 更新状态并触发重新渲染
      this.componentUpdate({navigationViewShown: !this.navigationViewShown});
   }
   // ... 其他事件处理 ...
}
  • render(): 返回组件的 UI 结构。
  • this.componentUpdate({...}): 用于更新组件状态并触发界面重新渲染。
  • 条件渲染: 使用 && 操作符根据状态 (navigationViewShown) 动态显示或隐藏子组件。

4.2. Function Component (函数组件)

函数组件是简单的 JavaScript 函数,接收 propskids (子元素) 作为参数,并返回 JSX 结构。它们通常用于展示型组件。

示例 (samples.reactor/window/trayicon-test.htm):

javascript 复制代码
// 生成弹出菜单窗口内容的函数组件
function PopuMenuWindow(props, kids) {

  function onWindowActivate(evt) {
    if(!evt.reason)  
      this.close(); // 窗口失去焦点时关闭
  }

  return <html window-frame="solid-with-shadow"
               window-width="max-content"
               window-height="max-content"
               window-onactivate={onWindowActivate}>
          <style src={__DIR__ + "menu-window-style.css"} />
          <body>
            <menu.popup.visible>
              <li onClick={revealWindow}>Show window</li>
              {/* ... 其他菜单项 ... */}
            </menu.popup.visible>
          </body>
        </html>;
}

// 在事件处理中使用函数组件创建新窗口
const popup = new Window({
   type: Window.POPUP_WINDOW,
   html: <PopuMenuWindow />, // 将函数组件的 JSX 作为窗口内容
   x: screenX, 
   y: screenY,
   // ... 其他选项 ...
});

4.3 状态管理与界面更新

在 Reactor (类组件) 中,界面更新通常由状态变化驱动。

  • 状态定义 : 在类组件的属性中定义状态变量。

    javascript 复制代码
    class Application extends Element {
       navigationViewShown = true; // 状态变量
       // ...
    }
  • 更新状态 : 使用 this.componentUpdate({ stateName: newValue }) 方法更新状态。这会自动触发 render() 方法,重新计算 UI。

    javascript 复制代码
    this.componentUpdate({navigationViewShown: !this.navigationViewShown});
  • 条件渲染 : 在 render() 方法中,根据状态值决定渲染哪些元素或组件。

    jsx 复制代码
    { this.navigationViewShown && <NavigationView /> }

4.4 组件内事件处理

Sciter.js 提供多种事件处理方式:

  • 全局事件监听 (document.on) : 监听整个文档上的事件,通常使用 CSS 选择器来指定目标元素。

    javascript 复制代码
    // 监听 ID 为 'set' 的按钮点击事件
    document.on("click", "button#set", async function(evt, button) { 
      // ... 事件处理逻辑 ...
    });
  • 元素内联事件处理 (JSX) : 在 JSX 标签上直接绑定事件处理器。

    jsx 复制代码
    <button onClick={this.handleClick}>Click Me</button>
    <li onClick={revealWindow}>Show window</li> 
  • 特定元素/窗口事件 (Window.this.on) : 监听特定窗口或元素的事件,例如系统托盘图标点击。

    javascript 复制代码
    Window.this.on("trayiconclick", evt => {
      if(evt.data.buttons == 2) { // 右键点击
        // ... 处理逻辑 ...
      }
    });
  • 类组件中的特定选择器事件 : 在类组件内部,可以使用类似 CSS 选择器的语法来绑定事件。

    javascript 复制代码
    class MyComponent extends Element {
      ["on click at .my-button"](event, element) {
        // 处理 .my-button 元素的点击事件
      }
      render() {
        return <button class="my-button">Click</button>;
      }
    }

7. 最佳实践

整个 GUI 应用的层面基于 routing 的方式来路由更新组件,内部使用组件的方式来更新内容。

路由的方式有两种:

  • 基于href 的路由(类似samples.reactor/routing ) : 这种方式更接近传统 Web 应用的页面路由,适用于应用包含多个相对独立的、需要整体切换的视图或"页面"的场景。它通常涉及一个顶层组件来管理当前路由状态,并根据href 链接(如route:routeName )来加载和显示不同的视图组件。这可以看作是应用层面的路由。
  • 基于状态的条件渲染(类似samples.app/classic ) : 这种方式更侧重于在同一个主布局内,根据组件的内部状态来动态显示或隐藏某些子组件或视图区域。例如,点击菜单或按钮改变一个状态变量,然后render 方法根据这个状态决定是否渲染某个面板。这可以理解为在组件内部通过状态控制局部内容的显示切换,虽然不完全是"路由",但实现了类似的效果,特别适用于固定布局下的局部视图更新。
  1. 组件化: 将 UI 拆分成小的、可复用的组件(类组件或函数组件)。
  2. 状态管理 :
    • 对于简单应用或局部状态,直接在类组件中使用 this.componentUpdate
    • 对于复杂应用,考虑将共享状态提升到共同的父组件中,并通过 props 向下传递。
  3. 路由选择 :
    • 对于多页面、独立视图切换的应用,使用基于 href 的路由方式(如 samples.reactor/routing 所示)更清晰。
    • 对于固定布局、局部内容更新的应用,使用基于状态的条件渲染(如 samples.app/classic 所示)更简单直接。
  4. 模块化 : 使用 ES6 模块 (import/export) 来组织代码。

希望这份文档能帮助你开始 Sciter.js 的 GUI 开发之旅!建议多参考 SDK 中的 samples.reactorsamples.sciter 目录下的示例代码。**

相关推荐
Bl_a_ck9 分钟前
开发环境(Development Environment)
开发语言·前端·javascript·typescript·ecmascript
田本初22 分钟前
使用vite重构vue-cli的vue3项目
前端·vue.js·重构
ai产品老杨30 分钟前
AI赋能安全生产,推进数智化转型的智慧油站开源了。
前端·javascript·vue.js·人工智能·ecmascript
帮帮志35 分钟前
vue实现与后台springboot传递数据【传值/取值 Axios 】
前端·vue.js·spring boot
xixingzhe21 小时前
Nginx 配置多个监听端口
服务器·前端·nginx
清风细雨_林木木2 小时前
Vue 2 项目中配置 Tailwind CSS 和 Font Awesome 的最佳实践
前端·css·vue.js
逊嘘2 小时前
【Web前端开发】CSS基础
前端·css
小宁爱Python2 小时前
深入掌握CSS Flex布局:从原理到实战
前端·javascript·css
Attacking-Coder2 小时前
前端面试宝典---webpack面试题
前端·面试·webpack
极小狐2 小时前
极狐GitLab 容器镜像仓库功能介绍
java·前端·数据库·npm·gitlab