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

speedy-jsx

v1.1.26

Published

响应式JavaScript框架

Readme

Speedy-JSX

响应式JavaScript框架,提供虚拟DOM、组件系统、状态管理和路由功能。

目录

特性

  • 响应式数据系统
  • 虚拟DOM渲染
  • 组件化开发
  • 内置路由
  • 状态管理
  • CSS-in-JS支持
  • 异步组件

安装

npm install speedy-jsx
# 或
yarn add speedy-jsx

快速开始

基本示例

import { render, ref } from 'speedy-jsx';

function Counter() {
  const count = ref(0);
  const increment = () => { count.value++ };
  
  return () => (
    <div>
      <p>计数: {count.value}</p>
      <button onClick={increment}>增加</button>
    </div>
  );
}

// 可以直接传递组件函数
render(Counter, document.getElementById('app'));

组件开发

组件Props使用规范

在Speedy-JSX框架中,Props是响应式对象,需要遵循以下规则:

不要解构Props

// ❌ 错误:直接解构会丢失响应性
function MyComponent({ title, count }) {
  return () => <div>{title}: {count}</div>;
}

// ✅ 正确:直接使用props对象引用属性
function MyComponent(props) {
  return () => <div>{props.title}: {props.count}</div>;
}

正确处理Children

在Speedy-JSX中,子元素作为函数参数传递给渲染函数:

// ✅ 正确:通过渲染函数的参数获取children
function Container(props) {
  return (children) => (
    <div className={props.className}>{children}</div>
  );
}

Props的默认值

function Button(props) {
  return (children) => {
    const type = props.type || "button";
    const disabled = props.disabled || false;
    
    return (
      <button type={type} disabled={disabled}>
        {children}
      </button>
    );
  };
}

响应式系统

创建响应式状态

import { ref, reactive, computed } from 'speedy-jsx';

// 创建响应式引用
const count = ref(0);
count.value++; // 通过.value访问和修改

// 创建响应式对象
const state = reactive({
  count: 0,
  text: 'hello'
});
state.count++; // 直接修改属性

// 创建计算属性
const doubleCount = computed(() => count.value * 2);
console.log(doubleCount.value); // 自动更新

监听变化

import { watch, watchEffect } from 'speedy-jsx';

// 监听特定值
watch(count, (newValue, oldValue) => {
  console.log(`count从${oldValue}变为${newValue}`);
});

// 自动收集依赖并监听
watchEffect(() => {
  console.log(`当前count: ${count.value}, 两倍值: ${doubleCount.value}`);
});

状态管理

创建Store

import { defineStore, ref } from 'speedy-jsx';

// 创建一个store
const counterStore = defineStore(() => {
  const count = ref(0);
  
  function increment() {
    count.value++;
  }
  
  return {
    count,
    increment
  };
});

// 使用store
function Counter() {
  return () => (
    <div>
      <p>Count: {counterStore.count.value}</p>
      <button onClick={counterStore.increment}>+1</button>
    </div>
  );
}

持久化Store

import { definePersistStore, ref } from 'speedy-jsx';

const todoStore = definePersistStore(() => {
  const todos = ref([]);
  
  function addTodo(text) {
    todos.value.push({ id: Date.now(), text, completed: false });
  }
  
  return { todos, addTodo };
}, {
  key: 'app-todos', // 存储键名
  storage: localStorage // 存储介质
});

路由系统

Speedy-JSX提供了路由系统,支持哈希模式和历史模式、嵌套路由、动态参数、命名路由等功能。

基本用法

import { Router, RouterView, RouterLink } from 'speedy-jsx';

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/users/:id', component: UserProfile }
];

function App() {
  return () => (
    <Router routes={routes} mode="hash">
      <nav>
        <RouterLink to="/">首页</RouterLink>
        <RouterLink to="/about">关于</RouterLink>
      </nav>
      <RouterView />
    </Router>
  );
}

// 直接传递组件函数
render(App, document.getElementById('app'));

路由模式

Speedy路由系统支持两种导航模式:

// 哈希模式 (默认) - 使用URL的哈希部分(#)
<Router routes={routes} mode="hash">

// 历史模式 - 使用HTML5 History API
<Router routes={routes} mode="history">

嵌套路由

Speedy支持无限层级的嵌套路由:

const routes = [
  { 
    path: '/user', 
    component: UserLayout,
    children: [
      { path: '/profile', component: UserProfile },
      { path: '/settings', component: UserSettings },
      { 
        path: '/posts', 
        component: PostsLayout,
        children: [
          { path: '/list', component: PostList },
          { path: '/:id', component: PostDetail }
        ]
      }
    ]
  }
];

// 在父组件中使用RouterView
function UserLayout() {
  return () => (
    <div>
      <h2>用户中心</h2>
      <RouterView /> {/* 子路由组件将在这里渲染 */}
    </div>
  );
}

动态路由参数

使用冒号语法定义动态路由参数:

// 路由定义
const routes = [
  { path: '/users/:id', component: UserDetail },
  { path: '/articles/:category/:slug', component: ArticleDetail }
];

// 在组件中访问参数
import { useParams } from 'speedy-jsx';

function UserDetail() {
  const params = useParams();
  
  return () => (
    <div>用户ID: {params.value.id}</div>
  );
}

查询参数

访问URL查询参数:

import { useQuery } from 'speedy-jsx';

function SearchPage() {
  const query = useQuery();
  
  return () => (
    <div>
      <h2>搜索结果</h2>
      <p>关键词: {query.value.keyword}</p>
      <p>页码: {query.value.page || 1}</p>
    </div>
  );
}

// 使用RouterLink传递查询参数
<RouterLink to={{ path: '/search', query: { keyword: 'speedy', page: 2 } }}>
  搜索
</RouterLink>

命名路由

可以为路由指定名称,并通过名称导航:

// 定义命名路由
const routes = [
  { path: '/', name: 'home', component: Home },
  { path: '/about', name: 'about', component: About },
  { path: '/users/:id', name: 'user-detail', component: UserDetail }
];

// 使用命名路由导航
<RouterLink to={{ name: 'user-detail', params: { id: 123 } }}>
  查看用户
</RouterLink>

// 在代码中导航
import { useRouter } from 'speedy-jsx';

function NavigateButton() {
  const router = useRouter();
  
  const handleClick = () => {
    router.value.push({ name: 'user-detail', params: { id: 123 } });
  };
  
  return () => (
    <button onClick={handleClick}>查看用户</button>
  );
}

路由导航守卫

Speedy提供了全面的导航守卫系统,用于控制导航流程:

import { onBeforeEach, onBeforeResolve, onAfterEach } from 'speedy-jsx';

// 全局前置守卫 - 在导航开始前触发
onBeforeEach((to, from, next) => {
  // 检查用户是否已登录
  if (to.path.startsWith('/admin') && !isAuthenticated()) {
    // 重定向到登录页
    next('/login');
  } else {
    // 继续导航
    next();
  }
});

// 全局解析守卫 - 在导航解析完成后、组件渲染前触发
onBeforeResolve((to, from, next) => {
  // 可以在这里加载数据
  loadPageData(to.path).then(() => {
    next();
  });
});

// 全局后置钩子 - 在导航完成后触发
onAfterEach((to, from) => {
  // 更新页面标题
  document.title = to.meta.title || 'Speedy应用';
  
  // 记录页面访问
  trackPageView(to.path);
});

路由元数据

可以为路由添加元数据,用于存储与路由相关的额外信息:

const routes = [
  { 
    path: '/admin',
    component: AdminLayout,
    meta: { 
      requiresAuth: true,
      title: '管理后台'
    }
  }
];

// 在导航守卫中使用元数据
onBeforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login');
  } else {
    next();
  }
});

编程式导航

除了使用RouterLink外,还可以在代码中进行导航:

import { useRouter } from 'speedy-jsx';

