Jiess 组件系统
目录
- 什么是 Jiess 组件?
- 设计原理
- 组件基本结构
- 组件上下文 this
- 组件传参
- 组件入参校验
- 组件数据暴露
- 全局注册组件
- 组件生命周期
- 组件样式
- 作用域样式共享
- 异步组件
- 依赖注入
- 组件通信
- 组件最佳实践
- 组件测试
- 常见问题与解决方案
- 总结
什么是 Jiess 组件?
Jiess 组件是一种基于配置对象的跨框架组件实现方式。通过统一的 JavaScript 对象描述组件的结构、行为和样式,由 Jiess 运行时根据当前框架环境自动转换为对应框架(Vue 3 / React / Web Components)的组件实例。
核心优势:
- 跨框架兼容:一套代码可在 Vue 3、React 和 Web Components 环境中无缝运行
- 配置驱动:使用普通 JavaScript 对象配置,无需学习额外模板语法
- 完整功能:支持生命周期、样式管理、组件通信等完整组件特性
- 易于集成:可与现有框架生态系统无缝集成
设计原理
Jiess 组件系统的核心设计思想是:通过统一的配置对象描述组件,再由 Jiess 运行时根据当前框架环境,将配置转换为对应框架的组件实例。一套代码,多框架运行。
核心概念
- 组件配置对象:使用普通 JavaScript 对象描述组件的结构、行为和样式
- 跨框架适配层:负责将 Jiess 组件配置转换为具体框架的组件
- 组件上下文:提供组件运行时的各种工具方法和状态管理
- 生命周期钩子:与各框架生命周期对齐,确保组件行为一致性
跨框架兼容性
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 场景
适配流程
- 检测当前运行环境(Vue 3、React 或 Web Components)
- 根据环境选择对应的适配策略
- 转换组件配置为框架特定格式
- 处理框架特有的生命周期和特性
- 返回适配后的组件实例
组件基本结构
Jiess 组件采用简洁的对象声明方式。一个完整的 Jiess 组件基本结构如下:
const MyComponent = {
isJiess: true, // 标记为 Jiess 组件,必须设置为 true
name: 'MyComponent', // 组件名称,可选,便于调试和错误追踪
setup(props) { // 组件构造函数,核心逻辑在这里实现(必须用普通函数)
// 组件逻辑
},
props () { // 入参校验配置,可选
return {
// 在这里定义组件允许接收的属性规则
};
},
style() { // 组件样式配置,可选
return [
// 样式配置
];
}
};
// 使用组件
function setup () {
this.add({ is: MyComponent });
}配置项说明
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
isJiess | boolean | 是 | 标记该对象为 Jiess 组件,必须设置为 true |
name | string | 否 | 组件名称,用于调试和错误追踪 |
setup | function | 是 | 组件构造函数,定义组件逻辑和结构,必须使用普通函数以确保 this 上下文正确 |
props | function | 否 | 入参校验规则,返回属性配置对象 |
style | string / object / function | 否 | 组件样式配置 |
最小化组件示例
最简单的 Jiess 组件只需两个属性:isJiess 和 setup:
const MinimalComponent = {
isJiess: true,
setup(props) {
this.add('Hello, Jiess!');
}
};
function setup() {
this.add({ is: MinimalComponent });
}完整组件示例
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 组件的参数传递不区分属性和事件,所有参数都作为普通属性传入。
普通参数
// 父组件中使用组件
this.add({ is: MyComponent, message: 'Hello, Jiess!', count: 100 });
// 组件内部获取参数
const MyComponent = {
isJiess: true,
setup(props) {
const { message, count } = props;
this.add(`收到消息:${message},数量:${count}`);
}
};事件传递
事件也作为普通属性传递,组件内部可直接调用:
// 父组件中传递事件
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:传递插槽内容,用于组件内容分发
参数传递最佳实践
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 种类型
| 类型 | 检测方式 | 示例值 |
|---|---|---|
String | typeof === 'string' | 'hello' |
Number | typeof === 'number' | 42 |
Boolean | typeof === 'boolean' | true |
Object | typeof === 'object' | { key: 'val' } |
Array | Array.isArray() | [1, 2, 3] |
Function | typeof === 'function' | () => {} |
校验选项
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 类型支持
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 方法将组件内部数据和方法暴露给父组件,实现组件间通信。
基本用法
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 内置参数获取组件引用,访问暴露的数据和方法:
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();
}
}
});
}最佳实践
- 只暴露必要的数据和方法:避免暴露过多内部实现细节
- 使用响应式数据:暴露的数据应该是响应式的,以便父组件感知变化
- 明确的 API 设计:为暴露的方法和属性提供清晰的命名
- 避免循环依赖:确保父组件和子组件之间没有循环依赖
组件的模块化管理
Jiess 组件就是普通的 JavaScript 对象,无需特殊的注册机制——通过标准的 ES 模块 export/import 即可实现组件复用。
// 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; }
`
};// 使用组件
// 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('按钮被点击了!')
});
}最佳实践
- 模块化组织:将可复用的组件放在
components/目录下,按功能分文件夹管理 - 避免循环依赖:组件之间通过 props 和 events 通信,避免相互 import
- 使用 PascalCase:组件文件名使用 PascalCase(如
GlobalButton.js),与组件命名一致
组件生命周期
Jiess 提供了 5 个与框架生命周期对应的钩子函数:
| 钩子函数 | 触发时机 | 典型用途 |
|---|---|---|
onBeforeMount | 组件挂载到 DOM 前 | 初始化数据、准备资源 |
onMounted | 组件挂载到 DOM 后 | DOM 操作、网络请求、启动定时器 |
onBeforeUpdate | 组件更新前 | 获取更新前状态、保存快照 |
onUpdated | 组件更新后 | DOM 操作、第三方库更新 |
onBeforeUnmount | 组件从 DOM 卸载前 | 清理定时器、取消请求、移除事件监听 |
执行顺序
创建 → onBeforeMount → 挂载 → onMounted
数据变化 → onBeforeUpdate → 重新渲染 → onUpdated
卸载 → onBeforeUnmount最佳实践示例
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:javascriptimport { $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 语法特性:
- 嵌套规则:在选择器内部嵌套其他选择器,提高可读性
&符号引用:引用父选择器,方便编写伪类和组合选择器- 变量定义:使用
$符号定义变量并在样式中复用
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——无自己的作用域,继承祖先或全局):
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 语法):
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 实例,可用作动态取值,纯静态样式无需包函数):
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规则。
使用场景
- 覆盖第三方组件库的样式
- 定义需要全局生效的基础样式
- 跨组件样式共享
- 样式需要应用到动态生成的元素上
基本用法
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; }
}
}
`
}
};嵌套结构中使用
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; } // 提升到文档级,影响组件外部
}
}
`
}复杂选择器
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; } // 伪元素选择器
}
}
`
}注意事项
:global只能在 Shadow DOM 隔离样式(scope非share时)中使用,纯全局字符串样式中无效- 可在任意嵌套层级中使用,但建议保持合理的嵌套深度
- 过多的全局样式可能影响解析性能,建议只在必要时使用
- 使用全局样式时注意避免与其他组件样式产生命名冲突
作用域样式隔离
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 库。
实现机制
- Web Component Shadow Root 创建:组件挂载时通过原生
attachShadow({ mode: 'open' })创建 Shadow Root,提供天然样式隔离边界 - Vue3/React 文档级注入:创建文档级
<style>标签,isolated 和 global 样式统一注入,通过 SASS 嵌套选择器实现隔离 - 样式自动分流:isolated CSS 优先注入 Shadow Root(Web Component),回退到文档级(Vue3/React);global CSS(
@keyframes/@font-face/:global)始终注入文档级 - 样式注入:
StyleScopeManager.sanitizeCss()对 CSS 进行清洗后,按分流规则注入 - 作用域传递(仅 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 方法。
使用示例
// 基本 Shadow DOM 隔离样式
style: {
scope: 'self',
css: `
.scoped-element { color: red; padding: 16px; background-color: #f0f0f0; }
`
}作用域样式 vs 非作用域样式
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 作用域(共享给子组件):
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 作用域(打断继承链):
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 作用域(仅当前组件):
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 的样式继承规则
- 默认继承:子组件继承最近祖先组件的
share作用域样式(通过ShadowRootContext引用对象传播) - 作用域打断:
break打断祖先组件的样式共享链,创建新的 Shadow Root - 样式隔离:
self样式仅在当前组件内生效,不向子组件传播ShadowRootContext - 优先级:组件自身定义的样式优先级高于继承的样式
最佳实践
- 需要全局共享的基础样式用
share - 需要隔离的组件样式用
self - 需要打断继承链的场景用
break - 使用语义化类名,结合 BEM 等命名规范
- 作用域样式共享在 Vue 3、React 和 Web Components 环境下表现一致
异步组件
Jiess 支持在 setup 函数中使用 async/await 处理异步操作。
基本用法
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() 确保组件正确渲染:
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 逐层传递。
基本用法
// 父组件提供数据
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})` }
]
});
}
};适用场景
- 跨层级数据传递:数据需要在多个层级的组件间共享
- 全局配置:应用主题、用户信息等全局配置
- 服务注入:API 服务、工具函数等
- 插件系统:构建可扩展的插件系统
最佳实践示例
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 提供多种通信方式适应不同场景:
父子组件通信
- Props 传递(父 → 子):父组件通过 props 向子组件传递数据
- 事件回调(子 → 父):子组件调用父组件传递的事件函数向父组件传递数据
- 组件引用(父 → 子):父组件通过
$ref获取子组件引用,直接访问暴露的数据和方法
// 父 → 子:属性传递
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) }
}
})
}跨层级组件通信
- 依赖注入:使用
provide/inject跨层级传递数据(参见上文依赖注入) - 全局状态管理:使用全局响应式对象管理共享状态
// store.js
export const globalState = $reactive({ user: null, count: 0 })
// 任何组件都可以读写它
import { globalState } from '@/store'
globalState.count++ // 所有绑定它的视图都会更新兄弟组件通信
- 父组件作为中介:通过父组件传递数据和事件
- 全局状态管理:使用全局响应式对象共享状态
- 事件总线:使用事件总线机制传递事件
组件最佳实践
基础最佳实践
- 为每个组件设置
name:方便调试和错误追踪 - 合理使用作用域样式:避免样式冲突,提高组件封装性
- 定义清晰的 props 规则:提高组件的易用性和健壮性
- 使用 TypeScript 类型定义:提供更好的开发体验和类型安全
- 保持单一职责:一个组件只做一件事,提高可维护性
- 合理使用生命周期钩子:在适当的时机执行相应的逻辑
- 避免过度使用依赖注入:简单父子通信优先使用 props 和事件
性能优化
- 优化 setup 函数:避免在 setup 中执行复杂计算
- 合理使用响应式数据:只对需要响应式的数据使用
$ref或$reactive - 使用计算属性:对复杂计算使用
$computed缓存结果 - 虚拟滚动:大数据列表考虑使用虚拟滚动
- 减少不必要的渲染:合理设计组件结构,避免不必要的子组件渲染
- 使用事件委托:大量相似元素的事件监听使用事件委托
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: '项目列表' }
]
});
}
};代码组织
- 模块化设计:将组件拆分为更小的、可复用的模块
- 合理组织目录结构:按功能或特性分组
- 保持一致的代码风格:提高可读性
- 为复杂逻辑添加注释:便于后续维护
- 提取通用逻辑到工具函数:减少重复代码
组件复用设计
- 提取通用逻辑:将通用逻辑提取到工具函数或高阶组件
- 可配置性:通过 props 控制组件行为,设计可配置的组件
- 预留扩展接口:考虑组件的扩展性
- 使用组合模式:构建复杂组件用组合而非继承
组件测试
Jiess 组件可以使用标准的 JavaScript 测试框架进行测试。
测试工具推荐
- Vitest:基于 Vite 的快速测试框架
- Jest:功能强大的 JavaScript 测试框架
- @testing-library/react:React 组件测试库
- @vue/test-utils:Vue 组件测试库
- @open-wc/testing:Web Components 组件测试库
测试思路
- 测试组件渲染:验证组件是否正确渲染
- 测试属性传递:验证组件是否正确处理传入的属性
- 测试事件处理:验证组件是否正确处理用户交互
- 测试生命周期:验证组件在不同生命周期阶段的行为
可测试的组件示例
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 });
}
};测试最佳实践
- 单元测试:测试组件的各个独立功能
- 集成测试:测试组件与其他组件的交互
- 端到端测试:测试完整的用户流程
- 确保测试覆盖:覆盖主要功能和边界情况
- 模拟依赖:使用模拟数据和函数测试组件
- 清晰一致的命名:测试描述使用清晰的中文命名
// 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 提供的丰富工具函数