@equinor/fusion-framework-react-app

中文文档
14.3.9发布于 2026/11/12ISCTypeScriptES Module近 30 天下载 8095
原始使用文档
文档目录27 节
  1. Features
  2. Installation
  3. Usage
  4. Bootstrap an application
  5. Access a module
  6. HTTP requests
  7. MSAL authentication
  8. Context
  9. Navigation / routing
  10. State Management
  11. Installation
  12. Setup
  13. Basic Usage
  14. Cross-Component Synchronization
  15. Advanced Usage
  16. Best Practices
  17. Feature Flag
  18. Feature flags
  19. Roles
  20. Bookmarks
  21. Settings
  22. Environment variables
  23. Testing
  24. API Reference
  25. Core exports (main entry-point)
  26. Sub-path entry-points
  27. Configuration

@equinor/fusion-framework-react-app

React bindings for building modular Fusion Framework applications. Provides rendering helpers, configuration callbacks, and React hooks for accessing framework modules (HTTP, auth, context, navigation, bookmarks, roles, settings, and more).

Features

  • One-line app bootstrap via renderApp — creates a render function that mounts your component with React 18's createRoot
  • Module hooks — useAppModule / useAppModules for type-safe access to any registered module
  • Sub-path entry-points for optional capabilities: MSAL auth, HTTP, context, navigation, bookmarks, roles, feature flags, settings, analytics, AG Grid theming, help center, and apploader
  • Environment variables hook — useAppEnvironmentVariables for accessing app config at runtime
  • Lazy loading — components are wrapped in React.lazy + Suspense automatically

Installation

pnpm add @equinor/fusion-framework-react-app

Usage

Bootstrap an application

// 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;

Access a module

import { useAppModule } from '@equinor/fusion-framework-react-app';

const MyComponent = () => {
  const auth = useAppModule('auth');
  // ...
};

HTTP requests

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 authentication

Note: Requires @equinor/fusion-framework-module-msal. The MSAL module must be configured by the host/portal — apps should only consume the hooks.

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>;
};

Context

import { useCurrentContext } from '@equinor/fusion-framework-react-app/context';

const ContextInfo = () => {
  const context = useCurrentContext();
  return <p>Selected: {context?.title ?? 'none'}</p>;
};

Navigation / routing

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} />;
};

State Management

The Fusion Framework provides a powerful state management solution that enables persistent, cross-component state sharing with automatic synchronization. Unlike traditional React state that's lost on page refresh, this state persists across app sessions and stays synchronized between different components in real-time.

Key Benefits:

  • 🔄 Persistent State: Survives page refreshes and app restarts
  • 🔗 Cross-Component Sync: Share state between any components instantly
  • ⚡ Optimistic Updates: Responsive UI with automatic rollback on errors
  • 🛡️ Type Safe: Full TypeScript support with type inference
  • 🎯 Simple API: Works like useState but with persistence

Use Cases:

  • User preferences and settings
  • Form data that should persist
  • UI state like filters, sorting, or view modes
  • Data that needs to be shared across multiple components
  • Cache management for expensive operations

Installation

First, install the state module package:

pnpm install @equinor/fusion-framework-module-state

Setup

Enable the state module in your app configuration. This initializes the persistent storage and makes useAppState available throughout your application:

import { enableAppState } from '@equinor/fusion-framework-react-app/state';
export const configure: ModuleInitiator = (appConfigurator) => {
  enableAppState(appConfigurator);
};

[!CAUTION] The state management module is a powerful tool, but it's important to know the potential pitfalls and limitations when using it in your application. The state management is global and can lead to unexpected behavior if not used carefully.

example 1: If you have multiple components that rely on the same state, updating the state in one component can cause re-renders in all components that use that state, potentially leading to performance issues.

example 2: The user has open multiple tabs of the application, and each tab is modifying the same state. This can lead to unexpected behavior, as changes made in one tab will be reflected to all tabs. (like storing user preferences for selected columns)

Basic Usage

Use useAppState just like React's useState, but with automatic persistence. The first parameter is a unique key, and the second is an options object with the default value:

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>
  );
};

Cross-Component Synchronization

Multiple components can share the same state by using the same key. Changes in one component automatically update all others:

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>
);

Advanced Usage

Complex Objects with TypeScript:

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>;
};

Clearing State:

// Remove from storage completely
const clearSettings = () => setSettings(undefined);

Best Practices

Avoid Stale Closures

[!WARNING] When updating state based on the current value, always use the updater function to prevent stale closure issues in concurrent updates.

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);

State Key Organization

Use hierarchical naming for better organization:

