React 中的动态配置 — 无卡顿的 Feature Flags

发布: (2026年1月10日 GMT+8 02:55)
7 min read
原文: Dev.to

看起来您只提供了来源链接,而没有贴出需要翻译的正文内容。请把要翻译的文本(除代码块和 URL 之外)粘贴在这里,我会按照要求将其翻译成简体中文并保留原有的格式。谢谢!

React 中特性标记的问题

大多数 React 应用以三种方式处理配置:

1. 构建时环境变量

const isNewCheckout = process.env.REACT_APP_NEW_CHECKOUT === "true";

function Checkout() {
  return isNewCheckout ?  : ;
}

要改动?重新构建并重新部署整个应用。

2. 从顶层传递 Props

function App() {
  const [flags, setFlags] = useState(null);

  useEffect(() => {
    fetch("/api/flags")
      .then((r) => r.json())
      .then(setFlags);
  }, []);

  if (!flags) return ;

  return ;
}

可行,但现在你得把 props 通过很多组件层层传递。使用 Context 可以解决层层传递的问题,但每次标记变化都会重新渲染所有内容。

3. 使用第三方 SDK 及其模式

import { useFlags } from "some-feature-flag-sdk";

function Checkout() {
  const { newCheckout } = useFlags();
  // …
}

更好,但现在你要为每月 300 美元的账单买单,并且受限于供应商的 API。

动态配置应有的体验

function Checkout() {
  const isNewCheckout = useConfig("new-checkout");

  return isNewCheckout ?  : ;
}

当有人在仪表盘中切换该值时:

  • 组件自动重新渲染
  • 无需页面刷新
  • 无需属性钻取
  • 完整的 TypeScript 支持

使用 Replane 构建

Replane 是开源的(MIT 许可证),正好实现了这些功能。

1. 包裹你的应用

import { ReplaneProvider } from "@replanejs/react";

function App() {
  return (
    }
    >
      
    
  );
}

Provider 通过 Server‑Sent Events 进行连接。配置只加载一次,然后实时流式推送更新。

2. 在任意位置读取配置

import { useConfig } from "@replanejs/react";

function Checkout() {
  const isNewCheckout = useConfig("new-checkout");
  const discountBanner = useConfig("checkout-banner-text");

  return (
    
      {discountBanner && }
      {isNewCheckout ?  : }
    
  );
}

该 Hook 只订阅所请求的配置键,因此只有使用该键的组件在配置变更时会重新渲染。

3. 使用上下文添加目标化

function Checkout() {
  const { user } = useAuth();

  const rateLimit = useConfig("api-rate-limit", {
    context: {
      userId: user.id,
      plan: user.subscription,
      country: user.country,
    },
  });

  // Premium 用户可能得到 10 000,免费用户得到 100
}

覆盖规则在 Replane 仪表盘中定义:

  • 如果 plan 等于 premium → 返回 10000
  • 如果 country 等于 DE → 返回 500
  • 默认 → 100

添加新规则时无需修改代码。

使其类型安全

通用 Hook 可以工作,但你可以收紧契约:

// config.ts
import { createConfigHook } from "@replanejs/react";

interface AppConfigs {
  "new-checkout": boolean;
  "checkout-banner-text": string | null;
  "api-rate-limit": number;
  "pricing-tiers": {
    free: { requests: number };
    pro: { requests: number };
  };
}

export const useAppConfig = createConfigHook();
// Checkout.tsx
import { useAppConfig } from "./config";

function Checkout() {
  // Autocomplete works, type is inferred
  const isNewCheckout = useAppConfig("new-checkout");
  //    ^? boolean

  const pricing = useAppConfig("pricing-tiers");
  //    ^? { free: { requests: number }; pro: { requests: number } }
}

配置名称拼写错误?TypeScript 会捕获。类型假设错误?TypeScript 也会捕获。

处理加载状态

选择适合您应用的策略:

选项 1 – Loader prop(默认)

}
>
  

显示加载指示,直到 所有 配置加载完成。简单,但会阻塞整个应用。

选项 2 – Suspense

}>
  
    
  

与 React 的 Suspense 集成。如果您已经在使用它进行数据获取,这将是理想选择。

选项 3 – Async mode with defaults


  

立即使用提供的默认值渲染。实际值在连接建立后会替换默认值。没有加载 UI,但值可能在首次渲染后“翻转”。

服务器端渲染 (SSR) 水合

// On server
import { Replane, getReplaneSnapshot } from "@replanejs/react";

const replane = new Replane();
await replane.connect({ baseUrl: "...", sdkKey: "..." });
const snapshot = replane.getSnapshot();   // ← server‑fetched snapshot
// Pass `snapshot` to the client via props or serialize it into HTML

// On client

  

客户端会立即从快照进行水合,然后再连接以获取实时更新。

何时使用

合适的场景

  • 用于渐进式发布的功能标记
  • 简单的 A/B 测试变体
  • 按用户或租户的定制
  • 市场营销想要调整的 UI 文本
  • 运营限制(速率限制、最大条目数、超时)
  • 用于事件响应的紧急开关

保持为构建时配置

  • API 端点(运行时不要更改)
  • 分析键(运行时不要更改)
  • 任何影响构建输出的内容

常见错误

1. 将所有内容放入动态配置

并非每个值都需要实时更新。如果在应用运行期间不会改变,就保持为静态。

2. 没有默认值

// Bad – crashes if the config server is down
const limit = useConfig("rate-limit");

// Good – works even before the connection is established

  {/* ... */}

3. 上下文放错位置

// Bad – creates a new object each render, breaks memoization
const value = useConfig("limit", { context: { userId: user.id } });

// Better – stable reference
const context = useMemo(() => ({ userId: user.id }), [user.id]);
const value = useConfig("limit", { context });

4. 忽视错误边界

import { ErrorBoundary } from "react-error-boundary";

Config failed to load}>
  
    
  

连接失败会抛出异常;使用错误边界捕获它们。

入门

npm install @replanejs/react

如果你自行托管 Replane,请将 baseUrl 指向你的实例。否则,免费套餐可在 cloud.replane.dev 使用。

import { ReplaneProvider, useConfig } from "@replanejs/react";

function App() {
  return (
    Loading…}
    >
      
    
  );
}

function Main() {
  const isEnabled = useConfig("feature-enabled");
  return Feature is {isEnabled ? "on" : "off"};
}

那个结账紧急开关?现在已经变成仪表盘里的一个切换按钮。产品团队可以自行切换。10 % 的灰度发布?只需改动一条规则,无需部署。当凌晨 2 点出现故障时,我可以直接在手机上禁用它。

有疑问吗? 留下评论或查看 GitHub 仓库

Back to Blog

相关文章

阅读更多 »

改进的环境变量 UI

环境变量 UI 现在更易于在共享和项目环境变量之间进行管理。您可以减少滚动时间,使用更大的点击目标,……

待办应用

引言 在完成我的第一个以逻辑为中心的项目 Counters 后,我想在复杂度上迈出下一步——不是通过改进 UI,而是通过...