function NavigationExample() {
  const router = useRouter();
  
  function navigate() {
    // 导航到指定路径
    router.value.push('/about');
    
    // 导航到命名路由
    router.value.push({ name: 'user-detail', params: { id: 123 } });
    
    // 带查询参数的导航
    router.value.push({ path: '/search', query: { q: 'speedy' } });
    
    // 替换当前路由(不产生新的历史记录)
    router.value.replace('/new-path');
    
    // 前进、后退和跳转历史
    router.value.back();
    router.value.forward();
    router.value.go(-2); // 后退两步
  }
  
  return () => (
    <button onClick={navigate}>导航示例</button>
  );
}

活跃链接样式

RouterLink组件会根据当前路由自动添加活跃类:

<RouterLink 
  to="/about" 
  activeClass="active-link" 
  exactActiveClass="exact-active-link"
>
  关于我们
</RouterLink>

<style>
  .active-link {
    color: blue; /* 当前路由或其子路由匹配时应用 */
  }
  .exact-active-link {
    font-weight: bold; /* 仅当完全匹配当前路由时应用 */
  }
</style>

精确匹配模式

RouterLink 组件提供了 exact 属性,用于控制链接何时被视为活跃状态:

// 默认匹配模式(不使用exact):
// 当前路径是 /about 或以 /about/ 开头的路径(如 /about/team)时都会匹配
<RouterLink to="/about" activeClass="active">关于</RouterLink>

// 精确匹配模式:
// 只有当前路径完全等于 /about 时才会匹配
<RouterLink to="/about" exact={true} activeClass="active">关于</RouterLink>

精确匹配模式对于构建导航菜单特别有用,可以防止父路由链接在访问子路由时保持活跃状态:

// 导航菜单示例
function NavMenu() {
  return () => (
    <nav>
      <RouterLink to="/" exact={true} activeClass="active">首页</RouterLink>
      <RouterLink to="/products" exact={true} activeClass="active">产品</RouterLink>
      <RouterLink to="/products/new" activeClass="active">新产品</RouterLink>
      <RouterLink to="/about" activeClass="active">关于</RouterLink>
    </nav>
  );
}

上面的例子中,当访问 /products/new 路径时:

  • 设置了 exact={true}/products 链接不会激活
  • /products/new 链接会激活

这样可以更精确地控制导航项的视觉反馈,提升用户体验。

路由重定向

可以设置路由重定向,将一个路径自动导向另一个路径:

const routes = [
  { path: '/', component: Home },
  { path: '/home', redirect: '/' },
  
  // 动态重定向
  { 
    path: '/old-path/:id',
    redirect: to => {
      // 可以返回字符串路径
      return `/new-path/${to.params.id}`;
      
      // 或返回路由对象
      return {
        path: `/new-path/${to.params.id}`,
        query: { ...to.query, source: 'redirect' }
      };
    }
  }
];

404页面处理

可以添加通配符路由作为404页面:

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { path: '/404', component: NotFound },
];

嵌套路由404处理

Speedy 路由系统支持为每个嵌套级别配置独立的 404 页面,提供更精确的路由控制:

const routes = [
  { 
    path: '/user', 
    component: UserLayout,
    children: [
      { path: '/profile', component: UserProfile },
      // 用户模块专用的404页面,只会捕获/user/下的未匹配路径
      { path: '/404', component: UserNotFound }
    ]
  },
  { 
    path: '/blog',
    component: BlogLayout,
    children: [
      { path: '/posts', component: BlogPosts },
      // 博客模块专用的404页面
      { path: '/404', component: BlogNotFound }
    ]
  },
  // 全局404页面,捕获所有其他未匹配的路径
  { path: '/404', component: GlobalNotFound }
];

当路由系统无法匹配一个路径时,它会:

  1. 首先在当前路径的父级路由中查找 /404 子路由
  2. 如果找到,则渲染该嵌套404组件
  3. 如果未找到,则继续向上级路由查找
  4. 最终回退到全局 /404 路由

这种设计允许为不同的应用模块提供专门的404页面,显示更相关的错误信息和导航选项。

样式系统

Speedy提供了强大而灵活的CSS-in-JS样式系统,包含四个核心API:scopekeyframesmediaglobal,以及标签式API用于创建样式化组件。

