Skip to content

Jiess 组件系统 ​

目录 ​

什么是 Jiess 组件? ​

Jiess 组件是一种基于配置对象的跨框架组件实现方式。通过统一的 JavaScript 对象描述组件的结构、行为和样式,由 Jiess 运行时根据当前框架环境自动转换为对应框架(Vue 3 / React / Web Components)的组件实例。

核心优势:

  • 跨框架兼容:一套代码可在 Vue 3、React 和 Web Components 环境中无缝运行
  • 配置驱动:使用普通 JavaScript 对象配置,无需学习额外模板语法
  • 完整功能:支持生命周期、样式管理、组件通信等完整组件特性
  • 易于集成:可与现有框架生态系统无缝集成

设计原理 ​

Jiess 组件系统的核心设计思想是:通过统一的配置对象描述组件,再由 Jiess 运行时根据当前框架环境,将配置转换为对应框架的组件实例。一套代码,多框架运行。

核心概念 ​

  1. 组件配置对象:使用普通 JavaScript 对象描述组件的结构、行为和样式
  2. 跨框架适配层:负责将 Jiess 组件配置转换为具体框架的组件
  3. 组件上下文:提供组件运行时的各种工具方法和状态管理
  4. 生命周期钩子:与各框架生命周期对齐,确保组件行为一致性

跨框架兼容性 ​

Jiess 通过框架适配层在运行时检测当前环境并转换为对应框架的组件:

Vue 3 适配 ​

  • 将 Jiess 组件配置转换为 Vue 3 的函数式组件(h 函数)
  • 处理 Vue 特有的指令和生命周期,与 Vue 3 响应式系统无缝集成
  • 样式隔离:CSS 选择器级隔离(SASS 嵌套语法),isolated 和 global 样式统一注入文档级 style 标签

React 适配 ​

  • 将 Jiess 组件配置转换为 React 的函数组件
  • 处理 React 特有的 Hooks 和生命周期,与 React 状态管理系统兼容
  • 样式隔离:CSS 选择器级隔离(SASS 嵌套语法),isolated 和 global 样式统一注入文档级 style 标签

Web Components 适配 ​

将 Jiess 组件配置转换为 Web Components 标记对象:

  • 通过 component() 返回 { __jiessComponent: true, that, initCycle, frame } 标记对象
  • createElement() 直接返回真实 DOM 节点(document.createElement)
  • forceUpdate 通过清空容器 innerHTML + 重建 DOM 实现
  • Shadow DOM 隔离(仅 Web Component):使用原生 attachShadow({ mode: 'open' }) API 创建 Shadow Root,实现浏览器级样式硬隔离。不使用 customElements.define(即不使用 defineCustomElement),也不依赖任何第三方 Shadow DOM 库(如 react-shadow)。Vue3/React 使用 CSS 选择器级软隔离(SASS 嵌套语法),两种方案在隔离效果上等价。
  • 样式自动分流:isolated CSS 自动注入 Shadow Root 内部,global CSS(@keyframes/@font-face/:global)自动提升到文档级,用户使用 style()、className、:global 等 API 完全无感
  • 适用于无框架依赖的纯 Web Components 场景

适配流程 ​

  1. 检测当前运行环境(Vue 3、React 或 Web Components)
  2. 根据环境选择对应的适配策略
  3. 转换组件配置为框架特定格式
  4. 处理框架特有的生命周期和特性
  5. 返回适配后的组件实例

组件基本结构 ​

Jiess 组件采用简洁的对象声明方式。一个完整的 Jiess 组件基本结构如下:

javascript
const MyComponent = {
  isJiess: true,           // 标记为 Jiess 组件,必须设置为 true
  name: 'MyComponent',     // 组件名称,可选,便于调试和错误追踪
  setup(props) {           // 组件构造函数,核心逻辑在这里实现(必须用普通函数)
    // 组件逻辑
  },
  props () {               // 入参校验配置,可选
    return {
      // 在这里定义组件允许接收的属性规则
    };
  },
  style() {                // 组件样式配置,可选
    return [
      // 样式配置
    ];
  }
};

// 使用组件
function setup () {
  this.add({ is: MyComponent });
}

配置项说明 ​

属性类型必填描述
isJiessboolean是标记该对象为 Jiess 组件,必须设置为 true
namestring否组件名称,用于调试和错误追踪
setupfunction是组件构造函数,定义组件逻辑和结构,必须使用普通函数以确保 this 上下文正确
propsfunction否入参校验规则,返回属性配置对象
stylestring / object / function否组件样式配置

最小化组件示例 ​

最简单的 Jiess 组件只需两个属性:isJiess 和 setup:

javascript
const MinimalComponent = {
  isJiess: true,
  setup(props) {
    this.add('Hello, Jiess!');
  }
};

function setup() {
  this.add({ is: MinimalComponent });
}

完整组件示例 ​

javascript
const CompleteComponent = {
  isJiess: true,
  name: 'CompleteComponent',

  props() {
    return {
      title: { type: String, required: true },
      count: { type: Number, default: 0 },
      onUpdate: { type: Function }
    };
  },

  setup(props) {
    const { title, count, onUpdate } = props;
    this.add({
      is: 'div',
      className: 'complete-component',
      children: [
        { is: 'h2', children: title },
        { is: 'p', children: `Count: ${count}` },
        {
          is: 'button', children: 'Increment',
          onClick: () => { if (onUpdate) onUpdate(count + 1); }
        }
      ]
    });
  },

  style: `
    .complete-component { padding: 16px; border: 1px solid #e0e0e0; border-radius: 8px; }
    .complete-component h2 { margin-top: 0; color: #333; }
    .complete-component button {
      padding: 8px 16px; background-color: #409eff; color: white;
      border: none; border-radius: 4px; cursor: pointer;
    }
  `
};

组件上下文 this ​

setup 函数中的 this 提供了组件运行时所需的全部工具方法。

核心方法 ​

方法描述示例
this.add()添加待渲染的元素或组件节点this.add('Hello') 或 this.add({ is: 'div', children: 'Hello' })
this.expose()暴露组件数据和方法给父组件this.expose({ count, increment })
this.provide()向后代组件注入数据this.provide({ appName: 'Jiess' })
this.inject()获取祖先组件注入的数据const appName = this.inject('appName')
this.forceUpdate()强制更新组件this.forceUpdate()
this.nextTick()下一次 DOM 更新完成后执行回调this.nextTick(() => { /* DOM 已就绪 */ })

组件传参 ​

Jiess 组件的参数传递不区分属性和事件,所有参数都作为普通属性传入。

普通参数 ​

javascript
// 父组件中使用组件
this.add({ is: MyComponent, message: 'Hello, Jiess!', count: 100 });

// 组件内部获取参数
const MyComponent = {
  isJiess: true,
  setup(props) {
    const { message, count } = props;
    this.add(`收到消息:${message},数量:${count}`);
  }
};

事件传递 ​

事件也作为普通属性传递,组件内部可直接调用:

javascript
// 父组件中传递事件
this.add({ is: ButtonComponent, onClick: () => console.log('按钮被点击了') });

// 组件内部调用事件
const ButtonComponent = {
  isJiess: true,
  setup(props) {
    const { onClick } = props;
    this.add({
      is: 'button', children: '点击我',
      onClick: (e) => {
        console.log('组件内部处理点击事件');
        if (onClick) onClick(e);
      }
    });
  }
};

内置参数 ​

内置参数以 $ 为前缀,具有特殊含义,Jiess 会对其进行特殊处理。这些参数不会被注入组件的 props,而是在外部处理:

  • $if:条件渲染,控制组件是否显示
  • $ref:获取组件引用,用于访问组件暴露的数据和方法
  • $slots:传递插槽内容,用于组件内容分发

参数传递最佳实践 ​

javascript
const BestPracticeComponent = {
  isJiess: true,
  name: 'BestPracticeComponent',

  props() {
    return {
      title: { type: String, required: true },
      config: { type: Object, default: () => ({ size: 'medium' }) },
      onSubmit: { type: Function }
    };
  },

  setup(props) {
    const { title, config, onSubmit } = props;
    this.add({
      is: 'div',
      children: [
        { is: 'h3', children: title },
        {
          is: 'button', children: 'Submit',
          onClick: () => { if (onSubmit) onSubmit({ title, ...config }); }
        }
      ]
    });
  }
};

function setup() {
  this.add({
    is: BestPracticeComponent,
    title: 'Form Title',
    config: { size: 'large', theme: 'dark' },
    onSubmit: (data) => console.log('Submit data:', data)
  });
}

实践要点:使用解构赋值提高可读性;事件命名使用 on 前缀(如 onClick、onChange);为可选参数提供默认值;参数过多时考虑使用对象形式传递。

组件入参校验 ​

props 是一个可选的函数,用于定义组件的入参规则,提高组件的健壮性和可维护性。

支持的 6 种类型 ​

类型检测方式示例值
Stringtypeof === 'string''hello'
Numbertypeof === 'number'42
Booleantypeof === 'boolean'true
Objecttypeof === 'object'{ key: 'val' }
ArrayArray.isArray()[1, 2, 3]
Functiontypeof === 'function'() => {}

校验选项 ​

javascript
const FormInput = {
  isJiess: true,
  name: 'FormInput',
  props() {
    return {
      label: { type: String, required: true },
      value: { type: [String, Number] },
      disabled: { type: Boolean, default: false },
      onChange: { type: Function },
      config: { type: Object, default: () => ({ size: 'medium' }) },
      options: { type: Array, default: () => [] },
      // 自定义校验
      age: {
        type: Number,
        required: true,
        validator: (value) => value >= 0 && value <= 150
      },
      email: {
        type: String,
        validator: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
      }
    };
  },
  setup(props) {
    const { label, value, disabled, onChange } = props;
    this.add({
      is: 'div', className: 'form-input',
      children: [
        { is: 'label', children: label },
        {
          is: 'input', type: 'text',
          value: value || '', disabled: disabled,
          onInput: (e) => { if (onChange) onChange(e.target.value); }
        }
      ]
    });
  }
};

TypeScript 类型支持 ​

