# JS开发框架 **Repository Path**: legend0/jPages ## Basic Information - **Project Name**: JS开发框架 - **Description**: js框架,快速开发 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-01-17 - **Last Updated**: 2026-05-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JPage 框架 - 轻量级插件化 SPA 框架 > 🎉 **JPage** - 基于 Hash 路由的轻量级单页应用框架,采用插件化架构,核心精简,功能按需加载! *** ## 📖 目录 - [核心特性](#核心特性) - [快速开始](#快速开始) - [核心API](#核心api) - [响应式系统](#响应式系统) - [错误处理](#错误处理) - [插件系统](#插件系统) - [路由管理](#路由管理) - [页面管理](#页面管理) - [组件系统](#组件系统) - [配置选项](#配置选项) - [项目结构](#项目结构) - [常见问题](#常见问题) *** ## ✨ 核心特性 - ✅ **插件化架构** - 灵活的插件系统,支持自定义插件开发(如状态管理、异步任务管理等) - ✅ **生产级模板引擎** - 内置轻量级模板引擎,支持过滤器、条件、循环等语法 - ✅ **工具函数批量导入** - 支持 `JPage.utils()` 批量导入,支持通配符 `*` - ✅ **Hash 路由系统** - 支持路由参数、查询参数、路由守卫和嵌套路由 - ✅ **页面管理** - 自动加载页面组件,支持 CSS 隔离和智能缓存 - ✅ **组件系统** - 支持组件的注册、加载、渲染和生命周期管理 - ✅ **生命周期** - 完整的页面和组件生命周期钩子函数 - ✅ **响应式系统** - 基于 Proxy 的自动响应式数据,数据变更自动触发视图更新 - ✅ **错误处理** - 统一的错误处理机制,支持错误分类和全局处理 - ✅ **事件通信** - 统一的事件通信机制,内部使用 `$on/$emit`,外部使用 `JPage.on/emit` - ✅ **调试模式** - 详细的日志输出,方便开发调试 - ✅ **TypeScript 友好** - 完整的 JSDoc 注释,类型提示完善 *** ## 🚀 快速开始 ### 安装 ```bash # 克隆项目 git clone https://github.com/your-repo/jpage.git # 安装依赖 npm install # 构建项目 npm run build ``` ### 基础使用 ```html
年龄: {{ user.age }}
描述: {{ computed.description }}
{{ message }}
{{ message }}
计数: {{ count }}
{{ message }}
{{ user.profile.name }}
{{ items.0.title }}
总价: {{ price * quantity }}
折扣价: {{ price * 0.8 }}
金额: {{ (price * quantity).toFixed(2) }}
是否成年: {{ age >= 18 }}
状态: {{ status === 'active' ? '在线' : '离线' }}
是否显示: {{ show && hasPermission }}
整数: {{ parseInt(price) }}
浮点数: {{ parseFloat(price) }}
非数字: {{ isNaN(value) }}
编码: {{ encodeURIComponent(url) }}
时间戳: {{ Date.now() }}
JSON: {{ JSON.stringify(data) }}
最大值: {{ Math.max(a, b) }}
随机数: {{ Math.random() }}
欢迎回来,{{ username }}!
{% elif hasInvitation %}请先完成注册
{% else %}请登录
{% endif %} {% if score >= 90 %} 优秀 {% elif score >= 60 %} 及格 {% else %} 不及格 {% endif %}日期: {{ createTime | formatDate 'YYYY-MM-DD HH:mm' }}
价格: {{ price | number '¥' 2 }}
名称: {{ name | upper }}
简介: {{ description | truncate 50 '...' }}
昵称: {{ nickname | default '匿名用户' }}
标签: {{ tags | join ', ' }}
{{ title | trim | upper }}
{{ content | truncate 100 '...' | lower }}
` ``` #### 6. 事件绑定 使用 `@event` 语法绑定事件处理器: ```javascript template: ` 链接 ` ``` **支持的事件修饰符:** | 修饰符 | 说明 | |--------|------| | `.prevent` | 阻止默认行为 | | `.stop` | 阻止事件冒泡 | | `.once` | 只触发一次 | | `.self` | 只在事件目标自身触发 | **事件参数:** 支持传递参数和特殊变量 `$event`: ```javascript template: ` ` ``` #### 7. 保留区域 使用 `{% preserve %}`、`{% endpreserve %}` 标记需要保留内部状态的区域: ```javascript template: `` 标签组合会自动被添加 preserve 保护,避免模板引擎解析代码示例中的模板语法:
```javascript
template: `
// 这段代码中的 {{ }} 不会被解析
const greeting = "{{ name }}";
`
```
#### 8. 模板注释
使用 `{# comment #}` 添加模板注释,注释不会输出到 HTML:
```javascript
template: `
{# 这是一条注释 #}
{{ message }}
`
```
#### 9. 原始输出
使用 `{!! expression !!}` 进行原始输出(不进行 HTML 转义):
```javascript
template: `
{!! htmlContent !!}
`
```
> **注意**:原始输出存在 XSS 风险,请确保内容是安全可信的。
### 页面过渡动画
JPage 支持页面切换时的过渡动画效果,提供流畅的用户体验。
#### 配置过渡动画
```javascript
JPage.options({
transition: {
name: 'fade', // 动画类型:fade、slide、scale、flip
duration: 300 // 动画时长(毫秒)
}
});
```
#### 支持的动画类型
| 动画类型 | 效果描述 | 适用场景 |
|---------|---------|---------|
| `fade` | 淡入淡出,伴随轻微位移 | 通用页面切换 |
| `slide` | 水平滑动效果 | 水平导航场景 |
| `scale` | 缩放淡入淡出 | 模态框、弹窗 |
| `flip` | 3D翻转效果 | 卡片翻转、特殊效果 |
#### 动画执行流程
页面过渡动画按"离开动画→卸载→挂载→进入动画→清理"顺序执行。
#### 同一页面检测
当路由参数或查询参数变化但页面名称相同时,框架会智能判断是否需要执行完整切换,只有完全相同的路由才会跳过切换只更新数据。
#### 自定义动画
可以通过 CSS 自定义动画效果:
```css
/* 自定义过渡动画 */
.jpage-transition-custom.jpage-transition-enter {
opacity: 0;
transform: rotate(180deg);
}
.jpage-transition-custom.jpage-transition-enter-active {
opacity: 1;
transform: rotate(0);
transition: all 500ms ease-in-out;
}
```
#### 禁用动画
设置 `duration: 0` 或不配置 `transition` 即可禁用动画。
### 事件绑定语法
JPage 提供强大的事件绑定语法,支持参数传递和复杂表达式。
#### 基础事件绑定
```html
```
#### 支持的事件类型
- **鼠标事件**: `@click`, `@dblclick`, `@mouseover`, `@mouseout`, `@mousedown`, `@mouseup`
- **键盘事件**: `@keydown`, `@keyup`, `@keypress`
- **表单事件**: `@input`, `@change`, `@submit`, `@focus`, `@blur`
- **触摸事件**: `@touchstart`, `@touchend`, `@touchmove`
- **其他事件**: `@load`, `@error`, `@scroll`, `@resize`
#### 参数类型支持
| 参数类型 | 示例 | 说明 |
|---------|------|------|
| 字符串 | `'hello'`, `"world"` | 单引号或双引号字符串 |
| 数字 | `123`, `3.14` | 整数或浮点数 |
| 布尔值 | `true`, `false` | 布尔值 |
| null | `null` | null 值 |
| 对象 | `{key: 'value'}` | 简单对象字面量 |
| 特殊变量 | `$event` | DOM 事件对象 |
#### 事件处理器定义
在页面或组件中定义事件处理器:
```javascript
JPage.route('/example', {
name: 'example',
template: `
`,
methods: {
handleClick(message, number) {
console.log('点击事件:', message, number);
},
handleInput(event) {
console.log('输入值:', event.target.value);
}
}
});
```
#### 事件绑定特性
事件处理器自动绑定 this、支持参数自动解析、错误自动捕获、页面卸载时自动清理监听器。
***
| `default` | `{{ val \| default '默认值' }}` | 默认值回退 |
| `truncate` | `{{ text \| truncate 50 '...' }}` | 文本截断 |
#### 3. 条件渲染
```javascript
template: `
{% if isLoggedIn %}
欢迎回来,{{ userName }}
{% endif %}
{% if isAdmin %}
管理员权限
{% endif %}
{% if score >= 90 %}
优秀
{% elif score >= 60 %}
及格
{% else %}
不及格
{% endif %}
`
```
#### 4. 列表渲染
```javascript
template: `
{% for item in items %}
-
{{ __index }}. {{ item.name }}
{% if __first %}(第一个){% endif %}
{% if __last %}(最后一个){% endif %}
{% endfor %}
`,
data() {
return {
items: [
{ name: '苹果' },
{ name: '香蕉' },
{ name: '橙子' }
]
};
}
```
**循环变量:**
| 变量 | 说明 |
| --------- | ---------- |
| `__index` | 当前索引(从0开始) |
| `__first` | 是否为第一个元素 |
| `__last` | 是否为最后一个元素 |
#### 5. 注释
```javascript
template: `
{# 这是注释,不会渲染 #}
{{ message }}
`
```
#### 6. 事件绑定
JPage 支持多种事件绑定方式,推荐使用模板语法 `@click`:
```javascript
// 方式1:模板语法 @click(推荐)
template: `
`,
methods: {
handleClick() {
// 处理点击事件
JPage.push('/about');
}
}
// 方式2:使用 onclick 属性
template: `
`
// 方式3:在 mounted 钩子中绑定(数据更新会重新渲染,事件会失效)
mounted() {
document.getElementById('myButton').addEventListener('click', () => {
this.handleClick();
});
}
// 方式4:事件委托(适用于动态内容)
mounted() {
document.addEventListener('click', (e) => {
if (e.target.id === 'myButton') {
this.handleClick();
}
});
}
```
**支持的事件类型:**
- `@click` - 点击事件
- `@change` - 输入变化事件
- `@input` - 输入事件
- `@submit` - 表单提交事件
- `@focus` - 聚焦事件
- `@blur` - 失焦事件
- 其他原生 DOM 事件均支持
#### 7. 自定义过滤器
JPage 提供了优雅的过滤器注册方式,支持全局注册和局部定义两种方式。
**方式一:全局注册(推荐)**
通过 `JPage.template.filter()` 或 `JPage.template.filters()` 方法全局注册过滤器,注册后在所有页面和组件中都可使用。
```javascript
// 注册单个过滤器
JPage.template.filter('currency', function(val, symbol = '¥') {
const num = parseFloat(val);
return isNaN(num) ? '' : symbol + num.toFixed(2);
});
// 批量注册多个过滤器
JPage.template.filters({
currency: function(val, symbol = '¥') {
const num = parseFloat(val);
return isNaN(num) ? '' : symbol + num.toFixed(2);
},
uppercase: function(val) {
return String(val == null ? '' : val).toUpperCase();
},
truncate: function(val, length = 50, suffix = '...') {
const str = String(val == null ? '' : val);
return str.length <= length ? str : str.slice(0, length) + suffix;
}
});
```
**方式二:页面/组件局部定义**
在页面或组件的定义中通过 `filters` 属性局部注册过滤器,仅在当前页面或组件内生效。
```javascript
// 页面中定义局部过滤器
JPage.route('/product', {
name: 'product',
template: `
价格: {{ price | currency '¥' }}
描述: {{ description | truncate 100 }}
`,
data() {
return {
price: 199.99,
description: '这是一个很长的产品描述...'
};
},
filters: {
currency: function(val, symbol = '¥') {
const num = parseFloat(val);
return isNaN(num) ? '' : symbol + num.toFixed(2);
},
truncate: function(val, length = 50) {
const str = String(val == null ? '' : val);
return str.length <= length ? str : str.slice(0, length) + '...';
}
}
});
```
**过滤器优先级**
过滤器查找顺序为:组件/页面局部过滤器 → 全局注册过滤器 → 内置过滤器。
**内置过滤器列表**
|
| 过滤器 | 说明 | 示例 |
| :----------- | ----- | ---------------- | ---------------------------- |
| `formatDate` | 日期格式化 | \`{{ createTime | formatDate 'YYYY-MM-DD' }}\` |
| `number` | 数字千分位 | \`{{ price | number }}\` |
| `upper` | 转大写 | \`{{ name | upper }}\` |
| `lower` | 转小写 | \`{{ name | lower }}\` |
| `default` | 默认值回退 | \`{{ nickname | default '匿名用户' }}\` |
| `truncate` | 截断文本 | \`{{ description | truncate 50 }}\` |
**使用示例**
```javascript
template: `
用户名: {{ username | default '访客' | upper }}
价格: ¥{{ price | number }}
创建时间: {{ createTime | formatDate 'YYYY-MM-DD HH:mm' }}
简介: {{ intro | truncate 100 '...' }}
`
```
### 页面 CSS 隔离
```css
/* pages/home.css */
.home-page {
padding: 20px;
}
.home-page h1 {
color: #333;
}
/* 全局样式(不会被隔离) */
/* @global */
.global-style {
color: red;
}
/* 全局样式块 */
/* @global-start */
.global-block {
background: blue;
}
/* @global-end */
```
### 页面缓存
```javascript
// 配置页面缓存
JPage.routes({
'/user/:id': {
component: 'user',
cache: true // 启用缓存
}
});
// 手动清除缓存
JPage.getPageManager().cache.clear();
// 清除特定页面缓存
const pageManager = JPage.getPageManager();
pageManager.clearCache('user');
```
### 页面管理器 API
通过 `JPage.getPageManager()` 获取页面管理器实例,可使用以下方法:
| 方法 | 参数 | 返回值 | 说明 |
| ---------------------- | ------ | ------------- | -------- |
| `load(route)` | Object | Promise\ | 加载页面 |
| `unload(page)` | Page | Promise\ | 卸载页面 |
| `reload(data)` | Object | Promise\ | 重新加载当前页面 |
| `clearCache(pageName)` | String | PageManager | 清除页面缓存 |
| `getCurrentPage()` | - | Page | 获取当前页面 |
| `getPreviousPage()` | - | Page | 获取上一个页面 |
| `hasPage(name)` | String | Boolean | 检查页面是否存在 |
| `getStats()` | - | Object | 获取统计信息 |
| `clear()` | - | void | 清除所有页面 |
**示例:**
```javascript
// 重新加载当前页面
await JPage.pages.reload({ params: { id: 123 } });
// 或使用页面管理器
const pageManager = JPage.getPageManager();
await pageManager.reload({
params: { id: 123 },
query: { tab: 'info' }
});
// 获取当前页面信息
const currentPage = pageManager.getCurrentPage();
console.log(currentPage.name, currentPage.params);
// 获取统计信息
const stats = pageManager.getStats();
console.log(stats);
// { loadedPages: 3, currentPage: 'home', cacheSize: 2, cacheStats: {...} }
```
***
## 🧩 组件系统
### 注册组件
```javascript
// 注册全局组件
JPage.components.register('my-button', {
name: 'my-button',
template: `
`,
data() {
return {
text: '按钮',
type: 'default'
};
},
mounted() {
this.element.addEventListener('click', () => {
console.log('按钮被点击');
});
}
});
```
### 注册组件
**JPage.components.register(name, definition, options)**
注册一个组件定义,供后续渲染使用。
**参数:**
| 参数 | 类型 | 默认值 | 说明 |
| ------------ | ------ | ---- | ------ |
| `name` | String | - | 组件名称 |
| `definition` | Object | - | 组件定义对象 |
| `options` | Object | `{}` | 组件选项 |
**组件定义对象:**
```javascript
{
name: 'my-component', // 组件名称
template: '...', // 模板字符串
data() { // 数据函数
return { message: 'Hello' };
},
mounted() { // 挂载后钩子
console.log('组件已挂载');
},
destroyed() { // 销毁前钩子
console.log('组件将销毁');
}
}
```
**示例:**
```javascript
// 注册按钮组件
JPage.components.register('my-button', {
name: 'my-button',
template: '',
data() {
return {
text: '按钮',
type: 'primary'
};
},
mounted() {
console.log('按钮组件已挂载');
}
});
// 注册后即可渲染
await JPage.components.element('my-button', { text: '点击我' });
```
### 渲染组件
**JPage.components.element(name, data, options)**
渲染组件到指定位置,返回组件实例。
**参数:**
| 参数 | 类型 | 默认值 | 说明 |
| ----------------- | -------------- | -------- | ---------- |
| `name` | String | - | 组件名称 |
| `data` | Object | `{}` | 组件数据/props |
| `options` | Object | `{}` | 渲染选项 |
| `options.target` | String/Element | `'#app'` | 目标容器 |
| `options.css` | Boolean | `false` | 是否加载CSS |
| `options.replace` | Boolean | `false` | 是否替换目标元素 |
**示例:**
```javascript
// 渲染组件到指定容器
const button = await JPage.components.element('my-button', {
text: '点击我',
type: 'primary'
}, {
target: '#button-container',
css: true
});
// 渲染组件并替换目标元素
const header = await JPage.components.element('page-header', {
title: '页面标题'
}, {
target: '#header',
replace: true
});
```
### 重新加载组件
**JPage.components.reload(name, data)**
重新加载指定组件的所有实例。
**参数:**
| 参数 | 类型 | 默认值 | 说明 |
| ------ | ------ | ---- | ------ |
| `name` | String | - | 组件名称 |
| `data` | Object | `{}` | 新的组件数据 |
**示例:**
```javascript
// 重新加载组件,更新数据
await JPage.components.reload('my-button', {
text: '新文本',
type: 'success'
});
```
### 移除组件
**JPage.components.remove(name, callback)**
移除指定组件的所有实例。
**参数:**
| 参数 | 类型 | 默认值 | 说明 |
| ---------- | -------- | --- | ------ |
| `name` | String | - | 组件名称 |
| `callback` | Function | - | 移除完成回调 |
**示例:**
```javascript
// 移除组件
JPage.components.remove('my-button', (instances) => {
console.log(`已移除 ${instances.length} 个实例`);
});
```
### 获取组件实例
**JPage.components.get(id)**
根据实例ID获取组件实例。
**参数:**
| 参数 | 类型 | 默认值 | 说明 |
| ---- | ------ | --- | ------ |
| `id` | String | - | 组件实例ID |
**返回值:**
- `Component`: 组件实例
**示例:**
```javascript
const component = JPage.components.get('component-id-123');
```
### 获取组件所有实例
**JPage.components.getInstances(name)**
获取指定名称的所有组件实例。
**参数:**
| 参数 | 类型 | 默认值 | 说明 |
| ------ | ------ | --- | ------------------ |
| `name` | String | - | 组件名称(可选,不传则返回所有实例) |
**返回值:**
- `Array`: 组件实例数组
**示例:**
```javascript
// 获取所有 my-button 实例
const buttons = JPage.components.getInstances('my-button');
// 获取所有组件实例
const allComponents = JPage.components.getInstances();
```
### 检查组件是否注册
**JPage.components.has(name)**
检查组件是否已注册。
**参数:**
| 参数 | 类型 | 默认值 | 说明 |
| ------ | ------ | --- | ---- |
| `name` | String | - | 组件名称 |
**返回值:**
- `Boolean`: 是否存在
**示例:**
```javascript
if (JPage.components.has('my-button')) {
console.log('按钮组件已注册');
}
```
### 检查实例是否存在
**JPage.components.hasInstance(id)**
检查组件实例是否存在。
**参数:**
| 参数 | 类型 | 默认值 | 说明 |
| ---- | ------ | --- | ------ |
| `id` | String | - | 组件实例ID |
**返回值:**
- `Boolean`: 是否存在
**示例:**
```javascript
if (JPage.components.hasInstance('component-id-123')) {
console.log('实例存在');
}
```
### 组件实例方法
```javascript
// 获取组件实例
const component = await JPage.components.element('my-button');
// 组件实例方法
component.$setData({ text: '新文本' }); // 更新数据
component.$setProps({ type: 'primary' }); // 更新props
component.$mount('#container'); // 挂载到容器
component.$update({ text: '更新文本' }); // 更新数据并重新渲染
component.$destroy(); // 销毁组件
component.$addChild(childComponent); // 添加子组件
component.$removeChild(childComponent); // 移除子组件
// 组件自定义事件
component.$listen('custom:event', (data) => { ... }); // 监听自定义事件
component.$unlisten('custom:event', callback); // 取消监听
component.$listenOnce('custom:event', (data) => { ... }); // 监听一次
component.$emit('custom:event', { value: '数据' }); // 触发自定义事件(支持冒泡到父组件)
```
#### 组件完整生命周期
| 钩子 | 触发时机 |
| -------------- | ------------------- |
| `beforeCreate` | 组件实例创建前 |
| `created` | 组件实例创建后 |
| `beforeRender` | 组件渲染前 |
| `rendered` | 组件渲染完成 |
| `beforeMount` | 组件挂载前 |
| `mounted` | 组件挂载到 DOM 后 |
| `beforeUpdate` | 响应式数据变更前 |
| `updated` | 响应式数据变更后 |
| `beforeDestroy`| 组件销毁前 |
| `destroyed` | 组件销毁后 |
### 组件通信
JPage 采用统一的事件通信机制,分为内部通信和外部通信:
#### 内部通信(组件/页面内部)
使用 `$on`、`$emit`、`$off`、`$once` 进行组件/页面内部通信:
```javascript
// 在组件中监听事件
this.$on('custom:event', (data) => {
console.log('事件触发:', data);
});
// 触发事件(支持事件冒泡到父组件)
this.$emit('custom:event', { value: '数据' });
// 监听一次事件
this.$once('custom:event', (data) => {
console.log('只触发一次:', data);
});
// 取消监听
this.$off('custom:event');
```
#### 外部通信(全局)
使用 `JPage.on`、`JPage.emit`、`JPage.off`、`JPage.once` 进行全局通信:
```javascript
// 全局监听事件(返回包含 off 方法的对象)
const listener = JPage.on('global:event', (data) => {
console.log('全局事件:', data);
});
// 监听一次
JPage.once('global:event', (data) => {
console.log('全局事件(一次):', data);
});
// 触发全局事件
JPage.emit('global:event', { message: '全局消息' });
// 取消监听
JPage.off('global:event', callback);
// 或使用返回的 listener 取消
listener.off();
```
**全局事件 API:**
| 方法 | 参数 | 返回值 | 说明 |
| ------------------------------- | ------------------------- | --------- | --------------- |
| `JPage.on(event, callback, options)` | String, Function, Object | Object | 监听事件,返回 `{ event, id, off }` |
| `JPage.once(event, callback, options)` | String, Function, Object | Object | 监听一次事件 |
| `JPage.emit(event, ...args)` | String, ...any | Boolean | 触发事件 |
| `JPage.off(event, callback)` | String, Function | Boolean | 取消监听 |
**options 参数:**
| 选项 | 类型 | 默认值 | 说明 |
| ---------- | ------ | ---- | ----------- |
| `priority` | Number | `0` | 监听器优先级(数值越大越先执行) |
| `context` | Object | `null` | 监听器上下文(用于 offByContext 批量清理) |
#### 数据传递方式
```javascript
// 1. 通过 props 传递数据
const childComponent = await JPage.element('child-component', {
data: { value: '初始值' }
});
// 2. 通过事件传递数据
// 父组件监听
this.$on('child:update', (child, data) => {
console.log('子组件更新:', child.name, data);
});
// 子组件触发
this.$emit('child:update', { value: '新值' });
```
> **注意**:全局状态管理需通过插件方式实现,详见 [插件开发](#插件开发) 章节。
### 组件管理器 API
通过 `JPage.getComponentManager()` 获取组件管理器实例,可使用以下方法:
| 方法 | 参数 | 返回值 | 说明 |
| ------------------------------------- | ---------------------- | ------------------------- | --------- |
| `register(name, definition, options)` | String, Object, Object | ComponentManager | 注册组件 |
| `load(name)` | String | Promise\