作用域样式 (scope)

使用styled.scope创建局部作用域样式,自动生成唯一类名避免样式冲突:

import { styled } from 'speedy-jsx';

// 创建局部作用域样式
const styles = styled.scope({
  container: {
    display: 'flex',
    flexDirection: 'column',
    padding: '20px',
    backgroundColor: '#f5f5f5',
    borderRadius: '8px',
    boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
  },
  title: {
    fontSize: '24px',
    color: '#333',
    marginBottom: '16px'
  },
  content: {
    fontSize: '16px',
    lineHeight: '1.5',
    color: '#666'
  }
});

// 使用样式
function Card() {
  return () => (
    <div classStyle={[styles.container]}>
      <h2 classStyle={[styles.title]}>卡片标题</h2>
      <p classStyle={[styles.content]}>卡片内容</p>
    </div>
  );
}

样式通过classStyle属性应用于元素,支持多个样式的数组形式。每个样式都会生成唯一的类名,确保样式隔离和组件复用。

样式化组件 (Styled Components)

使用标签式API创建样式化组件,类似styled-components:

import { styled } from 'speedy-jsx';

// 创建样式化按钮组件
const Button = styled.button`
  background-color: ${props => props.$primary ? '#0070f3' : '#f0f0f0'};
  color: ${props => props.$primary ? 'white' : '#333'};
  padding: ${props => props.$size === 'large' ? '12px 24px' : '8px 16px'};
  border-radius: 4px;
  border: none;
  font-size: ${props => props.$size === 'large' ? '16px' : '14px'};
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    opacity: ${props => props.$hoverable ? 0.9 : 1};
  }
  
  &:active {
    transform: translateY(0);
  }
`;

// 使用样式化组件
function App() {
  return () => (
    <div>
      <Button $primary={true} $size="large" $hoverable={true}>
        主要按钮
      </Button>
      <Button $primary={false} $size="small" $hoverable={true}>
        次要按钮
      </Button>
    </div>
  );
}

特点:

  • 自动生成唯一类名
  • 支持动态属性(以$开头的props属性)
  • 支持嵌套选择器(使用&引用自身)
  • 响应式监听props变化自动更新样式

关键帧动画

使用styled.keyframes创建CSS动画:

import { styled } from 'speedy-jsx';

// 定义一个淡入动画
const fadeIn = styled.keyframes({
  '0%': {
    opacity: 0,
    transform: 'translateY(20px)'
  },
  '50%': {
    opacity: 0.5,
    transform: 'translateY(10px)'
  },
  '100%': {
    opacity: 1,
    transform: 'translateY(0)'
  }
});

// 定义一个脉冲动画
const pulse = styled.keyframes({
  '0%': { transform: 'scale(1)' },
  '50%': { transform: 'scale(1.05)' },
  '100%': { transform: 'scale(1)' }
});

// 在样式中使用动画
const styles = styled.scope({
  animatedBox: {
    animation: `${fadeIn} 0.5s ease-out forwards`,
    padding: '20px',
    backgroundColor: '#f0f0f0'
  },
  pulsingButton: {
    animation: `${pulse} 1s infinite ease-in-out`,
    padding: '10px 20px',
    backgroundColor: '#0070f3',
    color: 'white',
    border: 'none',
    borderRadius: '4px'
  }
});

function AnimationExample() {
  return () => (
    <div>
      <div classStyle={[styles.animatedBox]}>
        这个元素会淡入
      </div>
      <button classStyle={[styles.pulsingButton]}>
        脉冲按钮
      </button>
    </div>
  );
}

媒体查询

使用styled.media创建响应式布局:

import { styled } from 'speedy-jsx';

// 创建基础样式
const styles = styled.scope({
  grid: {
    display: 'grid',
    gridTemplateColumns: 'repeat(4, 1fr)',
    gap: '20px',
    padding: '20px'
  },
  card: {
    padding: '20px',
    borderRadius: '8px',
    backgroundColor: 'white',
    boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
    transition: 'transform 0.3s ease'
  }
});

// 添加响应式媒体查询
styled.media({
  // 平板设备
  '(max-width: 992px)': {
    [styles.grid.className]: {
      gridTemplateColumns: 'repeat(3, 1fr)'
    }
  },
  // 小平板设备
  '(max-width: 768px)': {
    [styles.grid.className]: {
      gridTemplateColumns: 'repeat(2, 1fr)'
    },
    [styles.card.className]: {
      padding: '15px'
    }
  },
  // 移动设备
  '(max-width: 480px)': {
    [styles.grid.className]: {
      gridTemplateColumns: '1fr',
      gap: '15px',
      padding: '10px'
    },
    [styles.card.className]: {
      padding: '10px'
    }
  }
});

function ResponsiveGrid() {
  return () => (
    <div classStyle={[styles.grid]}>
      <div classStyle={[styles.card]}>卡片1</div>
      <div classStyle={[styles.card]}>卡片2</div>
      <div classStyle={[styles.card]}>卡片3</div>
      <div classStyle={[styles.card]}>卡片4</div>
    </div>
  );
}

全局样式

使用styled.global添加全局样式规则:

import { styled } from 'speedy-jsx';

// 添加全局样式 - 通常在应用入口处调用一次
styled.global({
  'html, body': {
    margin: 0,
    padding: 0,
    fontFamily: 'system-ui, -apple-system, sans-serif',
    boxSizing: 'border-box',
    backgroundColor: '#f9f9f9'
  },
  '*, *::before, *::after': {
    boxSizing: 'inherit'
  },
  'a': {
    color: '#0070f3',
    textDecoration: 'none',
    transition: 'color 0.2s ease'
  },
  'a:hover': {
    color: '#0051bb',
    textDecoration: 'underline'
  },
  '.container': {
    maxWidth: '1200px',
    margin: '0 auto',
    padding: '0 20px'
  },
  // 支持嵌套选择器
  'form': {
    'input, select, textarea': {
      padding: '8px 12px',
      border: '1px solid #ddd',
      borderRadius: '4px',
      fontSize: '16px'
    },
    'button': {
      padding: '8px 16px',
      backgroundColor: '#0070f3',
      color: 'white',
      border: 'none',
      borderRadius: '4px',
      cursor: 'pointer'
    }
  }
});

组合使用样式系统

全面展示样式系统功能的综合示例:

import { styled } from 'speedy-jsx';
import { ref } from 'speedy-jsx';

// 1. 创建关键帧动画
const fadeIn = styled.keyframes({
  '0%': { opacity: 0, transform: 'translateY(10px)' },
  '100%': { opacity: 1, transform: 'translateY(0)' }
});

// 2. 创建全局样式
styled.global({
  'body': {
    margin: 0,
    fontFamily: 'sans-serif',
    backgroundColor: '#f5f5f5'
  }
});

// 3. 创建局部样式
const styles = styled.scope({
  app: {
    maxWidth: '800px',
    margin: '40px auto',
    padding: '20px',
    backgroundColor: 'white',
    borderRadius: '8px',
    boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
    animation: `${fadeIn} 0.5s ease-out`
  },
  header: {
    marginBottom: '24px',
    borderBottom: '1px solid #eee',
    paddingBottom: '16px'
  },
  title: {
    fontSize: '28px',
    fontWeight: '600',
    color: '#333',
    margin: '0 0 8px 0'
  },
  subtitle: {
    fontSize: '16px',
    color: '#666',
    margin: 0
  },
  content: {
    display: 'flex',
    flexDirection: 'column',
    gap: '20px'
  }
});

// 4. 创建样式化组件
const Card = styled.div`
  padding: 20px;
  border-radius: 8px;
  background-color: ${props => props.$dark ? '#333' : '#f9f9f9'};
  color: ${props => props.$dark ? '#fff' : '#333'};
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
  transition: all 0.3s ease;
  
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  }
  
  h3 {
    margin-top: 0;
    font-size: 18px;
  }
  
  p {
    margin-bottom: 0;
    line-height: 1.5;
  }
`;

