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

@oldbig/redux-lite

v1.0.15

Published

A lightweight, zero-dependency, type-safe state management library for React.

Readme

redux-lite

English

npm version license coverage badge

一个为 React 量身打造的、零依赖、类型安全、轻量级的状态管理库。

redux-lite 提供了一个现代、简洁且高性能的状态管理方案,旨在通过 TypeScript 提供顶级的开发体验。现在,对您的 React 组件进行单元测试变得简单到超乎想象。

核心特性

  • 🚀 零依赖:极其轻量,除了 react 作为对等依赖外,无任何第三方运行时依赖。
  • ⚡️ 高性能:通过智能的值比较,从设计上避免不必要的组件重复渲染。
  • ✨ 简洁直观的 API:极简的 API,易于学习和使用。
  • 🔒 完全类型安全:从 store 定义到 dispatchers,提供端到端的类型安全和卓越的自动补全体验。
  • ✅ 难以置信的简单测试:灵活的 Provider 让模拟单元测试的 state 变得轻而易举。
  • 🐞 DevTools 就绪:可选的、零成本的 Redux DevTools 集成,提供顶级的调试体验。
  • 🔌 中间件支持:通过自定义中间件扩展功能,类似于 Redux。

安装

npm install @oldbig/redux-lite
# or
yarn add @oldbig/redux-lite
# or
pnpm add @oldbig/redux-lite

快速上手

1. 定义你的初始 store

创建一个 storeDefinition 对象。这个唯一的对象是您整个 state 结构和类型的"真理之源"。

// store.ts
import { initiate, optional } from '@oldbig/redux-lite';

export const STORE_DEFINITION = {
  user: {
    name: 'Jhon' as string | null,
    age: 30,
  },
  // 对可能不存在的 state 切片使用 `optional`
  task: optional({ 
    id: 1,
    title: '完成 redux-lite',
  }),
  counter: 0,
};

export const { ReduxLiteProvider, useReduxLiteStore } = 
  initiate(STORE_DEFINITION);

2. 使用 Provider 包装你的应用

在你的主应用文件中,用 ReduxLiteProvider 来包装你的组件树。

// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ReduxLiteProvider } from './store';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <ReduxLiteProvider>
      <App />
    </ReduxLiteProvider>
  </React.StrictMode>,
);

3. 在组件中使用 hook

使用 useReduxLiteStore hook 来访问 state 切片及其对应的 dispatchers。该 hook 返回一个扁平化的对象,包含所有 state 属性和类型安全的 dispatcher 函数。

// MyComponent.tsx
import { useReduxLiteStore } from './store';

const MyComponent = () => {
  // 解构 state 和 dispatchers
  const { 
    user, 
    counter,
    dispatchUser, 
    dispatchPartialUser, 
    dispatchCounter 
  } = useReduxLiteStore();

  return (
    <div>
      <h2>用户: {user.name}</h2>
      <p>计数器: {counter}</p>

      {/* 全量更新 */}
      <button onClick={() => dispatchUser({ name: 'Ken', age: 31 })}>
        设置用户
      </button>

      {/* 部分更新 */}
      <button onClick={() => dispatchPartialUser({ age: 35 })}>
        增加年龄
      </button>

      {/* 函数式更新,可访问整个 store */}
      <button onClick={() => dispatchPartialUser((currentUser, store) => ({ age: currentUser.age + store.counter }))}>
        根据计数器增加年龄
      </button>
    </div>
  );
};

API

initiate(storeDefinition, options?)

本库唯一的入口点。

  • storeDefinition: 一个定义了您 store 结构和初始值的对象。
  • options (可选): 用于附加配置的对象。
    • devTools (可选): boolean | { name: string } - 启用或配置 Redux DevTools。
    • middlewares (可选): Middleware[] - 要应用的中间件数组。
  • 返回: 一个包含 { ReduxLiteProvider, useReduxLiteStore, useSelector } 的对象。

useReduxLiteStore()

该 hook 返回一个扁平化的对象,其中包含所有 state 切片和 dispatchers。

Dispatchers

对于 state 的每一个切片(例如 user),都会生成两个 dispatcher:

  • dispatchUser(payload): 用于全量更新。
  • dispatchPartialUser(payload): 用于部分更新。

payload 可以是一个值,也可以是一个函数。如果它是一个函数,它会接收该切片的先前状态作为第一个参数,并接收整个 store 的 state 作为第二个参数:(prevState, fullStore) => newState

optional(initialValue?)

一个辅助函数,用于将 state 的某个切片标记为可选的。该 state 属性的类型将被推导为 T | undefined

  • initialValue (可选): 该属性的初始值。如果未提供,则 state 的初始值为 undefined

useSelector(selector, equalityFn?)

一个用于选择和订阅 state 一部分的钩子,具有性能优化。它类似于 react-redux 中的 useSelector 钩子。

  • selector: (store: TStore) => TSelected - 一个函数,它接收整个 store 的 state 并返回所选的值。
  • equalityFn (可选): (a: TSelected, b: TSelected) => boolean - 一个用于比较所选值的函数。默认为 isEqual(一个深度相等检查)。如果 selector 函数返回的结果与上一次调用相同(通过此相等函数确定),useSelector 将返回之前的结果,这有助于防止使用它的组件进行不必要的重新渲染。在大多数情况下,您不需要提供此参数。仅当 selector 返回的值中包含函数类型的字段时,才需要提供此函数。

