nyannejs
v0.0.1-dev
Published
轻量级、高性能的现代前端JavaScript框架
Maintainers
Readme
功能特性
- [x] 响应式系统 (
reactive,ref,computed,effect) - [x] 虚拟DOM和diff算法
- [x] 组件系统和生命周期
- [x] 路由系统 (
Router) - [ ] 状态管理 (
Store) - [x] 模板引擎 (
Template) - [ ] 过渡动画 (
Transition) - [x] HTTP请求 (
Http) - [x] 工具函数 (
Utils)
核心优势
- 🚀 核心体积小(<15KB)
- ⚡ 高性能虚拟DOM
- 🎯 TypeScript支持
- 🔧 灵活的架构设计
- 📚 简单易用的API
目录
快速开始
安装
通过 npm 安装
npm install nyannejs通过 yarn 安装
yarn add nyannejs通过 CDN 引入
完整版本(包含所有扩展模块):
<!-- UMD 版本 -->
<script src="https://unpkg.com/nyannejs/dist/index.min.umd.js"></script>
<!-- 或使用压缩版本 -->
<script src="https://unpkg.com/nyannejs/dist/index.min.umd.js"></script>仅包含核心模块(响应式基础框架):
<!-- UMD 版本 -->
<script src="https://unpkg.com/nyannejs/dist/core/index.min.umd.js"></script>
<!-- 或使用压缩版本 -->
<script src="https://unpkg.com/nyannejs/dist/core/index.min.umd.js"></script>引入扩展模块(如路由、状态管理等):
[!WARNING] 警告:引入扩展模块时,请确保先引入核心模块。 [!INFO] 提示:扩展模块的引入顺序没有严格要求,只要在使用前先引入即可。但为了避免潜在问题,建议先引入核心模块,再引入其他扩展模块。
<!-- 引入模块 -->
<script src="https://unpkg.com/nyannejs/dist/exts/xxxx.min.umd.js"></script>基础示例
import { createApp, h, reactive } from 'nyannejs';
// 创建应用
const app = createApp({
data() {
return {
count: 0,
message: 'Hello, NyanneJS!'
};
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
},
render() {
return h('div', { class: 'app' }, [
h('h1', {}, this.message),
h('p', {}, `Count: ${this.count}`),
h('button', {
onClick: this.decrement
}, '-'),
h('button', {
onClick: this.increment
}, '+')
]);
}
});
// 挂载应用
app.mount('#app');项目结构
nyannejs/
├── src/
│ ├── core/ # 核心模块
│ │ ├── app.ts # 应用创建
│ │ ├── component.ts # 组件系统
│ │ ├── dom.ts # DOM操作
│ │ ├── reactive.ts # 响应式系统
│ │ ├── types.ts # 类型定义
│ │ ├── utils.ts # 工具函数
│ │ ├── vdom.ts # 虚拟DOM
│ │ └── index.ts # 核心入口
│ ├── exts/ # 扩展模块
│ │ ├── router.ts # 路由模块
│ │ ├── store.ts # 状态管理
│ │ ├── template.ts # 模板引擎
│ │ ├── transition.ts # 过渡动画
│ │ ├── http.ts # HTTP请求
│ │ └── utils.ts # 扩展工具函数
│ └── index.ts # 主入口
├── dist/ # 构建输出
│ ├── index.js # ESM 格式
│ ├── index.cjs # CommonJS 格式
│ ├── index.min.umd.js # UMD 压缩版本
│ └── index.d.ts # TypeScript 类型声明
├── example/ # 示例代码
├── rollup.config.js # Rollup 配置
├── tsconfig.json # TypeScript 配置
└── package.json # 项目配置核心概念
1. 响应式系统
NyanneJS 使用基于 ES6 Proxy 的响应式系统,提供了高效、简洁的响应式编程能力。
核心特性:
- 基于 Proxy 的细粒度依赖追踪
- 自动依赖收集和触发更新
- 支持嵌套对象的深度响应式
- 提供计算属性和监听器
- 支持只读和浅层响应式
2. 虚拟DOM
虚拟DOM是NyanneJS性能优化的核心,通过diff算法最小化DOM操作。
核心特性:
- 高效的diff算法
- 扁平化子节点处理
- Key-based列表更新优化
- HTML字符串直接渲染支持
3. 组件系统
组件化开发是NyanneJS的核心思想,支持函数组件和对象组件。
核心特性:
- 支持函数式和对象式组件定义
- 组件实例和生命周期管理
- Props、Data、Methods分离
- 计算属性和监听器支持
- Keep-alive组件缓存
4. 应用实例
通过 createApp 创建应用实例,管理整个应用的生命周期。
响应式系统
reactive
创建响应式对象,所有嵌套属性都会变成响应式的。
import { reactive } from 'nyannejs';
const state = reactive({
count: 0,
user: {
name: 'John',
age: 30
}
});
// 读取属性(自动收集依赖)
console.log(state.count); // 0
console.log(state.user.name); // 'John'
// 修改属性(自动触发更新)
state.count++;
state.user.name = 'Jane';特性:
- 自动解包嵌套的ref对象
- 深度响应式,所有嵌套属性都是响应式的
- 支持 Symbol 键
ref
创建响应式引用,通常用于基本类型值。
import { ref, effect } from 'nyannejs';
const count = ref(0);
// 访问值
console.log(count.value); // 0
// 修改值
count.value++;
// 在 effect 中使用
effect(() => {
console.log('Count changed:', count.value);
});特性:
- 通过
.value访问和修改值 - 在 reactive 对象中自动解包
- 支持任意类型值
computed
创建计算属性,基于其他响应式数据计算派生值。
import { reactive, computed, effect } from 'nyannejs';
const state = reactive({
firstName: 'John',
lastName: 'Doe'
});
const fullName = computed(() => {
return `${state.firstName} ${state.lastName}`;
});
console.log(fullName.value); // 'John Doe'
// 修改依赖数据
state.firstName = 'Jane';
console.log(fullName.value); // 'Jane Doe'特性:
- 惰性计算,只在访问时计算
- 结果缓存,避免重复计算
- 自动追踪依赖,依赖变化时重新计算
effect
创建副作用函数,在响应式数据变化时自动执行。
import { reactive, effect } from 'nyannejs';
const state = reactive({ count: 0 });
effect(() => {
console.log('Count is:', state.count);
});
state.count++; // 输出: Count is: 1
state.count++; // 输出: Count is: 2特性:
- 自动收集依赖
- 依赖变化时自动重新执行
- 批量更新,避免频繁执行
toRef 和 toRefs
将响应式对象的属性转换为 ref。
import { reactive, toRef, toRefs } from 'nyannejs';
const state = reactive({
name: 'John',
age: 30
});
// 转换单个属性
const nameRef = toRef(state, 'name');
console.log(nameRef.value); // 'John'
// 转换所有属性
const { name, age } = toRefs(state);
console.log(name.value, age.value); // 'John' 30使用场景:
- 解构响应式对象时保持响应性
- 将响应式对象的属性传递给函数
- 保持对源对象的引用关系
shallowReactive 和 shallowRef
创建浅层响应式对象,只追踪对象自身属性的变化。
import { shallowReactive, shallowRef } from 'nyannejs';
// 浅层响应式对象
const state = shallowReactive({
count: 0,
nested: { value: 1 }
});
state.count++; // 触发更新
state.nested.value++; // 不会触发更新
// 浅层响应式引用
const obj = shallowRef({ count: 0 });
obj.value.count++; // 不会触发更新
obj.value = { count: 1 }; // 触发更新使用场景:
- 优化大型对象的性能
- 避免不必要的深度响应式转换
- 只关心对象引用变化的情况
readonly
创建只读响应式对象。
import { reactive, readonly } from 'nyannejs';
const original = reactive({ count: 0 });
const copy = readonly(original);
console.log(copy.count); // 0
copy.count++; // 警告: 只读属性不可修改使用场景:
- 防止意外修改
- 作为props传递给子组件
- 保护关键数据
虚拟DOM
h 函数
创建虚拟DOM节点(VNode)。
import { h } from 'nyannejs';
// 基本用法
const vnode1 = h('div', { class: 'app' }, 'Hello World');
// 嵌套子节点
const vnode2 = h('div', { class: 'app' }, [
h('h1', {}, 'Title'),
h('p', {}, 'Content')
]);
// 组件节点
const Component = () => h('div', {}, 'Component');
const vnode3 = h(Component, { prop: 'value' });
// HTML 字符串节点
const vnode4 = h('div', {}, '<span class="highlight">Text</span>');参数说明:
type: 节点类型(标签名、组件函数、'TEXT'、'HTML')props: 属性对象(可选)children: 子节点(可选)
支持的子节点类型:
- 字符串: 文本节点
- VNode: 虚拟DOM节点
- 数组: 多个子节点
- HTML字符串: 自动解析为HTML节点
diff 算法
虚拟DOM的diff算法用于比较新旧VNode的差异。
核心策略:
- 同层比较,不跨层比较
- 基于key的节点识别
- 类型不同则直接替换
- 递归比较子节点
补丁类型:
REPLACE: 替换节点PROPS: 更新属性TEXT: 更新文本内容CHILDREN: 更新子节点
示例:
import { h, diff } from 'nyannejs';
const oldVnode = h('div', { class: 'old' }, 'old text');
const newVnode = h('div', { class: 'new' }, 'new text');
const patches = diff(oldVnode, newVnode);
// patches 包含差异信息,用于更新DOM组件系统
defineComponent
定义组件配置。
import { defineComponent, h } from 'nyannejs';
const Counter = defineComponent({
name: 'Counter',
props: {
initialCount: {
type: Number,
default: 0
}
},
data() {
return {
count: this.initialCount || 0
};
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
},
computed: {
doubleCount() {
return this.count * 2;
}
},
render() {
return h('div', { class: 'counter' }, [
h('h2', {}, `Count: ${this.count}`),
h('p', {}, `Double: ${this.doubleCount}`),
h('button', { onClick: this.decrement }, '-'),
h('button', { onClick: this.increment }, '+')
]);
}
});函数组件
使用函数定义组件。
import { h } from 'nyannejs';
const Hello = (props) => {
return h('div', {}, `Hello, ${props.name}!`);
};
// 使用
const app = createApp({
render() {
return h('div', {}, [
h(Hello, { name: 'World' })
]);
}
});组件通信
Props 父传子
const Child = defineComponent({
props: ['message'],
render() {
return h('p', {}, this.message);
}
});
const Parent = defineComponent({
data() {
return {
msg: 'Hello from Parent'
};
},
render() {
return h(Child, { message: this.msg });
}
});Events 子传父
const Child = defineComponent({
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
this.$emit('update', this.count);
}
},
render() {
return h('button', {
onClick: this.increment
}, `Count: ${this.count}`);
}
});
const Parent = defineComponent({
data() {
return {
childCount: 0
};
},
render() {
return h('div', {}, [
h('p', {}, `Child count: ${this.childCount}`),
h(Child, {
onUpdate: (count) => {
this.childCount = count;
}
})
]);
}
});$refs 引用
const MyComponent = defineComponent({
data() {
return {
message: 'Hello'
};
},
methods: {
focusInput() {
this.$refs.inputRef?.focus();
}
},
render() {
return h('div', {}, [
h('input', {
ref: 'inputRef',
type: 'text'
}),
h('button', {
onClick: this.focusInput
}, 'Focus')
]);
}
});$update 手动更新
手动触发组件更新。
const MyComponent = defineComponent({
data() {
return {
list: [1, 2, 3]
};
},
methods: {
addItem() {
this.list.push(this.list.length + 1);
this.$update(); // 手动触发更新
}
},
render() {
return h('div', {}, [
h('ul', {}, this.list.map(item =>
h('li', {}, item.toString())
)),
h('button', {
onClick: this.addItem
}, 'Add Item')
]);
}
});Keep-alive
缓存组件实例,避免重复创建。
const CachedComponent = defineComponent({
keepAlive: true, // 启用缓存
data() {
return {
count: 0
};
},
render() {
return h('div', {}, `Count: ${this.count}`);
}
});生命周期
生命周期钩子
组件的生命周期钩子:
| 钩子名称 | 调用时机 |
|---------|---------|
| beforeCreate | 组件实例创建之前 |
| created | 组件实例创建完成 |
| beforeMount | 组件挂载到DOM之前 |
| mounted | 组件挂载到DOM之后 |
| beforeUpdate | 组件更新之前 |
| updated | 组件更新之后 |
| beforeUnmount | 组件卸载之前 |
| unmounted | 组件卸载之后 |
使用示例
const LifecycleComponent = defineComponent({
data() {
return {
count: 0
};
},
beforeCreate() {
console.log('beforeCreate: 实例创建前');
},
created() {
console.log('created: 实例已创建', this.count);
},
beforeMount() {
console.log('beforeMount: 即将挂载');
},
mounted() {
console.log('mounted: 已挂载到DOM');
},
beforeUpdate() {
console.log('beforeUpdate: 即将更新');
},
updated() {
console.log('updated: 已更新');
},
beforeUnmount() {
console.log('beforeUnmount: 即将卸载');
},
unmounted() {
console.log('unmounted: 已卸载');
},
methods: {
increment() {
this.count++;
}
},
render() {
return h('div', {}, [
h('p', {}, `Count: ${this.count}`),
h('button', {
onClick: this.increment
}, 'Increment')
]);
}
});生命周期流程图
创建阶段:
beforeCreate → created → beforeMount → mounted
更新阶段:
beforeUpdate → updated
卸载阶段:
beforeUnmount → unmounted路由系统
createRouter
创建路由实例。
import { createRouter } from 'nyannejs/Router';
import Home from './views/Home';
import About from './views/About';
const router = createRouter({
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
],
mode: 'hash', // 或 'history'
base: '/app'
});路由配置
基础路由
const routes = [
{
path: '/',
component: Home
},
{
path: '/about',
component: About
}
];动态路由
const routes = [
{
path: '/user/:id',
component: UserDetail
}
];
// 组件中访问参数
const UserDetail = (props) => {
const { id } = props.params;
return h('div', {}, `User ID: ${id}`);
};嵌套路由
const routes = [
{
path: '/admin',
component: AdminLayout,
children: [
{
path: 'dashboard',
component: Dashboard
},
{
path: 'settings',
component: Settings
}
]
}
];路由守卫
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (isAuthenticated()) {
next();
} else {
next('/login');
}
}
}
];路由导航
编程式导航
// push 导航
router.push('/home');
router.push({ path: '/user', query: { id: 123 } });
// replace 导航
router.replace('/login');
// 后退
router.back();
// 前进
router.forward();声明式导航(需自行实现)
const RouterLink = defineComponent({
props: ['to'],
methods: {
handleClick(e) {
e.preventDefault();
window.$router.push(this.to);
}
},
render() {
return h('a', {
href: `#${this.to}`,
onClick: this.handleClick
}, this.$slots?.default?.());
}
});全局守卫
// 前置守卫
router.beforeEach((to, from, next) => {
console.log('Navigating from', from.path, 'to', to.path);
next();
});
// 后置钩子
router.afterEach((to, from) => {
console.log('Navigation completed to', to.path);
});
// 错误处理
router.onError((err, to, from) => {
console.error('Navigation error:', err);
});路由信息
// 获取当前路由
const currentRoute = router.getCurrentRoute();
console.log(currentRoute.path);
console.log(currentRoute.params);
console.log(currentRoute.query);
console.log(currentRoute.meta);路由模式
Hash 模式
const router = createRouter({
routes,
mode: 'hash'
});
// URL: #/homeHistory 模式
const router = createRouter({
routes,
mode: 'history'
});
// URL: /home
// 需要服务器配置支持状态管理
[!WARNING] 警告:此扩展未完成开发。
createStore
创建状态管理store。
import { createStore } from 'nyannejs/Store';
const store = createStore({
state: {
count: 0,
user: null
},
getters: {
doubleCount: (state) => state.count * 2,
isLoggedIn: (state) => !!state.user
},
mutations: {
increment(state) {
state.count++;
},
setUser(state, user) {
state.user = user;
}
},
actions: {
async login({ commit }, credentials) {
const user = await api.login(credentials);
commit('setUser', user);
},
asyncIncrement({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
}
}
});State
访问状态:
// 直接访问
console.log(store.state.count);
// 在组件中使用
const MyComponent = defineComponent({
data() {
return {
localCount: store.state.count
};
},
render() {
return h('div', {}, `Count: ${this.localCount}`);
}
});Getters
访问计算属性:
// 直接访问
console.log(store.getters.doubleCount);
console.log(store.getters.isLoggedIn);
// 在组件中使用
const MyComponent = defineComponent({
data() {
return {
doubleCount: store.getters.doubleCount
};
},
render() {
return h('div', {}, `Double: ${this.doubleCount}`);
}
});Mutations
提交mutation修改状态:
// 直接提交
store.commit('increment');
store.commit('setUser', { name: 'John', id: 1 });
// 在组件中使用
const MyComponent = defineComponent({
methods: {
handleClick() {
this.$store.commit('increment');
}
},
render() {
return h('button', {
onClick: this.handleClick
}, 'Increment');
}
});Actions
分发action执行异步操作:
// 直接分发
store.dispatch('login', { username: 'admin', password: '123456' });
store.dispatch('asyncIncrement');
// 在组件中使用
const MyComponent = defineComponent({
methods: {
async handleLogin() {
await this.$store.dispatch('login', {
username: 'admin',
password: '123456'
});
}
},
render() {
return h('button', {
onClick: this.handleLogin
}, 'Login');
}
});模块化
定义模块
const userModule = {
namespaced: true,
state: {
currentUser: null,
token: null
},
getters: {
isAuthenticated: (state) => !!state.token
},
mutations: {
SET_USER(state, user) {
state.currentUser = user;
},
SET_TOKEN(state, token) {
state.token = token;
}
},
actions: {
async login({ commit }, credentials) {
const { user, token } = await api.login(credentials);
commit('SET_USER', user);
commit('SET_TOKEN', token);
}
}
};注册模块
const store = createStore({
state: {
count: 0
},
modules: {
user: userModule,
product: productModule
}
});使用模块
// 访问模块状态
console.log(store.state.user.currentUser);
// 调用模块mutation
store.commit('user/SET_USER', user);
// 分发模块action
store.dispatch('user/login', credentials);
// 访问模块getter
console.log(store.getters['user/isAuthenticated']);模板引擎
compile
编译模板字符串为渲染函数。
import { Template } from 'nyannejs/Template';
const templateStr = `
<div class="app">
<h1>{{ title }}</h1>
<p>{{ message }}</p>
</div>
`;
const render = Template.compile(templateStr);
const vdom = render(
{ title: 'Hello', message: 'World' },
h
);模板语法
文本插值
const template = `
<div>
<p>{{ message }}</p>
</div>
`;属性绑定
const template = `
<div :class="className" :id="itemId">
Content
</div>
`;事件绑定
const template = `
<button @click="handleClick">Click</button>
<input @input="handleInput">
`;条件渲染
const template = `
<div>
<p v-if="show">Visible</p>
<p v-else>Hidden</p>
</div>
`;列表渲染
const template = `
<ul>
<li v-for="item in items">{{ item.name }}</li>
</ul>
`;插槽
const template = `
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
`;Fetch 组件
NyanneJS 提供了一个功能强大的 HTTP 请求库,基于 XMLHttpRequest 封装,支持拦截器、超时控制、响应类型配置等功能。
创建请求实例
import { http, createRequest } from 'nyannejs/Http';
// 使用默认实例
http.get('/api/users');
// 创建自定义实例
const api = createRequest({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Authorization': 'Bearer token'
}
});
api.get('/users');基础方法
GET 请求
// 简单 GET 请求
http.get('/api/users')
.then(response => {
console.log('Users:', response.data);
})
.catch(error => {
console.error('Error:', error);
});
// 带查询参数
http.get('/api/users', {
params: {
page: 1,
limit: 10,
keyword: 'john'
}
})
.then(response => {
console.log('Page 1:', response.data);
});POST 请求
// 发送 JSON 数据
http.post('/api/users', {
name: 'John Doe',
email: '[email protected]',
age: 30
})
.then(response => {
console.log('Created:', response.data);
});
// 自定义配置
http.post('/api/users', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
timeout: 15000
});PUT 请求
http.put('/api/users/1', {
name: 'Jane Doe',
email: '[email protected]'
})
.then(response => {
console.log('Updated:', response.data);
});DELETE 请求
http.delete('/api/users/1')
.then(response => {
console.log('Deleted:', response.data);
});PATCH 请求
http.patch('/api/users/1', {
age: 31
})
.then(response => {
console.log('Patched:', response.data);
});请求配置
HttpConfig 接口
interface HttpConfig {
url: string; // 请求URL
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; // 请求方法
data?: any; // 请求体数据
params?: Record<string, any>; // URL查询参数
headers?: Record<string, string>; // 请求头
timeout?: number; // 超时时间(毫秒)
responseType?: 'json' | 'text' | 'blob' | 'arraybuffer'; // 响应类型
baseURL?: string; // 基础URL
}使用示例
http.get('/api/data', {
// 查询参数
params: {
page: 1,
size: 20
},
// 自定义请求头
headers: {
'Authorization': 'Bearer token',
'Accept': 'application/json'
},
// 响应类型
responseType: 'json',
// 超时设置
timeout: 10000
});响应对象
HttpResponse 接口
interface HttpResponse {
data: any; // 响应数据
status: number; // HTTP状态码
statusText: string; // 状态文本
headers: Record<string, string>; // 响应头
config: HttpConfig; // 请求配置
}使用示例
http.get('/api/users')
.then(response => {
console.log('Status:', response.status); // 200
console.log('Status Text:', response.statusText); // 'OK'
console.log('Data:', response.data); // 响应数据
console.log('Headers:', response.headers); // 响应头
console.log('Config:', response.config); // 请求配置
});拦截器
拦截器可以在请求发送前或响应返回后进行处理。
请求拦截器
// 添加请求拦截器
http.addInterceptor({
request: (config) => {
// 自动添加认证token
const token = localStorage.getItem('token');
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
// 添加时间戳防止缓存
config.params = {
...config.params,
_t: Date.now()
};
console.log('Request:', config);
return config;
}
});响应拦截器
// 添加响应拦截器
http.addInterceptor({
response: (response) => {
// 统一处理响应数据
if (response.data.code !== 200) {
// 业务错误处理
console.error('Business Error:', response.data.message);
return Promise.reject(response.data);
}
// 只返回业务数据
return response.data.data;
}
});错误拦截器
// 添加错误拦截器
http.addInterceptor({
error: (error) => {
console.error('Request Error:', error);
// 处理不同类型的错误
if (error.status === 401) {
// 未授权,跳转登录
window.location.href = '/login';
} else if (error.status === 404) {
console.error('Resource not found');
} else if (error.status === 500) {
console.error('Server error');
} else if (error.status === 408) {
console.error('Request timeout');
}
return Promise.reject(error);
}
});多个拦截器
// 添加多个拦截器,按添加顺序执行
http.addInterceptor({
request: (config) => {
console.log('First interceptor');
return config;
}
});
http.addInterceptor({
request: (config) => {
console.log('Second interceptor');
return config;
}
});
http.get('/api/data');
// 输出:
// First interceptor
// Second interceptor实例配置
设置基础URL
const api = createRequest({
baseURL: 'https://api.example.com'
});
// 请求会自动拼接基础URL
api.get('/users'); // 实际请求: https://api.example.com/users设置超时时间
const api = createRequest({
timeout: 5000 // 5秒超时
});
api.get('/users'); // 5秒未响应则超时设置默认请求头
const api = createRequest({
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
// 所有请求都会带上这些默认请求头
api.post('/users', { name: 'John' });链式配置
const api = new HttpRequest()
.setBaseURL('https://api.example.com')
.setTimeout(10000)
.setDefaultHeaders({
'Content-Type': 'application/json'
})
.addInterceptor({
request: (config) => {
console.log('Request:', config);
return config;
}
});
api.get('/users');响应类型
JSON 响应(默认)
http.get('/api/data', {
responseType: 'json'
})
.then(response => {
// response.data 是解析后的 JSON 对象
console.log(response.data);
});文本响应
http.get('/api/text', {
responseType: 'text'
})
.then(response => {
// response.data 是字符串
console.log(response.data);
});Blob 响应
http.get('/api/file/download', {
responseType: 'blob'
})
.then(response => {
// response.data 是 Blob 对象
const url = URL.createObjectURL(response.data);
const a = document.createElement('a');
a.href = url;
a.download = 'file.pdf';
a.click();
URL.revokeObjectURL(url);
});ArrayBuffer 响应
http.get('/api/binary', {
responseType: 'arraybuffer'
})
.then(response => {
// response.data 是 ArrayBuffer
console.log(response.data);
});在组件中使用
import { http } from 'nyannejs/Http';
const UserList = defineComponent({
data() {
return {
users: [],
loading: false,
error: null
};
},
methods: {
async fetchUsers() {
this.loading = true;
this.error = null;
try {
const response = await http.get('/api/users', {
params: {
page: 1,
limit: 10
}
});
this.users = response.data;
} catch (err) {
this.error = err.message || 'Failed to fetch users';
} finally {
this.loading = false;
}
},
async createUser(userData) {
try {
const response = await http.post('/api/users', userData);
this.users.push(response.data);
return response.data;
} catch (err) {
console.error('Failed to create user:', err);
throw err;
}
},
async deleteUser(userId) {
try {
await http.delete(`/api/users/${userId}`);
this.users = this.users.filter(user => user.id !== userId);
} catch (err) {
console.error('Failed to delete user:', err);
throw err;
}
}
},
mounted() {
this.fetchUsers();
},
render() {
if (this.loading) return h('div', {}, 'Loading...');
if (this.error) return h('div', {}, `Error: ${this.error}`);
return h('div', {}, [
h('ul', {},
this.users.map(user =>
h('li', { key: user.id }, [
h('span', {}, user.name),
h('button', {
onClick: () => this.deleteUser(user.id)
}, 'Delete')
])
)
)
]);
}
});高级用法
请求取消(使用 AbortController)
let abortController = null;
async function fetchData() {
// 取消之前的请求
if (abortController) {
abortController.abort();
}
abortController = new AbortController();
try {
const response = await fetch('/api/data', {
signal: abortController.signal
});
const data = await response.json();
return data;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was cancelled');
}
throw error;
}
}
// 调用
fetchData();
// 取消请求
if (abortController) {
abortController.abort();
}请求重试
async function retryRequest(requestFn, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await requestFn();
} catch (error) {
lastError = error;
console.log(`Retry ${i + 1}/${maxRetries}`);
// 等待一段时间后重试
if (i < maxRetries - 1) {
await new Promise(resolve =>
setTimeout(resolve, 1000 * (i + 1))
);
}
}
}
throw lastError;
}
// 使用
retryRequest(() => http.get('/api/unstable-endpoint'))
.then(response => console.log('Success:', response))
.catch(error => console.error('Failed after retries:', error));并发请求
// 使用 Promise.all 并发请求
Promise.all([
http.get('/api/users'),
http.get('/api/posts'),
http.get('/api/comments')
])
.then(([users, posts, comments]) => {
console.log('Users:', users.data);
console.log('Posts:', posts.data);
console.log('Comments:', comments.data);
})
.catch(error => {
console.error('One or more requests failed:', error);
});请求队列
class RequestQueue {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.running = 0;
this.queue = [];
}
add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.next();
});
}
next() {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.running++;
const { requestFn, resolve, reject } = this.queue.shift();
requestFn()
.then(resolve)
.catch(reject)
.finally(() => {
this.running--;
this.next();
});
}
}
// 使用
const queue = new RequestQueue(3);
queue.add(() => http.get('/api/data1'));
queue.add(() => http.get('/api/data2'));
queue.add(() => http.get('/api/data3'));
queue.add(() => http.get('/api/data4'));
queue.add(() => http.get('/api/data5'));最佳实践
1. 创建专用的 API 实例
// api.js
export const api = createRequest({
baseURL: process.env.API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// 添加拦截器
api.addInterceptor({
request: (config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error: (error) => {
if (error.status === 401) {
// 清除token并跳转登录
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
});
// 定义API方法
export const userApi = {
list: (params) => api.get('/users', { params }),
get: (id) => api.get(`/users/${id}`),
create: (data) => api.post('/users', data),
update: (id, data) => api.put(`/users/${id}`, data),
delete: (id) => api.delete(`/users/${id}`)
};2. 在组件中使用 API
import { userApi } from './api';
const UserComponent = defineComponent({
data() {
return {
users: []
};
},
methods: {
async loadUsers() {
try {
const response = await userApi.list({ page: 1 });
this.users = response.data;
} catch (error) {
console.error('Failed to load users:', error);
}
}
},
mounted() {
this.loadUsers();
},
render() {
return h('ul', {},
this.users.map(user =>
h('li', { key: user.id }, user.name)
)
);
}
});3. 错误处理
// 创建统一的错误处理函数
function handleRequestError(error) {
if (error.status) {
// HTTP 错误
switch (error.status) {
case 400:
console.error('Bad Request:', error.data);
break;
case 401:
console.error('Unauthorized');
break;
case 403:
console.error('Forbidden');
break;
case 404:
console.error('Not Found');
break;
case 500:
console.error('Server Error');
break;
default:
console.error('HTTP Error:', error.status);
}
} else if (error.request) {
// 网络错误
console.error('Network Error');
} else {
// 其他错误
console.error('Error:', error.message);
}
return Promise.reject(error);
}
// 使用
http.get('/api/data')
.then(response => {
console.log('Success:', response.data);
})
.catch(handleRequestError);4. 请求缓存
class RequestCache {
constructor() {
this.cache = new Map();
this.maxAge = 5 * 60 * 1000; // 5分钟
}
set(key, value) {
this.cache.set(key, {
value,
timestamp: Date.now()
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.maxAge) {
this.cache.delete(key);
return null;
}
return item.value;
}
}
const cache = new RequestCache();
async function cachedGet(url, config) {
const cacheKey = url + JSON.stringify(config?.params || {});
const cached = cache.get(cacheKey);
if (cached) {
return cached;
}
const response = await http.get(url, config);
cache.set(cacheKey, response);
return response;
}
// 使用
cachedGet('/api/users', { params: { page: 1 } });API 参考
HttpRequest 类
class HttpRequest {
// 设置基础URL
setBaseURL(url: string): this;
// 设置超时时间
setTimeout(timeout: number): this;
// 设置默认请求头
setDefaultHeaders(headers: Record<string, string>): this;
// 添加拦截器
addInterceptor(interceptor: HttpInterceptor): this;
// 发送请求
request(config: HttpConfig): Promise<HttpResponse>;
// GET 请求
get(url: string, config?: Partial<HttpConfig>): Promise<HttpResponse>;
// POST 请求
post(url: string, data?: any, config?: Partial<HttpConfig>): Promise<HttpResponse>;
// PUT 请求
put(url: string, data?: any, config?: Partial<HttpConfig>): Promise<HttpResponse>;
// DELETE 请求
delete(url: string, config?: Partial<HttpConfig>): Promise<HttpResponse>;
// PATCH 请求
patch(url: string, data?: any, config?: Partial<HttpConfig>): Promise<HttpResponse>;
}HttpInterceptor 接口
interface HttpInterceptor {
// 请求拦截器
request?: (config: HttpConfig) => HttpConfig | Promise<HttpConfig>;
// 响应拦截器
response?: (response: any) => any | Promise<any>;
// 错误拦截器
error?: (error: any) => any | Promise<any>;
}默认实例
// 全局默认实例
http.get('/api/data');
http.post('/api/data', { key: 'value' });
// 创建自定义实例
const api = createRequest({ baseURL: 'https://api.example.com' });
api.get('/data');Fetch 数据获取标签
<fetch> 标签用于在模板中直接发起异步 HTTP 请求并获取数据。
基本语法
<fetch url="/api/data" method="GET" name="result">
<!-- 数据加载成功后渲染的内容 -->
<div>{{ result }}</div>
</fetch>属性说明
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| url | string | - | 是 | 请求的 URL 地址 |
| method | string | 'GET' | 否 | HTTP 请求方法 (GET/POST/PUT/DELETE) |
| name | string | '$fetch' | 否 | 存储响应数据的变量名 |
使用示例
基础 GET 请求
const UserList = defineComponent({
template: Template.compile(`
<div class="user-list">
<h1>用户列表</h1>
<fetch url="/api/users" method="GET" name="users">
<ul>
<li v-for="user in users">{{ user.name }}</li>
</ul>
</fetch>
</div>
`),
data() {
return {
users: []
};
}
});使用不同的变量名
const ProductList = defineComponent({
template: Template.compile(`
<fetch url="/api/products" method="GET" name="products">
<div class="products">
<h2>商品列表</h2>
<div v-for="product in products" :key="product.id">
<h3>{{ product.name }}</h3>
<p>价格: ¥{{ product.price }}</p>
</div>
</div>
</fetch>
`),
data() {
return {
products: []
};
}
});多个 fetch 请求
const Dashboard = defineComponent({
template: Template.compile(`
<div class="dashboard">
<fetch url="/api/users" method="GET" name="users">
<div class="users">
<h2>用户 ({{ users.length }})</h2>
<ul>
<li v-for="user in users">{{ user.name }}</li>
</ul>
</div>
</fetch>
<fetch url="/api/posts" method="GET" name="posts">
<div class="posts">
<h2>文章 ({{ posts.length }})</h2>
<ul>
<li v-for="post in posts">{{ post.title }}</li>
</ul>
</div>
</fetch>
<fetch url="/api/comments" method="GET" name="comments">
<div class="comments">
<h2>评论 ({{ comments.length }})</h2>
<ul>
<li v-for="comment in comments">{{ comment.content }}</li>
</ul>
</div>
</fetch>
</div>
`),
data() {
return {
users: [],
posts: [],
comments: []
};
}
});嵌套使用
const UserProfile = defineComponent({
template: Template.compile(`
<div class="profile">
<fetch url="/api/user/1" method="GET" name="user">
<h1>{{ user.name }}</h1>
<p>{{ user.bio }}</p>
<fetch :url="'/api/user/' + user.id + '/posts'" method="GET" name="posts">
<h2>TA的文章 ({{ posts.length }})</h2>
<ul>
<li v-for="post in posts">
<h3>{{ post.title }}</h3>
<p>{{ post.content }}</p>
</li>
</ul>
</fetch>
</fetch>
</div>
`),
data() {
return {
user: null,
posts: []
};
}
});与 v-if 结合使用
const DataComponent = defineComponent({
template: Template.compile(`
<div class="data">
<fetch url="/api/status" method="GET" name="status">
<div v-if="status.active">
<p>系统正常运行</p>
<fetch url="/api/data" method="GET" name="data">
<ul>
<li v-for="item in data">{{ item.name }}</li>
</ul>
</fetch>
</div>
<div v-else>
<p>系统维护中</p>
</div>
</fetch>
</div>
`),
data() {
return {
status: null,
data: []
};
}
});POST 请求示例
const FormComponent = defineComponent({
template: Template.compile(`
<div class="form">
<fetch url="/api/submit" method="POST" name="response">
<div v-if="response.success">
<p>提交成功!</p>
<p>消息: {{ response.message }}</p>
</div>
<div v-else>
<p>提交失败: {{ response.error }}</p>
</div>
</fetch>
</div>
`),
data() {
return {
response: null
};
},
methods: {
async submitData() {
await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'John',
email: '[email protected]'
})
});
}
}
});工作原理
<fetch> 标签会在编译时转换为以下 JavaScript 代码:
result = await fetch('/api/data', { method: 'GET' }).then(r => r.json());
return [/* children content */];编译后的函数会在组件渲染时自动执行,获取数据并存储到指定的变量名中。
注意事项
- 异步执行: fetch 请求是异步的,数据加载完成前可能显示空白
- 数据类型: 默认解析为 JSON,确保 API 返回有效的 JSON 数据
- 错误处理: 当前版本需要自行处理请求错误
- 命名冲突: 确保不同的 fetch 使用不同的
name属性避免覆盖 - 组件生命周期: fetch 在组件首次渲染时执行一次,不会自动重新请求
最佳实践
1. 提供加载状态
const LoadingComponent = defineComponent({
template: Template.compile(`
<div class="loading-container">
<fetch url="/api/data" method="GET" name="data">
<div v-if="data && data.length > 0">
<ul>
<li v-for="item in data">{{ item.name }}</li>
</ul>
</div>
<div v-else>
<p>数据加载中...</p>
</div>
</fetch>
</div>
`),
data() {
return {
data: null
};
}
});2. 错误处理
const SafeFetchComponent = defineComponent({
template: Template.compile(`
<div class="safe-fetch">
<fetch url="/api/data" method="GET" name="data">
<div v-if="data">
<ul>
<li v-for="item in data">{{ item.name }}</li>
</ul>
</div>
<div v-else-if="data === null">
<p>加载中...</p>
</div>
<div v-else>
<p>加载失败</p>
</div>
</fetch>
</div>
`),
data() {
return {
data: null
};
}
});3. 数据缓存
const CachedFetchComponent = defineComponent({
template: Template.compile(`
<div class="cached-data">
<fetch url="/api/config" method="GET" name="config">
<div>
<p>应用名称: {{ config.appName }}</p>
<p>版本: {{ config.version }}</p>
</div>
</fetch>
</div>
`),
data() {
return {
config: null
};
},
async beforeCreate() {
// 可以在这里手动缓存数据
if (!this.config) {
try {
const response = await fetch('/api/config');
this.config = await response.json();
} catch (error) {
console.error('Failed to fetch config:', error);
}
}
}
});完整示例
import { defineComponent } from 'nyannejs';
import { Template } from 'nyannejs/Template';
const Dashboard = defineComponent({
template: Template.compile(`
<div class="dashboard">
<header>
<h1>NyanneJS Dashboard</h1>
<p>展示 fetch 标签的使用</p>
</header>
<section class="section">
<h2>用户列表</h2>
<fetch url="/api/users" method="GET" name="users">
<div v-if="users && users.length > 0">
<p>共 {{ users.length }} 位用户</p>
<ul class="user-list">
<li v-for="user in users" :key="user.id">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</li>
</ul>
</div>
<div v-else>
<p>暂无用户数据</p>
</div>
</fetch>
</section>
<section class="section">
<h2>文章列表</h2>
<fetch url="/api/posts" method="GET" name="posts">
<div v-if="posts && posts.length > 0">
<p>共 {{ posts.length }} 篇文章</p>
<div class="post-list">
<div v-for="post in posts" :key="post.id" class="post-card">
<h3>{{ post.title }}</h3>
<p>{{ post.excerpt }}</p>
<small>作者: {{ post.author }}</small>
</div>
</div>
</div>
<div v-else>
<p>暂无文章数据</p>
</div>
</fetch>
</section>
<section class="section">
<h2>统计数据</h2>
<fetch url="/api/stats" method="GET" name="stats">
<div v-if="stats">
<div class="stat-card">
<h3>总访问量</h3>
<p class="stat-value">{{ stats.visits }}</p>
</div>
<div class="stat-card">
<h3>总用户数</h3>
<p class="stat-value">{{ stats.users }}</p>
</div>
<div class="stat-card">
<h3>总文章数</h3>
<p class="stat-value">{{ stats.posts }}</p>
</div>
</div>
</fetch>
</section>
</div>
`),
data() {
return {
users: null,
posts: null,
stats: null
};
},
mounted() {
console.log('Dashboard mounted');
}
});在组件中使用模板
过渡动画
[!WARNING] 警告:过渡动画功能未完成开发。
Transition 组件
使用Transition组件包裹需要动画的元素。
import { Transition } from 'nyannejs/Transition';
const AnimatedComponent = defineComponent({
data() {
return {
show: true
};
},
methods: {
toggle() {
this.show = !this.show;
}
},
render() {
return h('div', {}, [
h(Transition, {
name: 'fade',
mode: 'out-in'
}, [
this.show ? h('div', { class: 'content' }, 'Hello') : null
]),
h('button', {
onClick: this.toggle
}, 'Toggle')
]);
}
});CSS 过渡
/* fade-enter: 进入前 */
.fade-enter {
opacity: 0;
}
/* fade-enter-active: 进入中 */
.fade-enter-active {
opacity: 1;
transition: opacity 0.3s ease;
}
/* fade-leave: 离开前 */
.fade-leave {
opacity: 1;
}
/* fade-leave-active: 离开中 */
.fade-leave-active {
opacity: 0;
transition: opacity 0.3s ease;
}过渡模式
in-out: 新元素先进入,旧元素后离开out-in: 旧元素先离开,新元素后进入(默认)
h(Transition, {
name: 'fade',
mode: 'out-in'
}, children)HTTP请求
Http 扩展
提供HTTP请求功能。
import { Http } from 'nyannejs/Http';
// GET 请求
Http.get('/api/users')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error(error);
});
// POST 请求
Http.post('/api/users', {
name: 'John',
email: '[email protected]'
})
.then(response => {
console.log(response.data);
});
// PUT 请求
Http.put('/api/users/1', {
name: 'Jane'
})
.then(response => {
console.log(response.data);
});
// DELETE 请求
Http.delete('/api/users/1')
.then(response => {
console.log(response.data);
});请求配置
Http.get('/api/users', {
params: {
page: 1,
limit: 10
},
headers: {
'Authorization': 'Bearer token'
}
});拦截器
// 请求拦截器
Http.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${token}`;
return config;
});
// 响应拦截器
Http.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
// 处理未授权
}
return Promise.reject(error);
}
);工具函数
Utils 扩展
提供常用工具函数。
import { Utils } from 'nyannejs/Utils';
// 深度克隆
const cloned = Utils.deepClone(original);
// 深度比较
const isEqual = Utils.deepEqual(obj1, obj2);
// 扁平化数组
const flattened = Utils.flatten([1, [2, [3, 4]]]); // [1, 2, 3, 4]
// 类型判断
Utils.isVNode(value); // 是否为虚拟DOM节点
Utils.isFn(value); // 是否为函数
Utils.isObj(value); // 是否为对象
Utils.isStr(value); // 是否为字符串
// 下一个tick
Utils.nextTick(() => {
console.log('Next tick');
});API参考
核心 API
createApp
创建应用实例。
function createApp(options: ComponentOptions): AppInstance返回的 AppInstance:
interface AppInstance {
mount(container: string | HTMLElement): void;
unmount(): void;
}h
创建虚拟DOM节点。
function h(
type: string | Component,
props?: PlainObject,
children?: any
): VNodedefineComponent
定义组件。
function defineComponent(options: ComponentOptions): ComponentOptions响应式 API
reactive
function reactive<T extends PlainObject>(target: T): Tref
function ref<T>(value: T): Ref<T>computed
function computed<T>(getter: () => T): ComputedRef<T>effect
function effect(fn: Function, options?: { lazy?: boolean }): FunctiontoRef
function toRef<T, K extends keyof T>(target: T, key: K): Ref<T[K]>toRefs
function toRefs<T>(target: T): { [K in keyof T]: Ref<T[K]> }组件 API
ComponentOptions
interface ComponentOptions {
props?: string[] | Record<string, PropOptions>;
data?: () => PlainObject;
methods?: Record<string, Function>;
computed?: Record<string, Function>;
setup?: (props, context) => PlainObject | Function;
render?: Function;
template?: Function;
beforeCreate?: Function;
created?: Function;
beforeMount?: Function;
mounted?: Function;
beforeUpdate?: Function;
updated?: Function;
beforeUnmount?: Function;
unmounted?: Function;
provide?: () => Record<string, any>;
inject?: string[] | Record<string, any>;
expose?: string[];
name?: string;
keepAlive?: boolean;
}ComponentInstance
interface ComponentInstance {
vnode: VNode;
props: PlainObject;
data: PlainObject;
methods: PlainObject;
parent?: ComponentInstance;
$refs: Record<string, any>;
$children: ComponentInstance[];
$events?: Record<string, Function[]>;
mounted: boolean;
keepAlive: boolean;
$on?: (event: string, handler: Function) => void;
$emit?: (event: string, ...args: any[]) => void;
$set?: (key: string, value: any) => void;
$update?: (newVnode?: VNode) => void;
unmounted?: (isDeep?: boolean) => void;
}路由 API
createRouter
function createRouter(
routesOrOptions: RouteRecord[] | RouterConfig,
options?: RouterOptions
): RouterRouter
interface Router {
push(path: string | RouteLocation): void;
replace(path: string | RouteLocation): void;
back(): void;
forward(): void;
beforeEach(guard: NavigationGuard): void;
afterEach(hook: NavigationHook): void;
onError(handler: ErrorHandler): void;
getCurrentRoute(): RouteLocation;
install(app: AppInstance): void;
}RouteRecord
interface RouteRecord {
path: string;
component: Component;
name?: string;
meta?: Record<string, any>;
children?: RouteRecord[];
beforeEnter?: NavigationGuard;
}Store API
createStore
function createStore<S>(options: StoreModule<S>): Store<S>Store
interface Store<S> {
state: S;
getters: Record<string, any>;
commit(type: string, payload?: any): void;
dispatch(type: string, payload?: any): Promise<any>;
install(app: AppInstance): void;
}StoreModule
interface StoreModule<S> {
state: S;
getters?: Record<string, Function>;
mutations?: Record<string, Function>;
actions?: Record<string, Function>;
modules?: Record<string, StoreModule>;
namespaced?: boolean;
}最佳实践
1. 组件设计
单一职责原则:
// 好的做法
const UserAvatar = defineComponent({
props: ['userId', 'size'],
render() {
return h('img', {
src: `/api/users/${this.userId}/avatar`,
width: this.size,
height: this.size
});
}
});
// 不好的做法
const User = defineComponent({
props: ['userId'],
data() {
return {
posts: [],
comments: [],
settings: {}
};
},
// ... 过多的职责
});2. 状态管理
合理使用响应式:
// 基本类型使用 ref
const count = ref(0);
// 对象使用 reactive
const user = reactive({
name: 'John',
age: 30
});
// 大型不可变数据使用 shallowReactive
const largeData = shallowReactive(fetchLargeData());3. 性能优化
使用计算属性:
// 好的做法 - 使用计算属性缓存结果
const fullName = computed(() => {
return `${state.firstName} ${state.lastName}`;
});
// 不好的做法 - 在模板中重复计算
render() {
return h('div', {}, [
h('p', {}, `${state.firstName} ${state.lastName}`),
h('p', {}, `${state.firstName} ${state.lastName}`)
]);
}4. 路由设计
懒加载路由:
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard')
}
];5. 代码组织
按功能组织:
src/
├── views/
│ ├── Home/
│ │ ├── index.js
│ │ └── components/
│ └── Dashboard/
├── components/
│ ├── Button/
│ └── Input/
├── store/
│ ├── modules/
│ └── index.js
└── utils/性能优化
1. 虚拟DOM优化
使用key优化列表渲染:
const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
];
render() {
return h('ul', {},
items.map(item =>
h('li', { key: item.id }, item.name)
)
);
}2. 响应式优化
避免不必要的响应式:
// 使用 shallowReactive
const largeData = shallowReactive({
list: Array(10000).fill(0)
});
// 使用 readonly 保护数据
const config = readonly({
apiUrl: 'https://api.example.com'
});3. 组件优化
使用keep-alive缓存:
const CachedComponent = defineComponent({
keepAlive: true,
data() {
return {
heavyData: fetchHeavyData()
};
}
});4. 计算属性优化
避免在计算属性中进行昂贵操作:
// 好的做法
const filteredList = computed(() => {
return state.list.filter(item => item.active);
});
// 不好的做法
const result = computed(() => {
return expensiveOperation(state.data);
});5. 事件优化
使用事件委托:
const List = defineComponent({
data() {
return {
items: [1, 2, 3, 4, 5]
};
},
methods: {
handleClick(e) {
const itemId = e.target.dataset.id;
console.log('Clicked item:', itemId);
}
},
render() {
return h('ul', {
onClick: this.handleClick
},
this.items.map(id =>
h('li', { 'data-id': id }, `Item ${id}`)
)
);
}
});常见问题
Q1: 如何在组件外使用响应式数据?
import { reactive, effect } from 'nyannejs';
// 创建全局状态
const globalState = reactive({
theme: 'light',
language: 'zh-CN'
});
// 在任何地方使用
effect(() => {
document.documentElement.setAttribute('data-theme', globalState.theme);
});
export { globalState };Q2: 如何处理异步数据?
const AsyncComponent = defineComponent({
data() {
return {
loading: false,
data: null,
error: null
};
},
methods: {
async fetchData() {
this.loading = true;
this.error = null;
try {
const response = await fetch('/api/data');
this.data = await response.json();
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
}
},
mounted() {
this.fetchData();
},
render() {
if (this.loading) return h('div', {}, 'Loading...');
if (this.error) return h('div', {}, `Error: ${this.error}`);
return h('div', {}, JSON.stringify(this.data));
}
});Q3: 如何实现表单双向绑定?
const FormComponent = defineComponent({
data() {
return {
formData: {
username: '',
email: ''
}
};
},
methods: {
handleInput(key, value) {
this.formData[key] = value;
}
},
render() {
return h('form', {}, [
h('input', {
type: 'text',
value: this.formData.username,
onInput: (e) => this.handleInput('username', e.target.value)
}),
h('input', {
type: 'email',
value: this.formData.email,
onInput: (e) => this.handleInput('email', e.target.value)
})
]);
}
});Q4: 如何实现组件通信?
父传子:
// Parent
h(Child, { message: 'Hello' })
// Child
props: ['message']子传父:
// Child
this.$emit('update', newValue)
// Parent
h(Child, { onUpdate: (value) => { /* handle */ } })兄弟组件通信:
// 使用store或事件总线
const eventBus = reactive({
events: {}
});
// 发送事件
eventBus.$emit('event-name', data);
// 监听事件
effect(() => {
// 响应事件
});Q5: 如何优化大型列表渲染?
const VirtualList = defineComponent({
props: ['items', 'itemHeight', 'visibleCount'],
data() {
return {
scrollTop: 0
};
},
computed: {
visibleItems() {
const start = Math.floor(this.scrollTop / this.itemHeight);
const end = start + this.visibleCount;
return this.items.slice(start, end);
}
},
methods: {
handleScroll(e) {
this.scrollTop = e.target.scrollTop;
}
},
render() {
return h('div', {
style: {
height: `${this.visibleCount * this.itemHeight}px`,
overflowY: 'auto'
},
onScroll: this.handleScroll
},
this.visibleItems.map(item =>
h('div', {
style: {
height: `${this.itemHeight}px`
}
}, item.name)
)
);
}
});Q6: 如何实现路由守卫?
const router = createRouter({
routes: [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (isAuthenticated()) {
next();
} else {
next('/login');
}
}
}
]
});
// 全局守卫
router.beforeEach((to, from, next) => {
console.log('Navigating to:', to.path);
next();
});Q7: 如何调试响应式问题?
// 使用effect追踪依赖
effect(() => {
console.log('Current count:', state.count);
});
// 手动触发更新
state.count++;
// 检查响应式对象
console.log(state);许可证
Apache License 2.0
贡献
欢迎提交 Issue 和 Pull Request!