// 5. 添加响应式媒体查询
styled.media({
  '(max-width: 768px)': {
    [styles.app.className]: {
      margin: '20px 16px',
      padding: '16px'
    },
    [styles.title.className]: {
      fontSize: '24px'
    }
  }
});

// 主应用组件
function StyleSystemDemo() {
  const darkMode = ref(false);
  const toggleMode = () => { darkMode.value = !darkMode.value };
  
  return () => (
    <div classStyle={[styles.app]}>
      <header classStyle={[styles.header]}>
        <h1 classStyle={[styles.title]}>Speedy 样式系统演示</h1>
        <p classStyle={[styles.subtitle]}>展示样式系统的全部功能</p>
      </header>
      
      <div classStyle={[styles.content]}>
        <button onClick={toggleMode}>
          切换{darkMode.value ? '浅色' : '深色'}模式
        </button>
        
        <Card $dark={darkMode.value}>
          <h3>样式化组件</h3>
          <p>这是使用styled.div创建的卡片组件,支持动态属性和嵌套选择器。</p>
        </Card>
      </div>
    </div>
  );
}

高级特性

样式系统还提供以下高级特性:

  • 嵌套选择器:支持在样式对象中嵌套子选择器
  • 响应式props:样式化组件中可以使用响应式属性动态更新样式
  • 自动类名生成:确保样式隔离和避免冲突

异步组件

import { Async, Pending, Then, Catch } from 'speedy-jsx';

function DataLoader() {
  const fetchData = () => fetch('https://api.example.com/data');

  return () => (
    <Async promise={fetchData}>
      <Pending>
        <p>数据加载中...</p>
      </Pending>
      <Then>
        {(data) => <p>加载成功: {JSON.stringify(data)}</p>}
      </Then>
      <Catch>
        {(error) => <p>加载失败: {error.message}</p>}
      </Catch>
    </Async>
  );
}

自定义指令

Speedy提供了自定义指令系统,用于复用与DOM元素交互的逻辑。指令是一种特殊的函数,可以对渲染的DOM元素执行底层操作。

注册指令

import { directive } from 'speedy-jsx';

// 注册一个简单的show指令
directive("show", (el, update) => {
  // 设置update回调函数处理值变更
  update((oldValue, newValue) => {
    el.style.display = newValue ? 'block' : 'none';
  });

  // 可以注册多个update回调处理不同的逻辑
  update((oldValue, newValue) => {
    // 例如,添加过渡效果
    if (newValue) {
      el.style.opacity = '1';
      el.style.transition = 'opacity 0.3s';
    } else {
      el.style.opacity = '0';
    }
  });

  // 返回清理函数(可选)
  return () => {
    // 指令被移除时执行的清理操作
    console.log("清理show指令");
  };
});

使用指令

在JSX或模板中,指令通过use-前缀添加到元素上:

function ConditionalDisplay() {
  const visible = ref(true);
  
  const toggle = () => {
    visible.value = !visible.value;
  };
  
  return () => (
    <div>
      <button onClick={toggle}>切换显示</button>
      <div use-show={visible.value}>
        这个元素会根据visible的值显示或隐藏
      </div>
    </div>
  );
}

指令生命周期

指令有简单的生命周期:

  1. 初始化:指令首次应用到元素时执行
  2. 更新:当指令的值发生变化时,通过update回调处理
  3. 清理:当元素被卸载或指令被移除时执行

指令的最佳实践

  1. 单一职责:每个指令应该只关注一个特定功能
  2. 性能考虑:避免在指令中执行重复的昂贵操作
  3. 清理资源:始终返回清理函数来释放资源,如事件监听器
  4. 响应式值:指令值通常应该是响应式的,以便在值变化时触发更新

依赖注入

import { provide, inject } from 'speedy-jsx';

// 在父组件中提供数据
function Parent() {
  provide('theme', {
    primary: '#3498db',
    secondary: '#2ecc71'
  });
  
  return () => (
    <div>
      <Child />
    </div>
  );
}

