npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nyannejs

v0.0.1-dev

Published

轻量级、高性能的现代前端JavaScript框架

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

目录

  1. 快速开始
  2. 核心概念
  3. 响应式系统
  4. 虚拟DOM
  5. 组件系统
  6. 生命周期
  7. 路由系统
  8. 状态管理
  9. 模板引擎
  10. 过渡动画
  11. HTTP请求
  12. 工具函数
  13. API参考
  14. 最佳实践
  15. 性能优化
  16. 常见问题

快速开始

安装

通过 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的差异。

核心策略:

  1. 同层比较,不跨层比较
  2. 基于key的节点识别
  3. 类型不同则直接替换
  4. 递归比较子节点

补丁类型:

  • 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: #/home

History 模式

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 */];

编译后的函数会在组件渲染时自动执行,获取数据并存储到指定的变量名中。

注意事项

  1. 异步执行: fetch 请求是异步的,数据加载完成前可能显示空白
  2. 数据类型: 默认解析为 JSON,确保 API 返回有效的 JSON 数据
  3. 错误处理: 当前版本需要自行处理请求错误
  4. 命名冲突: 确保不同的 fetch 使用不同的 name 属性避免覆盖
  5. 组件生命周期: 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
): VNode

defineComponent

定义组件。

function defineComponent(options: ComponentOptions): ComponentOptions

响应式 API

reactive

function reactive<T extends PlainObject>(target: T): T

ref

function ref<T>(value: T): Ref<T>

computed

function computed<T>(getter: () => T): ComputedRef<T>

effect

function effect(fn: Function, options?: { lazy?: boolean }): Function

toRef

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
): Router

Router

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!

联系方式