@ equinor/ 组合框架- 反应- app
用于构建模块化Fusion框架应用程序的回放绑定. 为访问框架模块(HTTP, 认证, 上下文, 导航, 书签, 角色, 设置等) 提供渲染辅助工具 , 配置回调, 和 React hooks .
特征
- 单行程序拖带 通过
renderApp—— 创建渲染函数,将组件上载为 React 18'screateRoot - 模块钩 —— 说
useAppModule编号 :useAppModules用于安全进入任何已注册模块 - 分路径入口 用于可选能力: MSAL 认证、 HTTP、 上下文、 导航、 书签、 角色、 特征旗帜、 设置、 分析、 AG 网格主题、 帮助中心、 和装入程序
- 环境变量 钩-
useAppEnvironmentVariables用于运行时访问 app 配置 - 懒惰装入 ——组件被包入
React.lazy+ 键Suspense自动
安装
pnpm add @equinor/fusion-framework-react-app
使用量
启动应用程序
// config.ts
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';
export const configure: AppModuleInitiator = (configurator) => {
configurator.configureHttpClient('my-api', {
baseUri: 'https://api.example.com',
defaultScopes: ['api://my-api/.default'],
});
};
// App.tsx
export const App = () => <h1>Hello Fusion</h1>;
// index.ts
import { renderApp } from '@equinor/fusion-framework-react-app';
import { App } from './App';
import { configure } from './config';
export const render = renderApp(App, configure);
export default render;
访问模块
import { useAppModule } from '@equinor/fusion-framework-react-app';
const MyComponent = () => {
const auth = useAppModule('auth');
// ...
};
HTTP 请求
import { useHttpClient } from '@equinor/fusion-framework-react-app/http';
const MyComponent = () => {
const client = useHttpClient('my-api');
// client.fetch$('endpoint').subscribe(...)
// await client.fetchAsync('endpoint')
};
MSAL 认证
说明: 要求数
@equinor/fusion-framework-module-msal. MSAL 模块必须由主机/端口配置——应用程序只应消耗钩.
import { useCurrentAccount, useAccessToken } from '@equinor/fusion-framework-react-app/msal';
const UserInfo = () => {
const user = useCurrentAccount();
const { token } = useAccessToken({ scopes: ['User.Read'] });
if (!user) return <p>Not signed in</p>;
return <p>Welcome, {user.name}</p>;
};
A. 背景情况
import { useCurrentContext } from '@equinor/fusion-framework-react-app/context';
const ContextInfo = () => {
const context = useCurrentContext();
return <p>Selected: {context?.title ?? 'none'}</p>;
};
导航/路由
import { useRouter } from '@equinor/fusion-framework-react-app/navigation';
import { RouterProvider } from '@equinor/fusion-framework-react-router';
const routes = [{ path: '/', element: <Home /> }];
const App = () => {
const router = useRouter(routes);
return <RouterProvider router={router} />;
};
州管理层
Fusion框架提供了强大的状态管理解决方案,能够以自动同步的方式持续地,跨组件状态共享. 与传统的React状态在页面刷新中丢失不同,这种状态在应用会话间持续并保持在不同组件间实时同步.
主要惠益:
- 🔄 持久性状态:幸存页面刷新并重新启动应用程序
- 🔗 交叉组件同步: 任何组件之间立即共享状态
- ⚡ 优化更新: 自动回滚错误的响应UI
- 🛡️ 类型安全: 全类型脚本支持和类型推论
- 🎯 简单 API: 工作类
useState但坚持不懈
使用大小写 :
- 用户偏好和设置
- 表格数据应持续
- 类似过滤、排序或视图模式的 UI 状态
- 需要在多个组成部分之间共享的数据
- 昂贵业务的缓存管理
安装
首先,安装状态模块包:
pnpm install @equinor/fusion-framework-module-state
设置
启用应用程序配置中的状态模块 。 初始化持续存储并制作 useAppState 您可在您的申请中找到:
import { enableAppState } from '@equinor/fusion-framework-react-app/state';
export const configure: ModuleInitiator = (appConfigurator) => {
enableAppState(appConfigurator);
};
[叹息] 状态管理模块是一个强大的工具,但是在您的应用中使用它时必须了解潜在的陷阱和局限性. 国家管理是全球性的,如果不仔细使用,可能导致出乎意料的行为.
实例1: 如果有多个依赖于同一状态的组件,更新一个组件中的状态会导致所有使用该状态的组件的重接,可能导致性能问题.
实例2: 用户打开了应用程序的多分页,每个分页都在修改相同的状态. 这会导致出乎意料的行为,因为一个标签中所做的修改会被反映到所有标签中. (类似于存储选定列的用户首选项)
基本用途
使用 useAppState 就像反应一样 useState自动坚持 第一个参数是一个独特的密钥,第二个参数是一个带有默认值的选项对象:
import { useAppState } from '@equinor/fusion-framework-react-app/state';
const Counter = () => {
const [count, setCount] = useAppState('counter', { defaultValue: 0 });
return (
<div>
<span>Count: {count}</span>
<button onClick={() => setCount((prev) => (prev ?? 0) + 1)}>Increment</button>
</div>
);
};
跨组件同步
多个组件可以通过使用相同的密钥来共享相同的状态. 一个组件的更改自动更新所有其他组件 :
import { useAppState } from '@equinor/fusion-framework-react-app/state';
const Incrementer = () => {
const [count, setCount] = useAppState('counter', { defaultValue: 0 });
return (
<button onClick={() => setCount((prev) => (prev || 0) + 1)}>
Increment
</button>
);
};
const Display = () => {
const [count] = useAppState('counter', { defaultValue: 0 });
return <span>Current count: {count}</span>;
};
// Usage in your app
const App = () => (
<div>
<Incrementer />
<Display />
</div>
);
高级使用
有类型脚本的复杂对象 :
interface UserPreferences {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
}
const SettingsPanel = () => {
const [settings, setSettings] = useAppState<UserPreferences>('user-settings', {
defaultValue: { theme: 'light', language: 'en', notifications: true }
});
const toggleTheme = () => {
setSettings(prev => ({
...prev!,
theme: prev!.theme === 'light' ? 'dark' : 'light'
}));
};
return <button onClick={toggleTheme}>Theme: {settings?.theme}</button>;
};
清除状态 :
// Remove from storage completely
const clearSettings = () => setSettings(undefined);
最佳做法
避免断层关闭
[尖叫声] 在基于当前值更新状态时,始终使用更新器功能来防止同时更新中出现 stale 关闭问题.
const [count, setCount] = useAppState('counter', { defaultValue: 0 });
// ❌ Bad: Can use stale value in rapid updates
const increment = () => setCount(count + 1);
// ✅ Good: Always gets the latest value
const increment = () => setCount(prev => (prev || 0) + 1);
状态密钥组织
为更好的组织使用分级命名 :
// ✅ Good - hierarchical, descriptive
'user.profile.personal'
'user.preferences.theme'
'app.settings.notifications'
'feature.dashboard.filters'
// ❌ Avoid - flat, unclear
'userdata'
'settings'
'stuff'
使用强打字
// ✅ Good - strong typing
interface UserProfile {
id: string;
name: string;
email: string;
}
const [user, setUser] = useAppState<UserProfile>('user.profile');
// ❌ Avoid - weak typing
const [user, setUser] = useAppState('user.profile');
[! TIP] 验证复合型Schemas 使用库像
zod或者说yup在使用复杂状态计划之前验证它们.const userSchema = z.object({ id: z.string().uuid(), name: z.string().min(2).max(100), email: z.string().email(), }); type UserProfile = z.infer<typeof userSchema>; // ✅ Good - strong typing with validation const useMyUser = () => { const [value, setValue] = useAppState<UserProfile>('user.profile'); const setUser = useCallback((user: UserProfile) => { if (userSchema.safeParse(user).success) { setValue(user); return true; } else { console.warn('Provided user is invalid'); return false; } }, [setValue]); if(!userSchema.safeParse(value).success) { console.warn('Current user state is invalid'); return null; } return value; };
特性旗
地物标志
说明: 要求数
@equinor/fusion-framework-module-feature-flag。 。 。 .
import { enableFeatureFlag } from '@equinor/fusion-framework-react-app/feature-flag';
export const configure: AppModuleInitiator = (configurator) => {
enableFeatureFlag(configurator, [
{ key: 'dark-mode', title: 'Dark mode' },
{ key: 'beta-ui', title: 'Beta UI', allowUrl: true },
]);
};
import { useFeature } from '@equinor/fusion-framework-react-app/feature-flag';
const Toggle = () => {
const { feature, toggleFeature } = useFeature('dark-mode');
return <Switch checked={feature?.enabled} onChange={() => toggleFeature()} />;
};
作用
说明: 要求数
@equinor/fusion-framework-module-roles在申请中或父母关系 框架范围.
import { enableRoles } from '@equinor/fusion-framework-module-roles';
export const configure: AppModuleInitiator = (configurator) => {
enableRoles(configurator);
};
import { useAccessRole } from '@equinor/fusion-framework-react-app/roles';
const ReportsAccess = ({ claimableRoleId }: { claimableRoleId: string }) => {
const role = useAccessRole('Reports.Read');
if (role.isChecking) return <Spinner />;
if (role.checkError) return <ErrorMessage error={role.checkError} />;
if (role.hasAccessRole) return <Reports />;
/** Consumes the event-handler rejection; activationError below owns the visible failure. */
const handleActivate = (): void => {
void role
.activateClaimableRoleAssignment({
assignmentId: claimableRoleId,
reason: 'Open reports',
hours: 2,
})
.catch(() => undefined);
};
return role.hasClaimableRoleAssignmentForAccessRole ? (
<>
{role.activationError ? <p role="alert">{String(role.activationError)}</p> : null}
<button disabled={role.isActivating} onClick={handleActivate}>
Claim access
</button>
</>
) : null;
};
useAccessRole 安装时检查确切的存取作用名称,并在索赔成功后再次检查.
检查和索赔业务暴露出单独的装载和出错状态.
activateClaimableRoleAssignment 拒绝失败和设置 activationError; 事件处理器必须消耗
拒绝并让突变出错,使用户可以重试.
书签
import { useCurrentBookmark } from '@equinor/fusion-framework-react-app/bookmark';
const BookmarkView = () => {
const { currentBookmark } = useCurrentBookmark();
return <pre>{JSON.stringify(currentBookmark, null, 2)}</pre>;
};
设置
import { useAppSetting } from '@equinor/fusion-framework-react-app/settings';
const ThemePicker = () => {
const [theme, setTheme] = useAppSetting('theme', 'light');
return <button onClick={() => setTheme('dark')}>Go dark</button>;
};
环境变量
import { useAppEnvironmentVariables } from '@equinor/fusion-framework-react-app';
const EnvInfo = () => {
const env = useAppEnvironmentVariables();
if (!env.complete) return <p>Loading…</p>;
return <pre>{JSON.stringify(env.value, null, 2)}</pre>;
};
测试
测试公用事业renderAppHook, (中文(简体) ), renderAppComponent, (中文(简体) ), testApp插件
解析一个应用程序自己的显示/配置为虚拟模块)已移动到自己的软件包,
@equinor/fusion-framework-vitest-plugin-react-app —— 说
查看软件包的 README 用于使用 .
API 参考
核心出口(主要切入点)
| 导出 | 说明 |
|---|---|
renderApp | 从组件和可选配置回调创建挂载函数 |
makeComponent | Lazily 初始化模块并在框架提供者中包裹一个组件 |
createComponent | (折旧) 工厂返回a ComponentRenderer |
createLegacyApp | (折旧) 遗留的Fusion CLI 应用程序的包装器 |
renderComponent | 下级助手: 挂载 a ComponentRenderer 通过 createRoot |
useAppModule(key) | 按键返回单个模块实例 |
useAppModules() | 返回所有已关闭的应用程序模块 |
useAppEnvironmentVariables() | 应用环境配置的可观测状态 |
分路径入口
| 路径 | 主要出口 |
|---|---|
/msal | useCurrentAccount, (中文(简体) ), useAccessToken, (中文(简体) ), useToken |
/http | useHttpClient (再出口) |
/http/selectors | 反应选择器公用事业 |
/context | useCurrentContext, (中文(简体) ), useContextProvider, (中文(简体) ), useFrameworkCurrentContext |
/navigation | useRouter, (中文(简体) ), useNavigationModule |
/feature-flag | enableFeatureFlag, (中文(简体) ), useFeature |
/roles | useAccessRole |
/bookmark | enableBookmark, (中文(简体) ), useCurrentBookmark, (中文(简体) ), useBookmark |
/settings | useAppSetting, (中文(简体) ), useAppSettings |
/analytics | useTrackFeature |
/help-center | useHelpCenter |
/apploader | Apploader, (中文(简体) ), useApploader |
/framework | useFramework, (中文(简体) ), useCurrentUser, (中文(简体) ), useFrameworkHttpClient |
/widget | 部件切入点 |
配置
应用程序配置通过调用完成 renderApp (或 makeComponent) (中文(简体) ). 回调收到一个 IAppConfigurator 使用每个模块的构建器:
const configure: AppModuleInitiator = (configurator) => {
// HTTP clients
configurator.configureHttpClient('my-api', { baseUri: '...' });
// Feature flags
enableFeatureFlag(configurator, [{ key: 'beta', title: 'Beta' }]);
// Navigation, context, etc. — see module docs
};
MSAL / auth 模块由主机门户配置并自动提升为应用. 执行 没有 在应用程序代码中配置它 .