typescript
interface FormInputProps {
  label: string;
  value?: string | number;
  disabled?: boolean;
  onChange?: (value: string | number) => void;
  config?: { size?: 'small' | 'medium' | 'large'; theme?: 'light' | 'dark'; };
  options?: Array<{ label: string; value: string | number; }>;
}

const FormInput = {
  isJiess: true,
  name: 'FormInput',
  props() {
    return {
      label: { type: String, required: true },
      value: { type: [String, Number] },
      disabled: { type: Boolean, default: false },
      onChange: { type: Function },
      config: { type: Object, default: () => ({ size: 'medium' }) },
      options: { type: Array, default: () => [] }
    };
  },
  setup(props: FormInputProps) {
    const { label, value, disabled, onChange } = props;
    this.add({
      is: 'div', className: 'form-input',
      children: [
        { is: 'label', children: label },
        {
          is: 'input', type: 'text',
          value: value || '', disabled: disabled,
          onInput: (e) => { if (onChange) onChange((e.target as HTMLInputElement).value); }
        }
      ]
    });
  }
};

校验行为说明 ​

  • 缺失必填属性:控制台输出错误并阻止组件渲染
  • 类型不匹配:输出警告,使用默认值或 undefined
  • validator 失败:输出自定义错误信息,使用默认值
  • Object/Array 默认值:必须用工厂函数 () => [] 避免多实例共享同一引用

组件数据暴露 ​

使用 expose 方法将组件内部数据和方法暴露给父组件,实现组件间通信。

基本用法 ​

javascript
const CounterComponent = {
  isJiess: true,
  name: 'CounterComponent',
  setup(props) {
    const count = $ref(0);
    const increment = () => { count.value++; };
    const decrement = () => { count.value--; };
    const reset = () => { count.value = 0; };

    this.add({
      is: 'div', className: 'counter',
      children: [
        { is: 'div', className: 'count', children: { $proxy: count } },
        {
          is: 'div', className: 'buttons',
          children: [
            { is: 'button', children: '-', onClick: decrement },
            { is: 'button', children: '+', onClick: increment }
          ]
        }
      ]
    });

    this.expose({ count, increment, decrement, reset });
  }
};

获取组件引用 ​

通过 $ref 内置参数获取组件引用,访问暴露的数据和方法:

javascript
import { $ref } from '@jiess/core';

function setup() {
  const componentRef = $ref();

  this.add({ is: CounterComponent, $ref: componentRef });

  this.add({
    is: 'button', children: '调用组件方法',
    onClick: () => {
      if (componentRef.value) {
        console.log('当前计数:', componentRef.value.count.value);
        componentRef.value.increment();
      }
    }
  });
}

最佳实践 ​

  1. 只暴露必要的数据和方法:避免暴露过多内部实现细节
  2. 使用响应式数据:暴露的数据应该是响应式的,以便父组件感知变化
  3. 明确的 API 设计:为暴露的方法和属性提供清晰的命名
  4. 避免循环依赖:确保父组件和子组件之间没有循环依赖

组件的模块化管理 ​

Jiess 组件就是普通的 JavaScript 对象,无需特殊的注册机制——通过标准的 ES 模块 export/import 即可实现组件复用。

javascript
// components/GlobalButton.js
// AI-generated-2026-05-18 00:00:00 由AI生成
export const GlobalButton = {
  isJiess: true,
  name: 'GlobalButton',
  props() {
    return {
      type: { type: String, default: 'primary' },
      children: { type: String, required: true },
      onClick: { type: Function }
    };
  },
  setup(props) {
    const { type, children, onClick } = props;
    this.add({
      is: 'button',
      className: `global-button global-button-${type}`,
      children: children,
      onClick: onClick
    });
  },
  style: `
    .global-button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }
    .global-button-primary { background-color: #409eff; color: white; }
    .global-button-success { background-color: #67c23a; color: white; }
    .global-button-danger { background-color: #f56c6c; color: white; }
  `
};
javascript
// 使用组件
// AI-generated-2026-05-18 00:00:00 由AI生成
import { GlobalButton } from '@/components/GlobalButton';

function setup() {
  this.add({
    is: GlobalButton,
    type: 'success',
    children: '模块化按钮',
    onClick: () => console.log('按钮被点击了!')
  });
}

最佳实践 ​

  1. 模块化组织:将可复用的组件放在 components/ 目录下,按功能分文件夹管理
  2. 避免循环依赖:组件之间通过 props 和 events 通信,避免相互 import
  3. 使用 PascalCase:组件文件名使用 PascalCase(如 GlobalButton.js),与组件命名一致

组件生命周期 ​

Jiess 提供了 5 个与框架生命周期对应的钩子函数:

钩子函数触发时机典型用途
onBeforeMount组件挂载到 DOM 前初始化数据、准备资源
onMounted组件挂载到 DOM 后DOM 操作、网络请求、启动定时器
onBeforeUpdate组件更新前获取更新前状态、保存快照
onUpdated组件更新后DOM 操作、第三方库更新
onBeforeUnmount组件从 DOM 卸载前清理定时器、取消请求、移除事件监听

执行顺序 ​

创建 → onBeforeMount → 挂载 → onMounted
数据变化 → onBeforeUpdate → 重新渲染 → onUpdated
卸载 → onBeforeUnmount

最佳实践示例 ​

javascript
const LifecycleBestPracticeComponent = {
  isJiess: true,
  name: 'LifecycleBestPracticeComponent',
  setup(props) {
    let timer = null;
    let eventListener = null;

    this.add('生命周期最佳实践示例');

    // 挂载前:初始化数据
    this.onBeforeMount(() => {
      console.log('组件即将挂载,初始化数据');
    });

    // 挂载后:DOM 操作、网络请求、事件监听
    this.onMounted(() => {
      console.log('组件已挂载,执行 DOM 操作');
      eventListener = () => console.log('点击事件');
      window.addEventListener('click', eventListener);
      timer = setInterval(() => console.log('定时器执行'), 1000);
      fetch('https://api.example.com/data')
        .then(response => response.json())
        .then(data => console.log('获取数据成功:', data));
    });

    // 更新前:获取更新前状态
    this.onBeforeUpdate(() => {
      console.log('组件即将更新');
    });

    // 更新后:DOM 操作
    this.onUpdated(() => {
      console.log('组件已更新,执行 DOM 操作');
    });

    // 卸载前:清理资源
    this.onBeforeUnmount(() => {
      console.log('组件即将卸载,清理资源');
      if (timer) { clearInterval(timer); timer = null; }
      if (eventListener) {
        window.removeEventListener('click', eventListener);
        eventListener = null;
      }
    });
  }
};

实践要点:只在必要时使用钩子,避免过度使用;初始化操作放 onMounted;DOM 操作放 onMounted 或 onUpdated;务必在 onBeforeUnmount 中清理资源避免内存泄漏。

组件样式 ​

Jiess 提供灵活的样式定义方式,支持字符串、对象、函数三种形式,并支持类 Sass 语法。Shadow DOM 改造后,className 和 class 均直接设置类名——Shadow Root 提供天然样式隔离,无需作用域前缀;样式自动分流,isolated CSS 注入 Shadow Root,global CSS(@keyframes/@font-face/:global)提升到文档级,用户使用 style()、className、:global 等 API 完全无感。

⚠️ 组件级样式需显式启用(2026-08 起):样式系统已拆分为可选模块以减小核心体积。需要组件级 style() / scope / :global 能力时,在 $entry 第三参注入 $style:

javascript
import { $entry } from 'jiess-core';
import { $initStyle } from 'jiess-core/style';
const jiess = $entry(JiessEnv, frameParam, { $style: $initStyle() });

不注入时组件 style 定义被忽略;节点内联 style 属性(如 { is: 'div', style: { color: 'red' } })不受影响。使用 jiess-plus 时已自动注入。

类 Sass 语法支持 ​

Jiess 组件的样式配置支持以下类 Sass 语法特性:

  • 嵌套规则:在选择器内部嵌套其他选择器,提高可读性
  • & 符号引用:引用父选择器,方便编写伪类和组合选择器
  • 变量定义:使用 $ 符号定义变量并在样式中复用
javascript
const EnhancedStyleComponent = {
  isJiess: true,
  setup() {
    this.add({
      is: 'div', className: 'enhanced-container',
      children: [
        { is: 'h3', className: 'title', children: '类 Sass 语法示例' },
        { is: 'p', className: 'content', children: '这是一个使用类 Sass 语法的组件' }
      ]
    });
  },
  style: `
    $primary-color: #409eff;
    $border-radius: 8px;

    .enhanced-container {
      background-color: #f5f7fa; padding: 16px; border-radius: $border-radius;

      .title {
        color: $primary-color; font-size: 18px; margin-bottom: 12px;
        &:hover { color: darken($primary-color, 10%); }
      }
      .content { color: #606266; line-height: 1.5; }
    }
  `
};

三种样式形式 ​

字符串形式(等价于 { css: 该字符串 } 且未定义 scope——无自己的作用域,继承祖先或全局):

javascript
style: `
  .styled-text { color: #409eff; font-size: 18px; font-weight: bold; }
`
// 等价于
style: { css: '.styled-text { color: #409eff; font-size: 18px; font-weight: bold; }' }
// 也等价于
style: () => '.styled-text { color: #409eff; font-size: 18px; font-weight: bold; }'

对象形式(支持 scope 属性和类 Sass 语法):

javascript
style: {
  scope: 'self',
  css: `
    $bg-color: #f0f9ff; $padding: 16px; $border-radius: 8px;
    .scoped-div {
      background-color: $bg-color; padding: $padding; border-radius: $border-radius;
      &:hover { background-color: lighten($bg-color, 5%); }
    }
  `
}

函数形式(动态样式,支持类 Sass 语法;函数会被调用并传入 jiess 实例,可用作动态取值,纯静态样式无需包函数):

javascript
style: (jiess) => {
  const primaryColor = '#409eff';
  return {
    scope: 'self',
    css: `
      $primary-color: ${primaryColor}; $padding: 16px;
      .dynamic-style {
        background-color: $primary-color; color: white; padding: $padding; border-radius: 8px;
        &:hover { background-color: darken($primary-color, 10%); }
      }
    `
  };
}

注意:style 仅支持字符串、{ css, scope } 对象、返回二者的函数三种形式。不再支持数组形式——早期数组方案用于"同一组件内多种作用域控制",但各项 scope 一旦漏配/写错样式就会泄漏全局;现已被 :global 标记取代(默认全隔离、显式标记才穿透,更安全)。数组形式传入会静默无效(不报错也不注入)。

global 功能 ​

:global 功能允许在隔离样式中定义全局样式规则——这些规则会被自动提升到文档级,可以影响组件外部的元素。支持两种写法(与 CSS Modules 一致):

  • 标记式::global 之后的选择器不 scoped,如 .a:global .btn(.a 隔离、.btn 全局)
  • 函数式::global(...) 括号内不 scoped,如 :global(.btn)、.a:global(.btn)

Web Components 路径差异:Shadow DOM 无法"部分穿透",含 :global 的规则整条提升到文档级且标记自动剥离(真实浏览器不识别 :global 伪类)。如需"部分 scoped、部分全局",请将全局部分单独写成一条 :global 规则。

使用场景 ​

  1. 覆盖第三方组件库的样式
  2. 定义需要全局生效的基础样式
  3. 跨组件样式共享
  4. 样式需要应用到动态生成的元素上

基本用法 ​

javascript
const GlobalStyleComponent = {
  isJiess: true,
  setup() {
    this.add({ is: 'div', className: 'outer', children: '包含全局样式的组件' });
  },
  style: {
    scope: 'self',
    css: `
      .outer {
        padding: 16px; background-color: #f5f5f5;
        :global {
          .inner { color: red; }
          .inner1 > .inner2 { color: blue; }
        }
      }
    `
  }
};

嵌套结构中使用 ​

javascript
style: {
  scope: 'self',
  css: `
    .container {
      border: 1px solid #e0e0e0; padding: 16px;
      .local-element { color: green; margin-bottom: 12px; }  // 注入 Shadow Root,仅当前组件可见
      :global {
        .global-element { color: red; font-weight: bold; }    // 提升到文档级,影响组件外部
      }
    }
  `
}

复杂选择器 ​

javascript
style: {
  scope: 'self',
  css: `
    .parent {
      :global {
        .child1, .child2 { margin: 8px; }           // 组合选择器
        .ancestor .descendant { padding: 4px; }     // 后代选择器
        .hoverable:hover { background-color: #f0f0f0; }  // 伪类选择器
        .with-after::after { content: ' (global)'; color: gray; }  // 伪元素选择器
      }
    }
  `
}

注意事项 ​

  1. :global 只能在 Shadow DOM 隔离样式(scope 非 share 时)中使用,纯全局字符串样式中无效
  2. 可在任意嵌套层级中使用,但建议保持合理的嵌套深度
  3. 过多的全局样式可能影响解析性能,建议只在必要时使用
  4. 使用全局样式时注意避免与其他组件样式产生命名冲突

作用域样式隔离 ​

Jiess 采用双层隔离架构实现样式隔离,确保组件样式不会相互影响:

  • Web Component:使用原生 attachShadow({ mode: 'open' }) API 创建 Shadow Root,实现浏览器级样式硬隔离
  • Vue3 / React:使用 SASS-like 嵌套语法(.a { .b {} } → .a .b {})实现 CSS 选择器级软隔离,isolated 和 global 样式统一注入文档级 style 标签

两种方案在隔离效果上等价,用户使用 style()、className、:global 等 API 完全无感。不使用 customElements.define,不依赖任何第三方 Shadow DOM 库。

实现机制 ​

  1. Web Component Shadow Root 创建:组件挂载时通过原生 attachShadow({ mode: 'open' }) 创建 Shadow Root,提供天然样式隔离边界
  2. Vue3/React 文档级注入:创建文档级 <style> 标签,isolated 和 global 样式统一注入,通过 SASS 嵌套选择器实现隔离
  3. 样式自动分流:isolated CSS 优先注入 Shadow Root(Web Component),回退到文档级(Vue3/React);global CSS(@keyframes/@font-face/:global)始终注入文档级
  4. 样式注入:StyleScopeManager.sanitizeCss() 对 CSS 进行清洗后,按分流规则注入
  5. 作用域传递(仅 Web Component):通过 ShadowRootContext 引用对象在组件树中传播,实现 share/break/self 三模式

具体步骤:Web Component 挂载时创建 Shadow Root → StyleScopeManager.sanitizeCss() 清洗 CSS → isolated CSS 优先注入 Shadow Root(Web Component),回退到文档级(Vue3/React)→ global CSS 始终注入文档级 → 根据 scope(归一化为内部 styleMode:仅 'share'/'break' 按枚举,true 归为 'self')决定是否将 ShadowRootContext 引用传递给子组件(仅 Web Component 生效)。旧的 scope 后缀机制(data-v-xxx 属性选择器)已移除,StyleScopeManager 已精简至仅保留 sanitizeCss 方法。

使用示例 ​

javascript
// 基本 Shadow DOM 隔离样式
style: {
  scope: 'self',
  css: `
    .scoped-element { color: red; padding: 16px; background-color: #f0f0f0; }
  `
}

