在 React 代码库中添加功能标志
由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!
嗨👋
🤔 你是否曾经遇到过这种情况:你希望先向少数用户推出某个功能,然后根据反馈/分析结果将其推广到所有用户?或者你的团队开发了一个大型功能,但市场/产品团队却说我们暂时不会发布?
😖 你最终会创建一个单独的功能分支,并试图让它与主分支保持同步。但这还没完。几周后,你想发布这个功能。现在,你又得重新触发部署。移动应用的情况更糟,因为完整的发布流程需要 2-4 天。
😭 哦!等等?你发现了一个问题。你想阻止用户使用该功能。祝你好运!
👌 为了避免开发者们遇到类似的情况,我们推出了功能开关!它不仅对开发者有用,对市场营销、产品和销售团队也很有帮助。
什么是功能标志?
我喜欢 LaunchDarkly 的定义。
功能开关是一种软件开发流程/模式,用于在不部署代码的情况下远程启用或禁用功能。新功能可以在不向用户可见的情况下部署。功能开关有助于将部署与发布解耦,从而让您可以管理功能的完整生命周期。
特征标志可用于:
- 进行A/B测试。
- 管理 Beta 测试项目。
- 减少多次部署或回滚。
- 提供基于角色的访问权限。
- 通过先向较小群体推出功能,最大限度地减少发布失败。
一旦开始使用功能标志,就再也回不去了。
在 React 中添加功能标志
本实现使用了 React ContextAPI。在继续之前,请确保您已了解其基础知识。
我们先举个例子:
假设你负责一个大型网站/应用程序的💰支付网关。该网站/应用程序最近新增了两种支付方式:Apple Pay 和 Google Pay。
作为一名效率极高的开发者,你很快就完成了这两项集成,但市场团队希望将 Google Pay 的上线时间推迟几周。Apple Pay 将于明天正式上线。
你肯定不想维护一个单独的分支,然后在几周后重新部署。所以,你选择添加功能开关。😎
首先,让我们从创建功能标志的上下文开始。
// /contexts/FeatureFlags.js
export const FeatureFlags = React.createContext({});
现在,让我们创建一个 Provider 来包装我们的 React DOM 树。
// /contexts/FeatureFlags.js
export const FeatureFlags = React.createContext({});
export const FeatureFlagsProvider = ({ children }) => {
const [features, setFeatures] = React.useState({});
return (
<FeatureFlags.Provider value={{ features }}>
{children}
</FeatureFlags.Provider>
);
};
我们的上下文已经全部设置完毕,只剩下最后几项工作了。现在,我们可以用这个提供程序来封装这棵树。
// index.js
// ... imports here
import App from "./App";
import { FeatureFlagsProvider } from "./contexts/FeatureFlags";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<FeatureFlagsProvider>
<App />
</FeatureFlagsProvider>
</StrictMode>
);
现在,我们只需要实现我们的功能。我已经用 Fastify 创建了一个示例 API,您可以忽略这部分。
// enabling cors for codesandbox
fastify.register(require("fastify-cors"), {
origin: /\.csb\.app$/
});
// feature flags route
fastify.get("/feature-flags", function(request, reply) {
const features = {
isGooglePayEnabled: true,
isApplePayEnabled: false
}
reply.send({ features });
});
回到我们的上下文文件,让我们获取特征。
// /contexts/FeatureFlags.js
import { fetchFeatures } from 'api'
export const FeatureFlags = React.createContext({});
export const FeatureFlagsProvider = ({ children }) => {
const [isLoading, setIsLoading] = React.useState(true);
const [features, setFeatures] = React.useState({});
React.useEffect(() => {
(async () => {
try {
const data = await fetchFeatures();
if (data.features) {
setFeatures(data.features);
}
} catch (err) {
console.log(err);
} finally {
setIsLoading(false);
}
})();
}, []);
return (
<FeatureFlags.Provider value={{ features }}>
{isLoading ? "Loading..." : children}
</FeatureFlags.Provider>
);
};
我们刚刚useEffect为应用程序添加了加载状态。
大功告成!🎉
最后一步是将它应用到我们的组件中。
// components/PaymentOptions.js
import { FeatureFlags } from "contexts/FeatureFlags";
const PaymentOptions = () => {
const { features } = React.useContext(FeatureFlags);
const handleClick = () => alert("Payment successful!");
return (
<>
<button className="btn" onClick={handleClick}>
Credit Card
</button>
{features.isApplePayEnabled && (
<button className="btn" onClick={handleClick}>
Apple Pay
</button>
)}
{features.isGooglePayEnabled && (
<button className="btn" onClick={handleClick}>
Google Pay
</button>
)}
</>
);
};
export default PaymentOptions;
🚀 现在,我们可以启动这个应用程序,并完全控制新创建的功能。
👏 我们可以随时启用 Google Pay,用户会立即看到。如果出现问题,我们可以禁用这两种支付方式。
reply.send({ isGooglePayEnabled: false, isApplePayEnabled: false});
临走前还有最后一点,这个实现方案只是最基本的功能。你可以根据团队的需求进行扩展。我目前想到的一些改进方向包括:
- 添加一个
FeatureFlag组件,该组件接受一个属性feature,并根据该属性隐藏或渲染子组件。
<FeatureFlag feature="isGooglePayEnabled">
<button onClick={handlePayment}>Google Pay</button>
</FeatureFlag>
- 添加缓存和回退机制。如果 API 调用失败怎么办?在这种情况下,我们可以回退到缓存版本。这个功能真的很有意思。😉
🔗 哦,这里是codesandbox 链接,如果你想自己动手试试的话。
就这些啦!👋
希望这篇文章对你有所帮助。也欢迎分享给其他人。
🤙 如果你想和我聊任何事,请在Twitter或LinkedIn上私信我。
文章来源:https://dev.to/thesanjeevsharma/adding-feature-flags-to-your-react-codebase-22a8