// 在子组件中注入数据
function Child() {
  const theme = inject('theme', { primary: '#000', secondary: '#fff' });
  
  return () => (
    <div style={{ color: theme.primary }}>
      子组件内容
    </div>
  );
}

批量更新和效果控制

import { batch, nextTick } from 'speedy-jsx';

// 批量更新
batch(() => {
  count1.value++;
  count2.value++;
  count3.value++;
}); // 只会触发一次更新

// 下一个DOM更新后执行
nextTick(() => {
  console.log('DOM已更新');
});

生命周期钩子

import { onMounted, onUpdated, onUnmounted } from 'speedy-jsx';

function MyComponent() {
  onMounted(() => {
    console.log('组件已挂载');
  });
  
  onUpdated(() => {
    console.log('组件已更新');
  });
  
  onUnmounted(() => {
    console.log('组件已卸载');
  });
  
  return () => <div>组件内容</div>;
}

资源管理与清理

Speedy提供了强大的资源管理机制,通过$runScope()函数可以轻松管理组件中的各种资源,确保在组件卸载时自动清理,避免内存泄漏。

基本用法

import { $runScope } from 'speedy-jsx';

function TimerComponent() {
  const count = ref(0);
  // 获取运行作用域
  const run = $runScope();
  
  onMounted(() => {
    
    // 设置定时器,会在组件卸载时自动清理
    run.setInterval(() => {
      count.value++;
    }, 1000);
  });
  
  return () => <div>计数: {count.value}</div>;
}

可用的资源管理API

$runScope()提供了多种资源管理API:

定时器管理

function TimerExample() {
  const message = ref('');
  const run = $runScope();
  
  onMounted(() => {
    
    // 设置定时器,自动清理
    run.setTimeout(() => {
      message.value = '已延迟3秒显示';
    }, 3000);
    
    // 设置循环定时器,自动清理
    run.setInterval(() => {
      console.log('每秒执行一次');
    }, 1000);
  });
  
  return () => <div>{message.value}</div>;
}

事件监听器

function EventListenerExample() {
  const mousePosition = ref({ x: 0, y: 0 });
  const run = $runScope();
  
  onMounted(() => {
    
    // 添加事件监听器,自动移除
    run.onWindow('mousemove', (e) => {
      mousePosition.value = { x: e.clientX, y: e.clientY };
    });
    
    // 也可以使用更通用的on方法
    const button = document.getElementById('my-button');
    if (button) {
      run.on(button, 'click', () => {
        console.log('按钮被点击');
      });
    }
  });
  
  return () => (
    <div>鼠标位置: {mousePosition.value.x}, {mousePosition.value.y}</div>
  );
}

动画帧管理

function AnimationExample() {
  const position = ref(0);
  const run = $runScope();
  
  onMounted(() => {
    
    function animate() {
      position.value += 5;
      if (position.value < 500) {
        run.requestAnimationFrame(animate);
      }
    }
    
    // 开始动画,自动取消
    run.requestAnimationFrame(animate);
  });
  
  return () => (
    <div style={{ transform: `translateX(${position.value}px)` }}>
      移动的元素
    </div>
  );
}

网络请求管理

function FetchExample() {
  const data = ref(null);
  const loading = ref(false);
  const error = ref(null);
  const run = $runScope();
  
  function loadData() {
    
    loading.value = true;
    error.value = null;
    
    // 创建可取消的fetch请求
    run.fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(result => {
        data.value = result;
        loading.value = false;
      })
      .catch(err => {
        error.value = err.message;
        loading.value = false;
      });
  }
  
  onMounted(loadData);
  
  return () => (
    <div>
      {loading.value && <p>加载中...</p>}
      {error.value && <p>错误: {error.value}</p>}
      {data.value && <p>数据: {JSON.stringify(data.value)}</p>}
      <button onClick={loadData}>重新加载</button>
    </div>
  );
}

WebSocket管理