// ✅ Good - hierarchical, descriptive
'user.profile.personal'
'user.preferences.theme'
'app.settings.notifications'
'feature.dashboard.filters'

// ❌ Avoid - flat, unclear
'userdata'
'settings'
'stuff'

Use strong typing

// ✅ 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] Validate Complex Schemas Use a library like zod or yup to validate complex state schemas before using them.

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;
};

Feature Flag

Feature flags

Note: Requires @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()} />;
};

Roles

Note: Requires @equinor/fusion-framework-module-roles in the application or parent framework scope.

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 checks the exact access-role name when mounted and checks again after a successful claim. Check and claim operations expose separate loading and error states. activateClaimableRoleAssignment rejects on failure as well as setting activationError; React event handlers must consume that rejection and render the mutation error so users can retry.

Bookmarks

import { useCurrentBookmark } from '@equinor/fusion-framework-react-app/bookmark';

const BookmarkView = () => {
  const { currentBookmark } = useCurrentBookmark();
  return <pre>{JSON.stringify(currentBookmark, null, 2)}</pre>;
};

Settings

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>;
};

Environment variables

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>;
};

Testing

Testing utilities (renderAppHook, renderAppComponent, testApp, and a Vitest plugin resolving an app's own manifest/config as virtual modules) have moved to their own package, @equinor/fusion-framework-vitest-plugin-react-app — see that package's README for usage.

API Reference

Core exports (main entry-point)

ExportDescription
renderAppCreates a mount function from a component and optional config callback
makeComponentLazily initialises modules and wraps a component in framework providers
createComponent(deprecated) Factory returning a ComponentRenderer
createLegacyApp(deprecated) Wrapper for legacy Fusion CLI apps
renderComponentLower-level helper: mounts a ComponentRenderer via createRoot
useAppModule(key)Returns a single module instance by key
useAppModules()Returns all initialised application modules
useAppEnvironmentVariables()Observable state of the app's environment config

Sub-path entry-points

PathKey exports
/msaluseCurrentAccount, useAccessToken, useToken
/httpuseHttpClient (re-export)
/http/selectorsResponse selector utilities
/contextuseCurrentContext, useContextProvider, useFrameworkCurrentContext
/navigationuseRouter, useNavigationModule
/feature-flagenableFeatureFlag, useFeature
/rolesuseAccessRole
/bookmarkenableBookmark, useCurrentBookmark, useBookmark
/settingsuseAppSetting, useAppSettings
/analyticsuseTrackFeature
/help-centeruseHelpCenter
/apploaderApploader, useApploader
/frameworkuseFramework, useCurrentUser, useFrameworkHttpClient
/widgetWidget entry-point

Configuration

Application configuration is done via a callback passed to renderApp (or makeComponent). The callback receives an IAppConfigurator with builders for each module:

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
};

The MSAL / auth module is configured by the host portal and hoisted to apps automatically. Do not configure it in application code.

版本与发布记录

@equinor/fusion-framework-react-app 共发布 431 个版本,首次发布于 2022/01/14;过去 12 个月发布了 62 个版本。

版本发布日期距今
14.3.9latest2026/11/121 天前
14.3.9-next.0next2026/11/112 天前
14.3.82026/11/112 天前
14.3.8-next.22026/10/2915 天前
14.3.8-next.12026/10/2816 天前
14.3.8-next.02026/10/2816 天前

其他发布通道:apploader → 6.2.0-apploader-be21a6b60eb13eee8bf869e75002d8634e9c50a9,cli-search-index → 8.0.1-cli-search-index.0,msal-v5 → 9.0.0-msal-v5.1,trusted-publishing-20261015 → 14.3.5-trusted-publishing-20261015-9dfe7602cd2a49a8a9df00d62c26e7304a82a85a,next → 14.3.9-next.0。

关于 @equinor/fusion-framework-react-app 的常见问题

如何安装 @equinor/fusion-framework-react-app?

在项目目录运行 npm install @equinor/fusion-framework-react-app,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.0.0 || ^19.0.0)、@equinor/fusion-framework-module-msal(^12.1.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@equinor/fusion-framework-react-app 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@equinor/fusion-framework-react-app 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@equinor/fusion-framework-react-app 可以商用吗?

@equinor/fusion-framework-react-app 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@equinor/fusion-framework-react-app 的下载量有多少?

根据 npm 官方下载统计,@equinor/fusion-framework-react-app 在 2026/10/14 – 2026/11/12 这 30 天内被下载 8,095 次,日均约 270 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@equinor/fusion-framework-react-app 还在维护吗?

最新版本 14.3.9 发布于 2026/11/12(1 天前);至今共发布 431 个版本,其中过去 12 个月发布了 62 个;首次发布于 2022/01/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。