作用域样式 vs 非作用域样式 ​

javascript
const ScopeStyleComponent = {
  isJiess: true,
  setup() {
    this.add({
      is: 'div',
      className: 'scope-class',   // 直接设置,Shadow DOM 内天然隔离
      class: 'global-class',      // 同样直接设置,与 className 行为一致
      children: '作用域样式示例'
    });
  },
  style: `
    .scope-class { color: red; }
    .global-class { font-weight: bold; }
  `
};

作用域样式共享 ​

Jiess 支持三种 scope 模式控制样式的传递范围,在保持 Shadow DOM 隔离的同时实现层次化共享。三种模式基于 ShadowRootContext 引用对象在组件树中传播,决定子组件是否复用祖先组件的 Shadow Root。

scope 兼容布尔值与枚举值(源码 Done.ts 中为 truthy 判断,归一化规则:仅 'share'/'break' 按枚举处理,其余 truthy 值一律归为 'self')。styleMode 是内部状态名,不要写在 style 对象里。

作用域模式 ​

scope 值实际模式描述作用范围
true'self'(等价)样式仅在当前组件内生效仅当前组件
'self''self'样式仅在当前组件内生效仅当前组件
'share''share'样式共享给所有子组件当前组件及其所有后代组件
'break''break'打断祖先组件的共享作用域仅当前组件,阻断祖先透传
false(或不写)无自己作用域不生成作用域 ID、样式原样注入,继承最近祖先 share 传递的作用域;无祖先共享则无隔离继承祖先

各模式示例 ​

Share 作用域(共享给子组件):

javascript
const ParentComponent = {
  isJiess: true, name: 'ParentComponent',
  setup() {
    this.add({ is: 'div', className: 'shared-class', children: '父组件' });
    this.add({ is: ChildComponent });
  },
  style: () => ({ scope: 'share', css: '.shared-class { color: red; }' })
};

const ChildComponent = {
  isJiess: true, name: 'ChildComponent',
  setup() {
    this.add({ is: 'div', className: 'shared-class', children: '子组件' });
  }
};

Break 作用域(打断继承链):

javascript
const GrandparentComponent = {
  isJiess: true,
  setup() { this.add({ is: ParentComponent }); },
  style: () => ({ scope: 'share', css: '.test-class { color: red; }' })
};

const ParentComponent = {
  isJiess: true,
  setup() { this.add({ is: ChildComponent }); },
  style: () => ({ scope: 'break', css: '.test-class { color: blue; }' })
};
// ChildComponent 不会继承 GrandparentComponent 的样式

Self 作用域(仅当前组件):

javascript
const ParentComponent = {
  isJiess: true,
  setup() {
    this.add({ is: 'div', className: 'self-class', children: '父组件' });
    this.add({ is: ChildComponent });
  },
  style: () => ({ scope: 'self', css: '.self-class { color: red; }' })
};
// ChildComponent 不会继承 ParentComponent 的样式

继承规则 ​

  1. 默认继承:子组件继承最近祖先组件的 share 作用域样式(通过 ShadowRootContext 引用对象传播)
  2. 作用域打断:break 打断祖先组件的样式共享链,创建新的 Shadow Root
  3. 样式隔离:self 样式仅在当前组件内生效,不向子组件传播 ShadowRootContext
  4. 优先级:组件自身定义的样式优先级高于继承的样式

最佳实践 ​

  • 需要全局共享的基础样式用 share
  • 需要隔离的组件样式用 self
  • 需要打断继承链的场景用 break
  • 使用语义化类名,结合 BEM 等命名规范
  • 作用域样式共享在 Vue 3、React 和 Web Components 环境下表现一致

异步组件 ​

Jiess 支持在 setup 函数中使用 async/await 处理异步操作。

基本用法 ​

javascript
const AsyncComponent = {
  isJiess: true,
  name: 'AsyncComponent',
  async setup(props) {
    this.add({ is: 'div', className: 'loading', children: '加载中...' });

    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
      const data = await response.json();
      this.add({
        is: 'div', className: 'async-data',
        children: [
          { is: 'h3', children: data.title },
          { is: 'p', children: `状态:${data.completed ? '已完成' : '未完成'}` }
        ]
      });
    } catch (error) {
      this.add({ is: 'div', className: 'error', children: `加载失败:${error.message}` });
    } finally {
      this.forceUpdate();
    }
  },
  style: `
    .loading { color: #409eff; padding: 16px; }
    .async-data { padding: 16px; border: 1px solid #e0e0e0; border-radius: 8px; }
    .error { color: #f56c6c; padding: 16px; border: 1px solid #fbc4ab; border-radius: 8px; background-color: #fef0f0; }
  `
};

实际项目实现 ​

参考 jiess-platform 中的实践模式——异步操作完成后调用 forceUpdate() 确保组件正确渲染:

javascript
return {
  name,
  isJiess: true,
  async setup(props, attach) {
    attach.tools = tools;
    Object.assign(props, param);
    await setup.call(this, props, attach);
    this.forceUpdate();
  }
};

依赖注入 ​

provide/inject 机制允许组件间跨层级传递数据,无需通过 props 逐层传递。