何时使用 useSelector

虽然 useReduxLiteStore 对于访问 state 和 dispatchers 很方便,但对于只需要读取一小部分 state 的性能关键组件,强烈建议使用 useSelector。当 store 的其他部分发生变化时,它可以帮助防止不必要的重新渲染。

示例:

import { useSelector } from './store';

const UserName = () => {
 // 这个组件只会在 `user.name` 改变时重新渲染。
 const userName = useSelector(store => store.user.name);
 
 return <div>{userName}</div>
}

const UserAge = () => {
   // 这个组件只会在 `user.age` 改变时重新渲染。
   const userAge = useSelector(store => store.user.age);
   
   return <div>{userAge}</div>
}

处理异步操作

redux-lite 通过结合标准的 JavaScript async/await 语法和其 dispatch 的函数式更新形式,以一种优雅而简单的方式处理异步操作。这种模式非常直观、健壮,并且不需要学习任何新的 API。

推荐模式:

  1. 使用 async/await 来处理你的异步逻辑(例如,获取数据)。
  2. 调用同步的 dispatch 函数,并传入一个更新函数来应用结果。这能确保您始终基于最新的 state 工作,从而避免竞态条件。

示例:获取用户并更新 store

import { useReduxLiteStore } from './store';
import { api } from './api';

const UserComponent = () => {
  const { user, dispatchUser, dispatchPartialUser } = useReduxLiteStore();

  const handleFetchUser = async () => {
    try {
      // 1. 等待你的 API 返回数据
      const fetchedUser = await api.fetchUser(123);

      // 2. Dispatch 结果以全量更新 user 切片
      dispatchUser(fetchedUser);

    } catch (error) {
      console.error("获取用户失败:", error);
    }
  };
  
  const handleIncrementUserAge = async () => {
    try {
      // 1. 从你的 API 等待需要变更的数据
      const { ageIncrement } = await api.fetchAgeIncrement(123); // 假设返回 { ageIncrement: 1 }

      // 2. 使用函数式更新,只返回需要变更的部分对象。
      // redux-lite 会自动将这个部分对象与现有的 user state 合并。
      dispatchPartialUser(currentUser => ({
        age: currentUser.age + ageIncrement,
      }));

    } catch (error) {
      console.error("增加用户年龄失败:", error);
    }
  };

  return (
    <div>
      <p>当前用户: {user.name}</p>
      <button onClick={handleFetchUser}>获取用户</button>
      <button onClick={handleIncrementUserAge}>增加用户年龄</button>
    </div>
  );
};

这种模式代码清晰,易于测试,并且在不增加任何复杂性的情况下,充分利用了 redux-lite 类型安全的、函数式的 dispatchers 的全部功能。

性能

redux-lite 为高性能而设计。其内部的 reducer 使用了智能的值比较机制,当数据没有实际改变时,可以有效避免不必要的 state 更新和组件重新渲染。

在一个模拟真实世界场景、重复调用 dispatch 函数的基准测试中,redux-lite 能够实现:

  • 10,000 次计数器更新约需 16.43 毫秒 (每次更新 0.0016 毫秒)
  • 1,000 次数组推送操作约需 3.99 毫秒 (每次操作 0.0040 毫秒)
  • 10,000 次对象属性更新约需 15.48 毫秒 (每次更新 0.0015 毫秒)
  • 10,000 次部分对象更新约需 15.15 毫秒 (每次更新 0.0015 毫秒)
  • 1,000 次深度嵌套更新约需 3.42 毫秒 (每次更新 0.0034 毫秒)

这证明了它即使在包含 React 渲染生命周期的开销下,依然拥有卓越的速度。

与 Redux 对比

| 特性 | Redux (使用 Redux Toolkit) | redux-lite | | ------------ | -------------------------------------------------------- | ------------------------------------------------------------- | | 模板代码 | 需要 createSlice, configureStore, actions, reducers。 | 几乎为零。定义一个对象,就能得到你需要的一切。 | | API 表面 | API 较多,涉及切片、thunks、selectors 等多个概念。 | 极简。只有 initiate, optional 和返回的 hook。 | | 类型安全 | 良好,但 thunks 和 selectors 可能需要手动指定类型。 | 端到端。所有类型都从初始 store 自动推断。 | | 性能 | 高性能,但依赖记忆化的 selectors (reselect)。 | 内置。如果值深度相等,自动阻止更新。 | | 依赖 | 需要 @reduxjs/toolkitreact-redux。 | 。仅 react 作为对等依赖。 | | 简洁性 | 学习曲线较陡峭。 | 极其简单。如果你了解 React hooks,你就懂得 redux-lite。 |

redux-lite 让测试使用 store 的组件变得极其简单。ReduxLiteProvider 接受一个 initStore prop,它允许你提供一个深度的部分状态(deep partial state)来覆盖测试的默认初始状态。

这意味着你不再需要派发 action 来设置你期望的测试状态。你可以直接用它所需要的确切状态来渲染你的组件。

示例

以下是如何轻松地为你的组件模拟状态:

import { render } from '@testing-library/react';
import { initiate } from '@oldbig/redux-lite';
import React from 'react';

// 假设这是你的初始 store 配置
const STORE_DEFINITION = {
  user: { name: 'Guest', age: 0, profile: { theme: 'dark' } },
  isAuthenticated: false,
};

const { ReduxLiteProvider, useReduxLiteStore } = initiate(STORE_DEFINITION);

// --- 你的组件 ---
const UserProfile: React.FC = () => {
  const { user } = useReduxLiteStore();
  return <div>欢迎, {user.name} (主题: {user.profile.theme})</div>;
};

// --- 你的测试 ---
it('应该显示已认证用户的名称,并覆盖了 profile', () => {
  const { getByText } = render(
    <ReduxLiteProvider initStore={{ user: { name: 'Alice', profile: { theme: 'light' } }, isAuthenticated: true }}>
      <UserProfile />
    </ReduxLiteProvider>
  );

  // 组件会使用你提供的确切状态进行渲染
  expect(getByText('欢迎, Alice (主题: light)')).toBeInTheDocument();
});

it('应该浅合并 user 切片并替换嵌套对象', () => {
  const { getByText } = render(
    <ReduxLiteProvider initStore={{ user: { name: 'Bob' } }}>
      <UserProfile />
    </ReduxLiteProvider>
  );

  // user.name 被覆盖,user.age 保持默认,user.profile 不受影响
  expect(getByText('欢迎, Bob (主题: dark)')).toBeInTheDocument();
});

你可以轻松地在不同状态下测试你的组件,而无需任何复杂的设置或模拟。

redux-lite 提供了与 Redux DevTools 浏览器插件 的可选集成,为您提供顶级的调试体验,包括 action 追踪和时间旅行调试。

该功能默认禁用,在不使用时性能开销为零

如何启用

要启用此功能,只需在 initiate 函数中传入 devTools 选项。

// 使用默认配置启用
const { ReduxLiteProvider, useReduxLiteStore } = initiate(STORE_DEFINITION, {
  devTools: true
});

// 或为您的 store 实例提供一个名称和其他选项
const { ReduxLiteProvider, useReduxLiteStore } = initiate(STORE_DEFINITION, {
  devTools: {
    name: 'MyAppStore',
    maxAge: 50, // 限制存储的 action 数量
    latency: 500 // 批量处理 action 的延迟时间(毫秒)
  }
});

安装步骤

  1. 为您的浏览器安装 Redux DevTools 插件:
  2. 如上所示,在您的代码中启用该功能。
  3. 打开浏览器的开发者工具,找到 "Redux" 标签页。

Redux DevTools 截图

redux-lite 支持与 Redux 几乎完全相同的中间件 API,允许您扩展 store 的能力,用于日志记录、处理异步 action 等。

如何使用中间件

在调用 initiate 时,在 options 对象中传递一个中间件数组。

import { initiate, Middleware } from '@oldbig/redux-lite';

const logger: Middleware<any> = (api) => (next) => (action) => {
  console.log('dispatching', action);
  const result = next(action);
  console.log('next state', api.getState());
  return result;
};

const { ReduxLiteProvider, useReduxLiteStore } = initiate(STORE_DEFINITION, {
  middlewares: [logger]
});

编写自定义中间件

中间件是一个高阶函数,其签名如下:

type Middleware<S> = (api: MiddlewareAPI<S>) => (next: (action: Action<S>) => Action<S>) => (action: Action<S>) => Action<S>;
  • api: 一个包含两个方法的对象:
    • getState(): 返回当前 state。
    • dispatch(action): 派发一个 action。这会将 action 发送到中间件链的开始。
  • next: 一个将 action 传递给链中的下一个中间件的函数。您必须在某个时刻调用 next(action),以确保 action 最终能到达 reducer。
  • action: 正在派发的 action。

重要的中间件最佳实践

  1. 避免无限循环:在中间件中调用 api.dispatch(action) 会将 action 重新发送到中间件链的开头。为了避免无限循环,必须将 api.dispatch 调用放在适当的条件块中:
const conditionalDispatchMiddleware: Middleware<any> = (api) => (next) => (action) => {
  // 错误的做法 - 这会导致无限循环
  // api.dispatch({ type: 'someAction', payload: 'data', isPartial: false });
  
  // 正确的做法 - 将 dispatch 放在条件块中
  if (action.type === 'user_login') {
    api.dispatch({ type: 'notifications_show', payload: '欢迎!', isPartial: false });
  }
  
  return next(action);
};
  1. 错误处理:将中间件逻辑包装在 try-catch 块中,以防止一个有问题的中间件破坏整个链条。

  2. 性能:尽量减少中间件中的重量级计算,因为它们是同步运行的,可能会阻塞 UI 线程。

示例

支持本项目

如果您觉得 redux-lite 对您有帮助,并希望支持本项目的开发,请考虑:

非常感谢您的支持!

许可证

本项目采用 MIT 许可证。