Component コンポーネント開発

NocoBase では、ルートにマウントするページコンポーネントは普通の React コンポーネントです。React + Antd をそのまま使って書くことができ、普通のフロントエンド開発と変わりません。

NocoBase は追加で以下を提供しています:

  • observable + observer — 推奨の状態管理方式。useState よりも NocoBase エコシステムに適しています
  • useFlowContext() — NocoBase のコンテキスト機能(リクエスト送信、国際化、ルーティングナビゲーションなど)を取得します

基本的な書き方

最もシンプルなページコンポーネント:

// pages/HelloPage.tsx
export default function HelloPage() {
  return <h1>Hello, NocoBase!</h1>;
}

作成したら、プラグインの load() 内で this.router.add() を使って登録します。詳しくは Router ルーティングをご覧ください。

状態管理:observable

NocoBase では、React の useState の代わりに observable + observer を使ったコンポーネント状態管理を推奨しています。その利点は:

  • オブジェクトのプロパティを直接変更するだけで更新がトリガーされ、setState が不要
  • 自動的な依存関係収集により、使用しているプロパティが変化した時のみコンポーネントが再レンダリングされる
  • NocoBase の内部(FlowModel、FlowContext など)のリアクティブ機構と一貫性がある

基本的な使い方:observable.deep() でリアクティブオブジェクトを作成し、observer() でコンポーネントをラップします。observableobserver はどちらも @nocobase/flow-engine からインポートします:

import React from 'react';
import { Input } from 'antd';
import { observable, observer } from '@nocobase/flow-engine';

// リアクティブな状態オブジェクトを作成
const state = observable.deep({
  text: '',
});

// observer でコンポーネントをラップすると、状態変化時に自動更新される
const DemoPage = observer(() => {
  return (
    <div>
      <Input
        placeholder="何か入力してください..."
        value={state.text}
        onChange={(e) => {
          state.text = e.target.value;
        }}
      />
      {state.text && <div style={{ marginTop: 8 }}>入力内容:{state.text}</div>}
    </div>
  );
});

export default DemoPage;

プレビュー:

import { Application, Plugin } from '@nocobase/client-v2';

class MyPlugin extends Plugin {
  async load() {
    this.router.add('root', {
      path: '/',
      componentLoader: () => import('@docs/ja/plugin-development/client/component/_demos/observable-basic-page'),
    });
  }
}

// 以下のコードはこのデモをドキュメント内で独立して実行するためのものです。実際のプラグイン開発では app のインスタンス化を気にする必要はありません。
const app = new Application({
  // コード例では memory router を使用していますが、実際のプロジェクトではこれを気にする必要はありません。app のインスタンス化は NocoBase 内部で行われます。
  router: { type: 'memory', initialEntries: ['/'] },
  plugins: [MyPlugin],
});

export default app.getRootComponent();

より詳しい使い方は Observable リアクティブ機構をご覧ください。

useFlowContext の使い方

useFlowContext() は NocoBase の機能と接続するためのエントリポイントです。@nocobase/flow-engine からインポートし、ctx オブジェクトを返します:

import { useFlowContext } from '@nocobase/flow-engine';

export default function MyPage() {
  const ctx = useFlowContext();
  // ctx.api — リクエスト送信
  // ctx.t — 国際化
  // ctx.router — ルーティングナビゲーション
  // ctx.logger — ログ
  // ...
}

以下は主な機能の使用例です。

リクエスト送信

ctx.api.request() でバックエンド API を呼び出します。使い方は Axios と同じです:

const response = await ctx.api.request({
  url: 'users:list',
  method: 'get',
});
console.log(response.data);

国際化

ctx.t() で翻訳テキストを取得します:

const label = ctx.t('Hello');
// 名前空間を指定
const msg = ctx.t('Save success', { ns: '@my-project/plugin-hello' });

ルーティングナビゲーション

ctx.router.navigate() でページ遷移します:

ctx.router.navigate('/some-page'); // -> /v/some-page

現在のルートパラメータを取得:

// 例えばルート定義が /users/:id の場合
const { id } = ctx.route.params; // 動的パラメータの取得

現在のルート名を取得:

const { name } = ctx.route; // ルート名の取得

その他のログレベルと使い方は Context → 共通機能をご覧ください。

完全な例

observable、useFlowContext、Antd を組み合わせた、バックエンドからデータを取得して表示するページコンポーネント:

// pages/PostListPage.tsx
import React, { useEffect } from 'react';
import { Button, Card, List, Spin } from 'antd';
import { observable, observer, FlowContext, useFlowContext } from '@nocobase/flow-engine';

interface Post {
  id: number;
  title: string;
}

// observable でページ状態を管理
const state = observable.deep({
  posts: [] as Post[],
  loading: true,
});

const PostListPage = observer(() => {
  const ctx = useFlowContext();

  useEffect(() => {
    loadPosts(ctx);
  }, []);

  return (
    <Card title={ctx.t('Post list')}>
      <Spin spinning={state.loading}>
        <List
          dataSource={state.posts}
          renderItem={(post: Post) => (
            <List.Item
              actions={[
                <Button danger onClick={() => handleDelete(ctx, post.id)}>
                  {ctx.t('Delete')}
                </Button>,
              ]}
            >
              {post.title}
            </List.Item>
          )}
        />
      </Spin>
    </Card>
  );
});

async function loadPosts(ctx: FlowContext) {
  state.loading = true;
  try {
    const response = await ctx.api.request({
      url: 'posts:list',
      method: 'get',
    });
    state.posts = response.data?.data || [];
  } catch (error) {
    ctx.logger.error('記事リストの読み込みに失敗', { error });
  } finally {
    state.loading = false;
  }
}

async function handleDelete(ctx: FlowContext, id: number) {
  await ctx.api.request({
    url: `posts:destroy/${id}`,
    method: 'post',
  });
  loadPosts(ctx); // リストを更新
}

export default PostListPage;

次のステップ

  • useFlowContext が提供する全機能 — Context コンテキストを参照
  • コンポーネントのスタイルとテーマカスタマイズ — Styles & Themes スタイルとテーマを参照
  • コンポーネントを NocoBase の「ブロックの追加 / フィールド / 操作」メニューに表示し、ユーザーのビジュアル設定をサポートする場合は FlowModel でラップする必要があります — FlowEngineを参照
  • Component と FlowModel のどちらを使うか迷ったら — Component vs FlowModelを参照

関連リンク