基本用法 ​

javascript
// 父组件提供数据
function setup() {
  this.provide({
    appName: 'Jiess Demo',
    version: '1.0.0',
    user: { name: '张三', role: 'admin' }
  });
  this.add({ is: ChildComponent });
}

// 子组件
const ChildComponent = {
  isJiess: true,
  setup() {
    const appName = this.inject('appName');
    this.add({ is: 'div', children: [{ is: 'h3', children: appName }, { is: GrandChildComponent }] });
  }
};

// 孙子组件
const GrandChildComponent = {
  isJiess: true,
  setup() {
    const version = this.inject('version');
    const user = this.inject('user');
    this.add({
      is: 'div',
      children: [
        { is: 'p', children: `版本:${version}` },
        { is: 'p', children: `当前用户:${user.name} (${user.role})` }
      ]
    });
  }
};

适用场景 ​

  1. 跨层级数据传递:数据需要在多个层级的组件间共享
  2. 全局配置:应用主题、用户信息等全局配置
  3. 服务注入:API 服务、工具函数等
  4. 插件系统:构建可扩展的插件系统

最佳实践示例 ​

javascript
const ThemeProviderComponent = {
  isJiess: true,
  name: 'ThemeProviderComponent',
  setup(props) {
    const theme = $reactive({
      primaryColor: '#409eff',
      secondaryColor: '#606266',
      fontSize: '14px'
    });
    this.provide({ theme });
    this.add({ is: ChildComponent });
  }
};

const ThemedButtonComponent = {
  isJiess: true,
  name: 'ThemedButtonComponent',
  setup(props) {
    const theme = this.inject('theme') || {
      primaryColor: '#409eff', fontSize: '14px'
    };
    this.add({
      is: 'button', children: 'Themed Button',
      style: {
        backgroundColor: theme.primaryColor, color: 'white',
        fontSize: theme.fontSize, padding: '8px 16px',
        border: 'none', borderRadius: '4px'
      }
    });
  }
};

实践要点:合理控制传递层级;注入的数据应是响应式的;为可能不存在的注入数据提供默认值;简单父子通信优先使用 props 和事件。

组件通信 ​

Jiess 提供多种通信方式适应不同场景:

父子组件通信 ​

  1. Props 传递(父 → 子):父组件通过 props 向子组件传递数据
  2. 事件回调(子 → 父):子组件调用父组件传递的事件函数向父组件传递数据
  3. 组件引用(父 → 子):父组件通过 $ref 获取子组件引用,直接访问暴露的数据和方法
javascript
// 父 → 子:属性传递
this.add({ is: ChildComponent, message: 'Hello', count: 10 })

// 子 → 父:事件回调
this.add({
  is: ChildComponent,
  onSuccess: (result) => console.log('操作成功:', result),
  onError: (err) => console.error('操作失败:', err)
})

// 子组件内触发回调
function setup(props) {
  const { onSuccess, onError } = props
  this.add({
    is: 'button', children: '提交',
    onClick: async () => {
      try { onSuccess?.(await doSomething()) }
      catch (e) { onError?.(e) }
    }
  })
}

跨层级组件通信 ​

  1. 依赖注入:使用 provide/inject 跨层级传递数据(参见上文依赖注入)
  2. 全局状态管理:使用全局响应式对象管理共享状态
javascript
// store.js
export const globalState = $reactive({ user: null, count: 0 })

// 任何组件都可以读写它
import { globalState } from '@/store'
globalState.count++  // 所有绑定它的视图都会更新

兄弟组件通信 ​

  1. 父组件作为中介:通过父组件传递数据和事件
  2. 全局状态管理:使用全局响应式对象共享状态
  3. 事件总线:使用事件总线机制传递事件

组件最佳实践 ​

基础最佳实践 ​

  1. 为每个组件设置 name:方便调试和错误追踪
  2. 合理使用作用域样式:避免样式冲突,提高组件封装性
  3. 定义清晰的 props 规则:提高组件的易用性和健壮性
  4. 使用 TypeScript 类型定义:提供更好的开发体验和类型安全
  5. 保持单一职责:一个组件只做一件事,提高可维护性
  6. 合理使用生命周期钩子:在适当的时机执行相应的逻辑
  7. 避免过度使用依赖注入:简单父子通信优先使用 props 和事件

性能优化 ​

  1. 优化 setup 函数:避免在 setup 中执行复杂计算
  2. 合理使用响应式数据:只对需要响应式的数据使用 $ref 或 $reactive
  3. 使用计算属性:对复杂计算使用 $computed 缓存结果
  4. 虚拟滚动:大数据列表考虑使用虚拟滚动
  5. 减少不必要的渲染:合理设计组件结构,避免不必要的子组件渲染
  6. 使用事件委托:大量相似元素的事件监听使用事件委托
javascript
const PerformanceOptimizedComponent = {
  isJiess: true,
  name: 'PerformanceOptimizedComponent',
  props() {
    return { items: { type: Array, default: () => [] } };
  },
  setup(props) {
    const totalItems = $computed(() => props.items.length);
    const completedItems = $computed(() => props.items.filter(item => item.completed).length);

    this.add({
      is: 'div',
      children: [
        { is: 'h3', children: { $proxy: totalItems, done: (v) => `总项目数:${v}` } },
        { is: 'p', children: { $proxy: completedItems, done: (v) => `已完成:${v}` } },
        { is: 'div', children: '项目列表' }
      ]
    });
  }
};

代码组织 ​

  1. 模块化设计:将组件拆分为更小的、可复用的模块
  2. 合理组织目录结构:按功能或特性分组
  3. 保持一致的代码风格:提高可读性
  4. 为复杂逻辑添加注释:便于后续维护
  5. 提取通用逻辑到工具函数:减少重复代码

组件复用设计 ​

  1. 提取通用逻辑:将通用逻辑提取到工具函数或高阶组件
  2. 可配置性:通过 props 控制组件行为,设计可配置的组件
  3. 预留扩展接口:考虑组件的扩展性
  4. 使用组合模式:构建复杂组件用组合而非继承

组件测试 ​

Jiess 组件可以使用标准的 JavaScript 测试框架进行测试。

测试工具推荐 ​

  • Vitest:基于 Vite 的快速测试框架
  • Jest:功能强大的 JavaScript 测试框架
  • @testing-library/react:React 组件测试库
  • @vue/test-utils:Vue 组件测试库
  • @open-wc/testing:Web Components 组件测试库

测试思路 ​

  1. 测试组件渲染:验证组件是否正确渲染
  2. 测试属性传递:验证组件是否正确处理传入的属性
  3. 测试事件处理:验证组件是否正确处理用户交互
  4. 测试生命周期:验证组件在不同生命周期阶段的行为

可测试的组件示例 ​

javascript
const TestComponent = {
  isJiess: true,
  name: 'TestComponent',
  props() {
    return { initialValue: { type: Number, default: 0 } };
  },
  setup(props) {
    const count = $ref(props.initialValue);

    this.add({ is: 'div', className: 'test-count', children: { $proxy: count } });
    this.add({ is: 'button', className: 'test-button', children: '增加', onClick: () => count.value++ });

    this.expose({ count });
  }
};

测试最佳实践 ​

  1. 单元测试:测试组件的各个独立功能
  2. 集成测试:测试组件与其他组件的交互
  3. 端到端测试:测试完整的用户流程
  4. 确保测试覆盖:覆盖主要功能和边界情况
  5. 模拟依赖:使用模拟数据和函数测试组件
  6. 清晰一致的命名:测试描述使用清晰的中文命名
javascript
// Vitest 测试示例
import { describe, it, expect } from 'vitest';

describe('TestComponent', () => {
  it('应该渲染初始值', () => {
    const container = document.createElement('div');
    // 根据具体测试环境实现组件渲染
    // 例如:render(TestComponent, { initialValue: 0 }, container);
    expect(container.querySelector('.test-count')).toBeTruthy();
  });

  it('应该在点击按钮后增加计数', () => {
    const container = document.createElement('div');
    // 渲染组件
    // 模拟按钮点击
    // 验证计数是否增加
  });
});

常见问题与解决方案 ​

问题:组件不渲染 ​

原因:setup 函数中没有调用 this.add() 添加任何内容。 解决方案:确保在 setup 函数中调用 this.add() 添加至少一个元素。

问题:响应式数据不更新 ​

原因:没有使用 $ref 或 $reactive 创建响应式数据。 解决方案:使用 $ref 或 $reactive 创建需要响应式的数据。

问题:组件引用获取不到 ​

原因:没有使用 $ref 或没有正确传递 $ref 参数。 解决方案:确保使用 $ref 创建引用,并通过 $ref 参数传递给组件。

问题:样式不生效 ​

原因:样式配置格式不正确或 scope 设置错误;Shadow DOM 改造后 className 和 class 均直接设置类名,不再有作用域前缀差异。 解决方案:检查样式配置格式和 scope 设置(注意:scope 才是 style 对象上的属性名,兼容 true/false/'self'/'share'/'break',styleMode 是内部状态名不可写),确认 isolated CSS 已注入 Shadow Root、global CSS 已提升到文档级。

问题:跨框架兼容性问题 ​

原因:使用了框架特有的特性。 解决方案:避免使用框架特有的特性,使用 Jiess 提供的跨框架兼容 API。

问题:生命周期钩子不执行 ​

原因:生命周期钩子的使用方式不正确。 解决方案:确保在 setup 函数中通过 this.onMounted(() => {}) 等方式正确注册生命周期钩子。

总结 ​

Jiess 组件系统提供了强大而灵活的组件开发能力,支持:

  • 简洁的组件配置语法
  • 完整的组件生命周期
  • 灵活的组件传参与校验
  • 强大的样式管理与作用域隔离
  • Vue 3 / React / Web Components 跨框架兼容
  • 丰富的组件通信机制(props、事件、expose、provide/inject、全局状态)

接下来学习什么? ​

  • 创建节点:掌握 Jiess 的节点创建和配置方式
  • 响应式绑定:深入理解 Jiess 的响应式系统
  • 响应式观察:学习如何监听和响应数据变化
  • 工具 & 导出:探索 Jiess 提供的丰富工具函数