function ChatExample() {
  const messages = ref([]);
  const connection = ref(null);
  const run = $runScope();
  
  onMounted(() => {
    
    // 创建WebSocket连接,自动关闭
    const socket = run.createWebSocket('wss://chat.example.com');
    
    socket.onmessage = (event) => {
      messages.value.push(JSON.parse(event.data));
    };
    
    socket.onopen = () => {
      console.log('连接已建立');
    };
    
    connection.value = socket;
  });
  
  function sendMessage(text) {
    if (connection.value && connection.value.readyState === WebSocket.OPEN) {
      connection.value.send(JSON.stringify({ text }));
    }
  }
  
  return () => (
    <div>
      <div className="messages">
        {messages.value.map((msg, i) => (
          <div key={i}>{msg.text}</div>
        ))}
      </div>
      <button onClick={() => sendMessage('Hello')}>发送消息</button>
    </div>
  );
}

观察器管理

function ObserverExample() {
  const isVisible = ref(false);
  const elementSize = ref({ width: 0, height: 0 });
  const run = $runScope();
  
  onMounted(() => {
    const targetElement = document.getElementById('observed-element');
    
    if (targetElement) {
      // 创建交叉观察器,自动断开连接
      const intersectionObserver = run.createIntersectionObserver(
        (entries) => {
          isVisible.value = entries[0].isIntersecting;
        },
        { threshold: 0.1 }
      );
      intersectionObserver.observe(targetElement);
      
      // 创建尺寸观察器,自动断开连接
      const resizeObserver = run.createResizeObserver((entries) => {
        const rect = entries[0].contentRect;
        elementSize.value = { width: rect.width, height: rect.height };
      });
      resizeObserver.observe(targetElement);
    }
  });
  
  return () => (
    <div>
      <div id="observed-element" style={{ height: '200px', background: '#f0f0f0' }}>
        被观察的元素
      </div>
      <p>元素{isVisible.value ? '可见' : '不可见'}</p>
      <p>尺寸: {elementSize.value.width} x {elementSize.value.height}</p>
    </div>
  );
}

自定义清理函数

function CustomCleanupExample() {
  const thirdPartyLib = ref(null);
  const run = $runScope();
  
  onMounted(() => {
    
    // 初始化第三方库
    const instance = new SomeThirdPartyLib();
    instance.initialize();
    thirdPartyLib.value = instance;
    
    // 注册自定义清理函数
    run.onCleanup(() => {
      if (thirdPartyLib.value) {
        thirdPartyLib.value.destroy();
        thirdPartyLib.value = null;
      }
    });
  });
  
  return () => <div>第三方库示例</div>;
}

可取消的Promise

function CancelablePromiseExample() {
  const data = ref(null);
  const loading = ref(false);
  const run = $runScope();
  
  function loadData() {
    loading.value = true;
    
    // 创建可取消的Promise
    const { promise } = run.createCancelablePormise((resolve, reject) => {
      // 模拟长时间运行的异步操作
      setTimeout(() => {
        resolve({ result: 'Success' });
      }, 3000);
    });
    
    promise
      .then(result => {
        data.value = result;
        loading.value = false;
      })
      .catch(err => {
        if (err.canceled) {
          console.log('操作已取消');
        } else {
          console.error('发生错误:', err);
        }
        loading.value = false;
      });
  }
  
  onMounted(loadData);
  
  return () => (
    <div>
      {loading.value ? <p>加载中...</p> : <p>数据: {JSON.stringify(data.value)}</p>}
      <button onClick={loadData}>重新加载</button>
    </div>
  );
}

避免内存泄漏的最佳实践

使用$runScope()可以有效避免以下常见的内存泄漏源:

  1. 未清理的定时器: 使用run.setTimeoutrun.setInterval代替原生方法
  2. 未移除的事件监听器: 使用run.onrun.onWindowrun.onDocument添加事件监听
  3. 未关闭的网络连接: 使用run.fetchrun.createWebSocket管理网络请求
  4. 未断开连接的观察器: 使用run.create*Observer系列方法创建各种观察器
  5. 第三方库资源: 使用run.onCleanup注册自定义清理函数

通过这些API,您可以编写更加健壮的组件,无需担心资源泄漏问题。

API文档

完整的API文档请参考类型定义文件。