@arnosaine/is (美国英语)
特性旗, (中文(简体) ), 基于任务和权限的渲染, (中文(简体) ), A/B 测试,实验特征,以及 更多 在反应.
关键特性
- 有条件渲染组件的宣告语法
- 支持各种数据来源,包括上下文、钩子和API响应
- 可自定义默认条件和动态值
创建 习惯 <Is> 构成部分和 useIs 任何有条件渲染用途的钩子 .
或者创造 快捷键组件 喜欢 <IsAuthenticated>, (中文(简体) ), <HasRole> 编号 : <Role> 财务报告和审定财务报表 <HasPermission> 编号 : <Can>和钩子一样 useIsAuthenticated, (中文(简体) ), useHasRole 编号 : useRole 财务报告和审定财务报表 useHasPermission 编号 : useCan,用于最常见的案件.
如果您正在使用 React 路由器或 Remix, 请使用 createFromLoader 创建 loadIs 装载机和公用设备功能 authenticated。 。 。 .
目录
演示文稿
开始
在这里,我们创建一个组件和一个钩子来检查用户是否经过认证,或者是否启用实验特性. 我们把用户从 UserContext。实验特性启用于 preview.* 例如,http://preview.localhost:5173.
创建 <Is> 打开 useIs
./is.ts数字 :
import { create } from "@arnosaine/is";
import { use } from "react";
import UserContext from "./UserContext";
const [Is, useIs] = create(function useValues() {
const user = use(UserContext);
const isExperimental = location.hostname.startsWith("preview.");
// Or, get the value from the user context, a hook call, or another
// source.
// const isExperimental = user?.roles?.includes("developer") ?? false;
return {
// The property names become the prop and hook param names.
// Allowed types: boolean | number | string | boolean[] | number[] |
// string[].
authenticated: Boolean(user),
experimental: isExperimental,
// ...
};
});
export { Is, useIs };
使用 <Is> 打开 useIs
import { Is, useIs } from "./is";
// Component
<Is authenticated fallback="Please log in">
Welcome back!
</Is>;
<Is experimental>
<SomeExperimentalFeature />
</Is>;
// Hook
const isAuthenticated = useIs({ authenticated: true }); // boolean
const isExperimental = useIs({ experimental: true }); // boolean
i️ 在条件代码变大时考虑懒惰加载. 否则,有条件的代码就包含在捆绑中,即使没有被渲染出. 此外,如果未交代的代码应保密,则不要使用这种方法.
想法
特性旗
硬码特性
硬码特性列表也许是最简单的方法,仍然可以改进项目工作流程. 例如,可以在 release 分支,同时可以在 main 或者说 feature 树枝.
./is.ts数字 :
import { create } from "@arnosaine/is";
const [Is, useIs] = create(function useValues() {
return {
// Hardcoded features
feature: ["feature-abc", "feature-xyz"] as const,
// ...
};
});
export { Is, useIs };
构建时间特征
从构建时的环境变量读取已启用的特性 :
.env数字 :
FEATURES=["feature-abc","feature-xyz"]
./is.ts数字 :
import { create } from "@arnosaine/is";
const [Is, useIs] = create(function useValues() {
return {
// Read the enabled features from an environment variable at build
// time
feature: JSON.parse(import.meta.env.FEATURES ?? "[]"),
// ...
};
});
export { Is, useIs };
运行时间特征
运行时从配置文件或 API 读取启用的特性 :
public/config.json数字 :
{
"features": ["feature-abc", "feature-xyz"]
}
./is.ts数字 :
import { create } from "@arnosaine/is";
import { use } from "react"; // React v19
async function getConfig() {
const response = await fetch(import.meta.env.BASE_URL + "config.json");
return response.json();
}
const configPromise = getConfig();
const [Is, useIs] = create(function useValues() {
const config = use(configPromise);
return {
feature: config.features,
// ...
};
});
export { Is, useIs };
A/B 测试,实验特征
启用基于其他值的一些特性 :
./is.ts数字 :
import { create } from "@arnosaine/is";
const [Is, useIs] = create(function useValues() {
const features = [
/*...*/
];
// Enable some features only in development mode:
if (import.meta.env.MODE === "development") {
features.push("new-login-form");
}
// Or, enable some features only on `dev.*` domains, for example, at
// http://dev.localhost:5173:
if (location.hostname.startsWith("dev.")) {
features.push("new-landing-page");
}
return {
feature: features,
// ...
};
});
export { Is, useIs };
在预览模式中启用所有特性
./is.ts数字 :
import { create } from "@arnosaine/is";
const [Is, useIs] = create(function useValues() {
const features = [
/*...*/
];
const isPreview = location.hostname.startsWith("preview.");
return {
feature: isPreview
? // In preview mode, all features are enabled.
// Typed as string to accept any string as a feature name.
(true as unknown as string)
: features,
// ...
};
});
export { Is, useIs };
使用量
如何界定特征并不重要 <Is> 财务报告和审定财务报表 useIs 同为:
import { Is, useIs } from "./is";
// Component
<Is feature="new-login-form" fallback={<OldLoginForm />}>
<NewLoginForm />
</Is>;
// Hook
const showNewLoginForm = useIs({ feature: "new-login-form" });
域的应用备选案文
在浏览器中,
location.hostname是常数,而location.hostname === "example.com" && <p>This appears only on example.com</p>可能是你所需要的一切。 您仍可选择使用Is模式来表示一致性以及服务器侧动作和加载器.
./is.ts数字 :
import { create } from "@arnosaine/is";
const [Is, useIs] = create(function useValues() {
const domain = location.hostname.endsWith(".localhost")
? // On <domain>.localhost, get subdomain.
location.hostname.slice(0, -".localhost".length)
: location.hostname;
return {
variant: domain,
// ...
};
});
export { Is, useIs };
使用量
import { Is, useIs } from "./is";
// Component
<Is variant="example.com">
<p>This appears only on example.com</p>
</Is>;
// Hook
const isExampleDotCom = useIs({ variant: "example.com" });
用户角色和权限
./is.ts数字 :
import { create } from "@arnosaine/is";
import { use } from "react";
import UserContext from "./UserContext";
const [Is, useIs] = create(function useValues() {
const user = use(UserContext);
return {
authenticated: Boolean(user),
role: user?.roles, // ["admin", ...]
permission: user?.permissions, // ["create-articles", ...]
// ...
};
});
export { Is, useIs };
使用量
import { Is, useIs } from "./is";
// Component
<Is authenticated fallback="Please log in">
Welcome back!
</Is>;
<Is role="admin">
<AdminPanel />
</Is>;
<Is permission="update-articles">
<button>Edit</button>
</Is>;
// Hook
const isAuthenticated = useIs({ authenticated: true });
const isAdmin = useIs({ role: "admin" });
const canUpdateArticles = useIs({ permission: "update-articles" });
是特定的日子
./is.ts数字 :
import { create } from "@arnosaine/is";
import { easter } from "date-easter";
import { isSameDay } from "date-fns";
const [Is, useIs] = create(function useValues() {
return {
easter: isSameDay(new Date(easter()), new Date()),
// ...
};
});
export { Is, useIs };
使用量
import { Is, useIs } from "./is";
// Component
<Is easter>🐣🐣🐣</Is>;
// Hook
const isEaster = useIs({ easter: true });
快捷键组件和钩
import { create } from "@arnosaine/is";
import { use } from "react";
import UserContext from "./UserContext";
const [IsAuthenticated, useIsAuthenticated] = create(
function useValues() {
const user = use(UserContext);
return { authenticated: Boolean(user) };
},
{ authenticated: true } // Default props / hook params
);
<IsAuthenticated fallback="Please log in">Welcome back!</IsAuthenticated>;
const isAuthenticated = useIsAuthenticated();
import { create, toBooleanValues } from "@arnosaine/is";
import { use } from "react";
import UserContext from "./UserContext";
const [HasRole, useHasRole] = create(function useValues() {
const user = use(UserContext);
// Create object { [role: string]: true }
return Object.fromEntries((user?.roles ?? []).map((role) => [role, true]));
});
<HasRole admin>
<AdminPanel />
</HasRole>;
const isAdmin = useHasRole({ admin: true });
// Same with toBooleanValues utility
const [Role, useRole] = create(() => toBooleanValues(use(UserContext)?.roles));
<Role admin>
<AdminPanel />
</Role>;
const isAdmin = useRole({ admin: true });
import { create, toBooleanValues } from "@arnosaine/is";
import { use } from "react";
import UserContext from "./UserContext";
const [HasPermission, useHasPermission] = create(function useValues() {
const user = use(UserContext);
// Create object { [permission: string]: true }
return Object.fromEntries(
(user?.permissions ?? []).map((permission) => [permission, true])
);
});
<HasPermission update-articles>
<button>Edit</button>
</HasPermission>;
const canUpdateArticles = useHasPermission({ "update-articles": true });
// Same with toBooleanValues utility
const [Can, useCan] = create(() =>
toBooleanValues(use(UserContext)?.permissions)
);
<Can update-articles>
<button>Edit</button>
</Can>;
const canUpdateArticles = useCan({ "update-articles": true });
用于非常特定用途
import { create } from "@arnosaine/is";
import { use } from "react";
import UserContext from "./UserContext";
const [CanUpdateArticles, useCanUpdateArticles] = create(
function useValues() {
const user = use(UserContext);
return {
updateArticles: user?.permissions?.includes("update-articles") ?? false,
};
},
{ updateArticles: true } // Default props / hook params
);
<CanUpdateArticles>
<button>Edit</button>
</CanUpdateArticles>;
const canUpdateArticles = useCanUpdateArticles();
装入器( 反应路由器 / Remix)
设置
-
创建
<Is>, (中文(简体) ),useIs打开loadIs使用createFromLoader。 。 。 ../app/is.ts数字 :import { createFromLoader } from "@arnosaine/is"; import { loadConfig, loadUser } from "./loaders"; const [Is, useIs, loadIs] = createFromLoader(async (args) => { const { hostname } = new URL(args.request.url); const isPreview = hostname.startsWith("preview."); const user = await loadUser(args); const config = await loadConfig(args); return { authenticated: Boolean(user), feature: config?.features, preview: isPreview, role: user?.roles, // ... }; }); export { Is, useIs, loadIs };./app/root.tsx数字 : -
返回时
...is从根loader编号 :clientLoader。见 选项 以使用其他路线.import { loadIs } from "./is"; export const loader = async (args: LoaderFunctionArgs) => { const is = await loadIs(args); return { ...is, // ...other loader data... }; };
i️ 根
ErrorBoundary无法访问根loader数据。 从根起Layout导出与 root 共享ErrorBoundary,如果使用<Is>或者说useIs输入Layout导出,考虑将所有路由前缀为_.(路径)和使用ErrorBoundary内routes/_.tsx在到达根之前捕捉出错ErrorBoundary。 。 。 .
使用 loadIs
import { loadIs } from "./is";
// Or clientLoader
export const loader = async (args: LoaderFunctionArgs) => {
const is = await loadIs(args);
const isAuthenticated = is({ authenticated: true });
const hasFeatureABC = is({ feature: "feature-abc" });
const isPreview = is({ preview: true });
const isAdmin = is({ role: "admin" });
// ...
};
公用事业
./app/utils/auth.tsx数字 :
import { loaderFunctionArgs } from "@remix-run/node";
import { loadIs } from "./is";
export const authenticated = async (
args: LoaderFunctionArgs,
role?: string | string[]
) => {
const is = await loadIs(args);
// Ensure user is authenticated
if (!is({ authenticated: true })) {
throw new Response("Unauthorized", {
status: 401,
});
}
// If the optional role parameter is available, ensure the user has
// the required roles
if (!is({ role })) {
throw new Response("Forbidden", {
status: 403,
});
}
};
import { authenticated } from "./utils/auth";
export const loader = async (args: LoaderFunctionArgs) => {
await authenticated(args, "admin");
// User is authenticated and has the role "admin".
// ...
};
API 密码
create
打电话 create 宣布: Is 构成部分和 useIs 钩.
const [Is, useIs] = create(useValues, defaultConditions?);
名称 Is 财务报告和审定财务报表 useIs 建议采用多用途组件和钩子。 对于单用途,可以相应命名. 可选 defaultConditions 参数对于单一目的的执行也往往有用.
const [IsAuthenticated, useIsAuthenticated] = create(
() => {
// Retrieve the user. Since this is a hook, using other hooks and
// context is allowed.
const user = { name: "Example" }; // Example: use(UserContext)
return { authenticated: Boolean(user) };
},
{ authenticated: true }
);
参数
useValues: 获取并计算电流的反向钩values用于比较逻辑.- 选项
defaultConditions: 默认道具/参数Is财务报告和审定财务报表useIs。 。 。 . - 选项
options: 用于配置行为的一个选项对象.- 选项
method(单位:千美元)"every" | "some": 默认 :"some"。指定如何匹配数组类型值和条件。 使用"some"仅要求一些条件与数值相匹配,或"every"要求所有条件匹配.
- 选项
回返
createFromLoader
打电话 createFromLoader 宣布: Is 构成部分 useIs 钩子和 loadIs 加载器.
const [Is, useIs, loadIs] = createFromLoader(loadValues, defaultConditions?, options?);
名称 Is, (中文(简体) ), useIs 财务报告和审定财务报表 loadIs 建议采用多用途部件、钩和装载器。 对于单用途,可以相应命名. 可选 defaultConditions 参数对于单一目的的执行也往往有用.
const [IsAuthenticated, useIsAuthenticated, loadIsAuthenticated] =
createFromLoader(
async (args) => {
// Retrieve the user. Since this is a loader, using await and
// other loaders is allowed.
const user = await loadUser(args);
return { authenticated: Boolean(user) };
},
{ authenticated: true }
);
参数
loadValues: 反应路由器 / Remix 加载器函数,用于获取和计算电流values用于比较逻辑.- 选项
defaultConditions: 默认道具/参数Is, (中文(简体) ),useIs财务报告和审定财务报表is。 。 。 . - 选项
options: 用于配置行为的一个选项对象.- 选项
method(单位:千美元)"every" | "some": 默认 :"some"。指定如何匹配数组类型值和条件。 使用"some"仅要求一些条件与数值相匹配,或"every"要求所有条件匹配. - 选项
prop默认值 :"__is_values"。 。 。 .is对象(功能)属性,预计将在根加载器数据中返回. - 选项
routeId: 默认: 根路由 ID ("root"或者说"0") (中文(简体) ). 提供is.__is_values从它的装载器。 示例"routes/admin"。 。 。 .
- 选项
回返
createFromLoader 返回包含 Is 构成部分 useIs 钩子和 loadIs 加载器.
<Is>
道具
...conditions: 条件与defaultConditions然后比较useValues编号 :loadValues返回值。 如果给出了多个条件,所有条件都必须与其相应的值相匹配. 对于任何阵列类型条件:- 如果相应的值也是数组和
options.method实值"some"(默认),值数组必须至少包含一个条件项. 若为options.method实值"every",值阵列必须包括所有条件项. - 如果对应的值不是数组,则该值必须是条件项之一.
- 如果相应的值也是数组和
- 选项
children: 如果所有条件都相符, 您打算做的 UI . - 选项
fallback: 如果某些条件不匹配, 您打算做的 UI .
使用量
<Is authenticated fallback="Please log in">
Welcome back!
</Is>
<IsAuthenticated fallback="Please log in">Welcome back!</IsAuthenticated>
useIs
参数
conditions: 条件与defaultConditions然后比较useValues编号 :loadValues返回值。 如果给出了多个条件,所有条件都必须与其相应的值相匹配. 对于任何阵列类型条件:- 如果相应的值也是数组和
options.method实值"some"(默认),值数组必须至少包含一个条件项. 若为options.method实值"every",值阵列必须包括所有条件项. - 如果对应的值不是数组,则该值必须是条件项之一.
- 如果相应的值也是数组和
回返
useIs 返回时 true 如果所有条件都相符, false 否则.
使用量
const isAuthenticated = useIs({ authenticated: true });
const isAuthenticated = useIsAuthenticated();
loadIs
参数
args: 反应路由器/重混LoaderFunctionArgs, (中文(简体) ),ActionFunctionArgs, (中文(简体) ),ClientLoaderFunctionArgs,或ClientActionFunctionArgs。 。 。 .
回返
loadIs 返回 a Promise 决定 is 函数.
使用量
export const loader = async (args: LoaderFunctionArgs) => {
const is = await loadIs(args);
const authenticated = await loadIsAuthenticated(args);
const isAuthenticated = is({ authenticated: true });
const isAuthenticated = authenticated();
// ...
};
is
is 函数是正在等待的调用返回值 loadIs。 。 。 .
参数
conditions: 条件与defaultConditions然后比较useValues编号 :loadValues返回值。 如果给出了多个条件,所有条件都必须与其相应的值相匹配. 对于任何阵列类型条件:- 如果相应的值也是数组和
options.method实值"some"(默认),值数组必须至少包含一个条件项. 若为options.method实值"every",值阵列必须包括所有条件项. - 如果对应的值不是数组,则该值必须是条件项之一.
- 如果相应的值也是数组和
回返
is 返回 a true 如果所有条件都相符, false 否则.
使用量
单位 root.tsx 您也必须返回 ...is 从 loader 编号 : clientLoader。见 选项 以使用其他路线.
export const loader = async (args: LoaderFunctionArgs) => {
const is = await loadIs(args);
return {
...is,
// ...other loader data...
};
};
toBooleanValues
打电话 toBooleanValues 以 true 数值.
const permissionList = [
"create-articles",
"read-articles",
"update-articles",
"delete-articles",
];
const permissionValues = toBooleanValues(permissions);
// { "create-articles": true, "read-articles": true, ... }
参数
- 选项
strings: 一系列字符串.
回返
toBooleanValues 返回对象 true 数值.
类型
Value
- 类型
Value实值boolean | number | string。 。 。 . - 也可能更具体一些,比如:
string数值.
示例
const features = ["feature-abc", "feature-xyz"] as const;
// "feature-abc" | "feature-xyz"
type Feature = (typeof features)[number];
Values
- 类型
Values实值Record<string, Value | Value[]>。 。 。 .
示例
{
"authenticated": true,
"roles": ["admin"],
"permissions": [
"create-articles",
"read-articles",
"update-articles",
"delete-articles"
]
}
Conditions
- 类型
Conditions实值Partial<Values>。 。 。 .
示例
{
"roles": "admin"
}