跳到主要内容

10 篇博文 含有标签「版本发布」

Reactive Data Client 的新版本

查看所有标签

v0.19: Coding Agents, Batch Controller.set(), Faster TypeScript

· 阅读需 37 分钟
Nathaniel Tucker
Reactive Data Client 的作者

v0.19 makes coding agents a first-class way to build with Data Client: one prompt sets up your app, and your agent follows skills that ship with the packages you installed. It also lets a Manager write a whole batch of streamed rows in one store update, type-checks endpoint code about 2x faster, and fixes a round of TypeScript and Vue issues.

Coding Agents:

  • One-prompt setup - Add the data-client plugin to Claude Code, Cursor or Codex and ask it to set up Data Client; it installs the packages, adds the provider and links the skills your app needs
  • Skills ship in the packages - npx --no data-client-skills links the skills your installed @data-client/* packages carry, so your agent follows the version you have instead of an older release
  • Agents keep using Data Client - Setup adds a note to AGENTS.md, and each skill names the everyday tasks it covers, so later sessions reach for Data Client instead of fetch in useEffect
  • Debug the running app - In development, globalThis.__DC__ gives your agent the store's Controller and recent actions from Cursor's browser, Chrome DevTools MCP, Playwright MCP or Claude in Chrome
  • Vue skills, llms.txt, and skills generated from these docs

New APIs:

Performance:

Other Improvements:

  • Fix TypeScript 7 module resolution for package exports; imports now resolve to declaration files (#4019)
  • renderDataHook() runs provider mount effects when the first render suspends (#4099)
  • Controller.set() values are typed by the schema, so ctrl.set(new schema.All(Todo), 42) is a TypeScript error (#4133)
  • Controller.set() deletes one entity with an Invalidate schema without the one-row batch workaround (#4230)
  • process() gets typed params in new RestEndpoint(), .extend() and resource().extend(), so a wrong path parameter is a TypeScript error instead of undefined at runtime (#4183, #4184)
  • getOptimisticResponse() returns are typed by the schema, so a wrong field type is a TypeScript error instead of a flash of bad data (#4198)
  • Vue useSuspense() and useLive() keep the previous data while new arguments load, instead of returning undefined (#4131)
  • Vue useSuspense() and useLive() send fetch errors after arguments change to onErrorCaptured() instead of an unhandled promise rejection (#4135)
  • Vue useSuspense(), useDLE() and useFetch() no longer refetch stale data on every store update, so a controller.set() is not overwritten (#4134)
  • Vue useSuspense() shows stale data on mount and refetches in the background, like React, instead of showing the <Suspense> fallback until the refetch finishes (#4169)
  • Vue useDLE() and useCache() keep expired invalidIfStale data through unrelated store updates instead of getting stuck loading (#4142)
  • Vue DataClientPlugin installs on Vue versions before 3.5 instead of throwing app.onUnmount is not a function (#4146)
  • Vue DataClientPlugin garbage collects by default, matching React's DataProvider, so you no longer need to pass gcPolicy; how it decides what to remove (#4148)
  • Vue composables accept getter arguments like () => ({ id: props.id }); they were typed to allow them but passed the function itself to the endpoint (#4115)
  • Fix Cannot find name 'NoInfer' and export type errors on TypeScript 4.x with skipLibCheck off (#4138)
  • Fix Entity, Endpoint, Union and RestEndpoint type errors on TypeScript 4.0–4.5 with skipLibCheck off (#4140)
  • Fix ',' expected errors importing @data-client/react/redux, @data-client/react/nextjs or @data-client/test on TypeScript 4.0–4.4 (#4232)
  • Fix act and renderHook from @data-client/test typed as any on TypeScript 4.0–4.4 (#4251)
  • React Native useSuspense(), useFetch() and useDLE() refetch on screen focus only when the current data is stale or invalid, instead of refetching fresh data on every refocus (#4378)
  • renderDataHook() works with React Native Testing Library 14 and stays synchronous, so renderDataHook() calls need no await (#4374)
  • Entity classes can be used where an EntityInterface is expected (#4149); prepare pk() overrides for a future release
  • useCache() and useDLE() return undefined for a deleted entity whose refetch failed, instead of a truthy Symbol that slipped past if (!data) checks; remove any workarounds (#4150)
  • Vue useFetch() keeps its data from being garbage collected while mounted, like useSuspense(), so a configured gcPolicy no longer evicts prefetched data that components read later (#4152)
  • Apps with Redux DevTools open no longer stutter in development on large stores or frequent updates; each update serializes 40-60x faster (#4163)
  • resource().extend() with an object of endpoint overrides keeps endpoints added earlier with .extend('name', options) in its type, so using them is no longer a TypeScript error (#4184)
  • RestEndpoint.push, unshift and remove accept the Entity's fields as the body when a Query or Lazy wraps the Collection, instead of only FormData (#4226)
  • Vue useFetch() is typed as the read-only Ref it returns, so promise.resolved (always undefined) is now a TypeScript error; read promise.value.resolved instead (#4114)
  • A Controller subclass passed to DataProvider or DataClientPlugin no longer needs a cast to type-check (#4228)
  • prepareStore()'s store can be passed to react-redux's <Provider> without a cast, and has dispatch() in its type (#4238)
  • resource().create accepts FormData without a cast, like update, partialUpdate and getList.push (#4235)
  • RestEndpoint.push, unshift and remove send an array body as JSON, so creating or removing several items at once no longer sends "[object Object]" to your server; resource().create takes an array too (#4262)
  • Polling keeps the schema of endpoints given pollFrequency with .extend(), so a polled useLive() list updates right away from mutations and optimistic updates instead of waiting for the next poll (#4437)
  • DataProvider only warns that it doesn't update during SSR when something fetches while the server renders, instead of on every server render, so pages that load data after mount (like Expo Router's static web output) stay quiet (#4449)
  • Server rendering with React 19 no longer logs "Uncaught suspense" when nothing suspended; React renders and discards fallbacks of large boundaries, and the warning now waits for a fallback that actually shows (#4450)
  • Vue createDataClient() is deprecated, since it's internal to DataClientPlugin; install the plugin with app.use(DataClientPlugin, options) instead of calling it directly (#4193)
  • Vue renderDataCompose()'s waitForNextUpdate() is deprecated, since it gives up silently after 1 second and lets a test pass while still suspended; await result instead, or await allSettled() after a change, which now also waits for fetches a prop change starts (unit testing composables, #4221)

Breaking Changes:

Get ready for future releases:

v0.18:Scalar、类型化文件下载、过滤感知的 Collection

· 阅读需 12 分钟
Nathaniel Tucker
Reactive Data Client 的作者

v0.18 聚焦于三件事:对随请求上下文变化的值进行更丰富的数据建模、更简单的类型化下载,以及更贴近真实 API 过滤条件的集合匹配。

新特性:

其他改进:

Fixtures
▶api/Company
import { Collection, Entity, RestEndpoint, Scalar } from '@data-client/rest';

export class Company extends Entity {
  id = '';
  name = '';
  price = 0;
  pct_equity = 0;
  shares = 0;
}

const PortfolioScalar = new Scalar({
  lens: args => args[0]?.portfolio,
  key: 'portfolio',
  entity: Company,
});
Company.schema = {
  pct_equity: PortfolioScalar,
  shares: PortfolioScalar,
};

export const getCompanies = new RestEndpoint({
  path: '/companies',
  searchParams: {} as { portfolio: string },
  // `portfolio` is a lens, not a filter — the returned Company list is the same
  // regardless of lens. Dropping it from `argsKey` collapses every portfolio to
  // one Collection pk, so `Collection.queryKey()` finds the list on every
  // switch and `useSuspense` reuses it without refetching.
  schema: new Collection([Company], { argsKey: () => ({}) }),
});

export const getPortfolioColumns = new RestEndpoint({
  path: '/companies/columns',
  searchParams: {} as { portfolio: string },
  schema: new Collection([PortfolioScalar], {
    argsKey: ({ portfolio }: { portfolio: string }) => ({ portfolio }),
  }),
});
▶CompanyGrid
▶PortfolioGrid
实时预览
Store▶

这个示例展示了同一批 Company 实体通过不同的投资组合视角进行渲染。稳定的 Entity 字段会被复用,而随视角变化的值则从独立的 Scalar 单元格中选取。

破坏性变更:

**在使用 AI 智能体?**安装迁移 skill——它会运行 codemod,为依赖参数的 schema 注册 argsKey,并处理 codemod 无法覆盖的情况(类字段、动态方法赋值、SFC <script> 块、自定义辅助函数):

npx skills add reactive/data-client --skill data-client-v0.18-migration
在终端中运行

否则,直接运行 codemod——它覆盖了常见情况:

npx jscodeshift -t https://dataclient.io/codemods/v0.18.js --extensions=ts,tsx,js,jsx src/
在终端中运行

v0.16:并行获取、Collection.move、Lazy

· 阅读需 13 分钟
Nathaniel Tucker
Reactive Data Client 的作者

新特性:

其他改进:

Collection.move 让你能够用一次操作轻松地在多个 Collection 之间移动实体:

import { useController } from '@data-client/react';
import { TaskResource, type Task } from './TaskResource';

export default function TaskCard({ task }: { task: Task }) {
  const handleMove = () => ctrl.fetch(
    TaskResource.getList.move,
    { id: task.id },
    { id: task.id, status: task.status === 'backlog' ? 'in-progress' : 'backlog' },
  );
  const ctrl = useController();
  return (
    <div className="listItem">
      <span style={{ flex: 1 }}>{task.title}</span>
      <button onClick={handleMove}>
        {task.status === 'backlog' ? '\u25bc' : '\u25b2'}
      </button>
    </div>
  );
}
实时预览

破坏性变更:

使用自动化 codemod 升级,它涵盖所有破坏性变更:

npx jscodeshift -t https://dataclient.io/codemods/v0.16.js --extensions=ts,tsx,js,jsx src/
在终端中运行

v0.15:Vue 3、Collection.remove、useDebounce 升级

· 阅读需 10 分钟
Nathaniel Tucker
Reactive Data Client 的作者

新平台:

  • Vue 3,提供完整的组合式函数:useSuspense、useCache、useDLE、useController、useQuery

新特性:

性能:

破坏性变更:

使用自动化 codemod 完成常见破坏性变更的升级:

npx jscodeshift -t https://dataclient.io/codemods/v0.15.js --extensions=ts,tsx,js,jsx src/
在终端中运行

v0.14:精简的 Manager API、性能提升与 GC

· 阅读需 13 分钟
Nathaniel Tucker
Reactive Data Client 的作者

Manager:

Manager API 得到了精简——更扁平的 action 结构降低了代码复杂度,并通过减少不同的内联缓存条目来提升性能;同时更简单的配置 API 让自定义更加愉快。

性能:

Schema/数据建模:

测试:

垃圾回收:

  • GCPolicy——自动回收过时数据

破坏性变更:

v0.13:NextJS App Router、Expo、React 19、Redux

· 阅读需 9 分钟
Nathaniel Tucker
Reactive Data Client 的作者

平台:

测试:

以下包已并入 @data-client/react:

  • Redux
  • Hooks

其他改进

破坏性变更:

备注

由于发布事故,0.12 版本被跳过了。

v0.11 Query、Queryable 与 useQuery

· 阅读需 12 分钟
Nathaniel Tucker
Reactive Data Client 的作者

除了规范化状态带来的性能和数据完整性收益之外,我们还获得了一项额外的好处:能够安全地直接访问并查询 store。

在本次发布中,我们围绕 Queryable Schema 的概念统一并简化了这一功能。它们包括 Entity、All、Collection、Query 和 Union

这一变化最大的影响是引入了新的 hook useQuery(),它允许使用 Queryable Schema 直接查找 store。

class User extends Entity {
username = '';
id = '';
groupId = '';
pk() {
return this.id;
}
static index = ['username' as const];
}

const bob = useQuery(User, { username: 'bob' });
const bob = useQuery(User, { id: '5' });

同样地,我们可以通过 controller.get 在 controller 和 snapshot 中查找 Queryable

const bob = snapshot.get(User, { username: 'bob' });

此外,我们还投入了更多的性能改进,使大多数操作的性能提升了约 2 倍,而 Query 则快了 16 倍。

迁移指南

破坏性变更:

其他亮点:

  • useCache() 接受带有 sideEffects 的 Endpoint(例如 Resource.update)
  • 允许 Entity.pk() 返回数字。
  • 2 到 16 倍的性能改进

v0.10:一致的 null 处理、URL 工具函数

· 阅读需 4 分钟
Nathaniel Tucker
Reactive Data Client 的作者

本次发布聚焦于一致性和可扩展性。null 处理现在在所有 schema 类型中保持统一,使数据转换更可预测。新的 URL 工具函数支持自定义 URL 构造和搜索参数编码。

class MyEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
searchToString(searchParams) {
// Use qs library for complex nested object encoding
return qs.stringify(searchParams);
}
}
迁移指南

破坏性变更:

其他改进:

v0.9:DevTools、Resource.extend 与清理遗留 API

· 阅读需 8 分钟
Nathaniel Tucker
Reactive Data Client 的作者

本次发布聚焦于开发者体验:在开发模式下显示的 devtools 按钮、更好的 DevTools 历史记录,以及 controller.expireAll() 和 controller.fetchIfStale() 等新的 controller 方法。

Resource.extend() 提供了三种强大的方式来自定义 resource——添加新的 endpoint、覆盖已有的 endpoint,或从基础 endpoint 派生。

const UserResource = createResource({
path: '/users/:id',
schema: User,
}).extend('current', {
path: '/users/current',
});
迁移指南

破坏性变更:

v0.2 Controller.fetch、异步 getHeaders、Collection

· 阅读需 6 分钟
Nathaniel Tucker
Reactive Data Client 的作者

Collection 让数组和对象可以通过 push 或 unshift 新成员来轻松扩展。它的名字来自 Backbone Collections。

Collection 现在是 Resource.getList 的默认 schema。

import { useController } from '@data-client/react';
import { TodoResource } from './TodoResource';

export default function CreateTodo({ userId }: { userId: number }) {
  const ctrl = useController();
  const handleKeyDown = async e => {
    if (e.key === 'Enter') {
      ctrl.fetch(TodoResource.getList.push, {
        userId,
        title: e.currentTarget.value,
        id: Math.random(),
      });
      e.currentTarget.value = '';
    }
  };
  return (
    <div className="listItem nogap">
      <label>
        <input type="checkbox" name="new" checked={false} disabled />
        <input type="text" onKeyDown={handleKeyDown} />
      </label>
    </div>
  );
}
实时预览

升级相当简单,因为我们引入了 @data-client/rest/next 和 @data-client/react/next,以便渐进式地采用本版本中变更的新 API。这使得实际的升级只是简单地重命名一下导入路径。

其他亮点包括

完整细节请继续阅读: