ルーターコンポーネント

ルーターコンポーネントは、`component` または `componentUrl` プロパティを使用してルートコンテンツを指定すると、ルーターによってロードできる特別なタイプのコンテンツです。

これは、アプリの構造を改善し、適切な場所に配置し、多くのことをより迅速かつ明確で快適な方法で行うのに役立ちます。

コンポーネント関数

コンポーネントは、`props` と `context` を受け取り、レンダー関数を返す関数です。

コンポーネントのレンダー関数は、コンポーネントの HTML コンテンツを含むタグ付きテンプレートリテラルを返す必要があります。

例えば

const MyComponent = (props, context) => {
  // some component logic
  let value = 'foo';

  // return render function
  return () => context.$h`
    <div class="page">
      <p>Value is ${value}</p>
    </div>
  `;
}

コンポーネントテンプレート

上記のように、コンポーネントのレンダー関数は、コンポーネントの HTML コンテンツを含むタグ付きテンプレートリテラルを返す必要があります。注意すべき重要な点がいくつかあります。

すべての自己終了タグは閉じなければなりません! `<br>`、`<img src="">`、`<input ...>` などの自己終了タグを閉じないと、コンパイラはエラーをスローします。

すべての空の要素は自己終了できます:

<div class="my-div"></div>

<!-- also valid as -->
<div class="my-div" />

コンポーネントプロパティ

コンポーネント関数が受け取る最初の引数は `props` です。このオブジェクトには、ナビゲートメソッドで渡すすべてのプロパティと、すべてのルートパラメータが含まれます。

例えば、次のルートがある場合

{
  path: '/blog/:id',
  component: MyComponent
}

`/blog/34/` URL を介してルートに移動すると、`props.id` は `'34'` になります。

また、API を使用してコンポーネントに次のように移動する場合

router.navigate('/blog/34/', {
  props: {
    foo: 'bar'
  }
})

すると、`props` は次のオブジェクトになります:`{ id: '34', foo: 'bar' }`

また、props には、カスタムコンポーネントに属性として渡されたプロパティが含まれます。カスタムコンポーネントにそのような属性がある場合

<my-component foo="bar" id="25" user=${{name: 'John'}} number=${30}></my-component>

すると `$props` は次のようになります

{
  foo: 'bar',
  id: '25',
  user: {
    name: 'John'
  },
  number: 30
}

コンポーネントコンテキスト

`context` オブジェクトには、多くの便利なヘルパーが含まれています

プロパティ説明
$h

コンポーネントのレンダー関数の結果とすべての HTML エントリをラップするために使用しなければならない特別なタグ付きテンプレートリテラル

const MyComponent = (props, { $h }) => {
  let list = ['item 1', 'item 2'];

  return () => $h`
    <div class="page">
      <ul>
        ${list.map((item) => $h`
          <li>${item}</li>
        `)}
      </ul>
    </div>
  `
}
$el

`.value` プロパティにコンポーネント HTML 要素を持つ Dom7 インスタンスが含まれるオブジェクト。

`$el.value` は、コンポーネントがマウントされた後 (または `pageInit` などのページイベント) でのみ使用できます。

const MyComponent = (props, { $el, $onMounted }) => {
  $onMounted(() => {
    $el.value.find('p').addClass('red')
  })
  // ...
}
$

Dom7 ライブラリ

const MyComponent = (props, { $, $onMounted }) => {
  $onMounted(() => {
    $('p').text('hello world')
  })
  // ...
}
$f7

Framework7 アプリインスタンス

$f7.dialog.alert('Hello world!')
$store

ストアインスタンス。詳細と例については、ストアのドキュメントを確認してください。

$f7route現在のルート。ルートの `query`、`hash`、`params`、`path`、`url` を含むオブジェクトが含まれています
$f7router

関連するルーターインスタンス

$f7router.back(); //navigate back
$theme

現在のテーマを示すブール値プロパティ `md`、`ios` を持つオブジェクト。例えば

if ($theme.ios) { /* do something when iOS theme is active */ }
if ($theme.md) { /* do something when MD theme is active */ }
$update(コールバック)

このメソッドは、このコンポーネントとその子が更新された状態で再レンダリングする必要があることを示します

const MyComponent = (props, { $update, $h }) => {
  // initial state
  let value = 'foo';

  const updateValue = () => {
    // update local state
    value = 'foo2';
    // call $update method
    $update();
  }

  return () => $h`
    <div class="page">
      <p>Value is ${value}</p>
      <button @click=${updateValue}>Update Value</button>
    </div>
  `;
}

DOM の変更がすぐに適用されることは保証されていないため、DOM に依存している場合 (たとえば、状態が変更された後に HTML コンテンツまたは属性値を取得する必要がある場合) は、`コールバック` 関数を引数として渡します。

$ref(初期値)

このメソッドは、リアクティブな「変数」を作成します。更新後、`$update()` メソッドを呼び出すことなく、コンポーネントを自動的に更新します。

新しい値を割り当てる必要がある `value` プロパティを持つオブジェクトを返します。

const MyComponent = (props, { $ref, $h }) => {
  // create reactive object
  const someVar = $ref('foo'); //-> { value: 'foo' }

  const updateValue = () => {
    // update "value" property of the reactive object
    someVar.value = 'bar';
  }

  return () => $h`
    <div class="page">
      <p>Value is ${someVar.value}</p>
      <button @click=${updateValue}>Update Value</button>
    </div>
  `;
}

DOM の変更がすぐに適用されることは保証されていないため、DOM に依存している場合 (たとえば、状態が変更された後に HTML コンテンツまたは属性値を取得する必要がある場合) は、`コールバック` 関数を引数として渡します。

$useState(初期値)

このメソッドは、リアクティブな「状態」を作成します.

`$useState` は3種類のデータを受け入れます

  • 配列
  • オブジェクト
  • `atoms` - 配列またはオブジェクトではないもの (文字列、数値、null など)

`array` と `object` はそのまま保持されます (既に参照です)。

`atoms` は Object.defineProperty を介してオブジェクトにラップされます (`$ref` と同様)。

`atoms` の場合、`$useState` は以下を返します

{
  state:         // state.value
  update:  (v)   // [state.value = value]
  clear:   ()    // [state.value = undefined]
  method:  (f)   // [custom method]
  async:   (f)   // [custom method promise]
}

`array` の場合、`$useState` は以下を返します

{
  state:         // state.value
  update:  (v)   // [state.length = 0, state.push(...value)]
  remove:  (x)   // [remove item/list by given position]
  clear:   ()    // [remove all items from array]
  insert:  (x,v) // [insert item/list at position x]
  replace: (x,v) // [replace item/list at position x]
  append:  (v)   // [append item/list]
  prepend: (v)   // [prepend item/list]
  swap:    (a,b) // [swap index a with index b]
  fromTo:  (a,b) // [move index a to index b]
  method:  (f)   // [custom method]
  async:   (f)   // [custom method promise]
}

`object` の場合、`$useState` は以下を返します

{
  state:         // state.value
  update:  (v)   // [Object.assign(state,value)]
  remove:  (v)   // [remove key or list of keys]
  clear:   ()    // [remove all keys from object]
  method:  (f)   // [cunstom method]
  async:   (f)   // [cunstom method promise]
}

例えば

// atoms
const { state, update } = $useState('text');

state.value; // 'text'
update('new value');
state.value; // 'new value'
clear();
state.value; // undefined
update('text');
state.value; // 'text'
// object
const { state, update, remove, clear } = $useState({});

state; // {}
update({ foo: 'bar' });
state; // { foo: 'bar' }
update({ foo: 'qux', baz: 'quux' });
state; // { foo: 'qux', baz: 'quux' }
update({ baz: 'corge' });
state; // { foo: 'qux', baz: 'corge' }
clear();
state; // {}
update({ grault: 'garply', list: [1, 2, 3] });
state; // { grault: 'garply', list: [1, 2, 3] }
update({ grault: null, dummy: { key: 'value' } });
state; // { grault: null, list: [1, 2, 3], dummy: { key: 'value' } }
remove('grault');
state; // { list: [1, 2, 3], dummy: { key: 'value' } }
update({ foo: 'bar' });
state; // { list: [1, 2, 3], dummy: { key: 'value' }, foo: 'bar' }
remove(['list', 'dummy']);
state; // { foo: 'bar' }
// array
const {
  state, update, insert, replace, append,
  prepend, swap, fromTo, remove, clear
} = $useState([]);

state // []
update([1, 2, 3]);
state // [1, 2, 3]
append(4);
state // [1, 2, 3, 4]
append([5, 6]);
state // [1, 2, 3, 4, 5, 6]
prepend(-1);
state // [-1, 1, 2, 3, 4, 5, 6]
prepend([-3, -2]);
state // [-3, -2, -1, 1, 2, 3, 4, 5, 6]
insert(3,0);
state // [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6]
insert(4,[0.5, 0.9]);
state // [-3, -2, -1, 0, 0.5, 0.9, 1, 2, 3, 4, 5, 6]
replace(5,0.8);
state // [-3, -2, -1, 0, 0.5, 0.8, 1, 2, 3, 4, 5, 6]
replace(7,[22, 2.5]);
state // [-3, -2, -1, 0, 0.5, 0.8, 1, 22, 2.5, 3, 4, 5, 6]
swap(0,12);
state // [6, -2, -1, 0, 0.5, 0.8, 1, 22, 2.5, 3, 4, 5, -3]
fromTo(1,10);
state // [6, -1, 0, 0.5, 0.8, 1, 22, 2.5, 3, 4, -2, 5, -3]
remove(2);
state // [6, -1, 0.5, 0.8, 1, 22, 2.5, 3, 4, -2, 5, -3]
remove([0, 3, 1, 2]);
state // [1, 22, 2.5, 3, 4, -2, 5, -3]
clear();
state // []
update([1, 2, 3]);
state // [1, 2, 3]
$tick(コールバック)

DOM に依存していて、`$update()` メソッドの呼び出し後にコンポーネントの状態と DOM が更新されていることを確認する必要がある場合にも、このメソッドを使用できます。

渡されたコールバックは、DOM の更新時に実行されます。

このメソッドは、DOM の更新時に解決される Promise を返します。

そのため、次のように使用できます

$update();

$tick(function () {
  console.log('DOM and state updated');
});

// Or as Promise
$tick().then(() => {
  console.log('DOM and state updated');
})

// Or in async function/method as:
await $tick();
console.log('DOM and state updated');
$f7ready(コールバック)

このメソッドは、メインアプリコンポーネントを使用して、アプリの初期化時に Framework7 API を確実に呼び出す場合にのみ使用する必要があります。

const AppComponent = (props, { $f7, $f7ready }) => {
  $f7ready(() => {
    // now it is safe to call Framework7 APIs
    $f7.dialog.alert('Hello!');
  });

  // ...
}
イベント
$on

DOM イベントハンドラをコンポーネントのルート要素にアタッチする関数

const MyComponent = (props, { $on }) => {
  // attach 'pageInit' event handler
  $on('pageInit', (e, page) => {
    console.log(page.name)
  });
  // ...
}

このようなイベントハンドラは、コンポーネントが破棄されると自動的にデタッチされます

$once

DOM イベントハンドラをコンポーネントのルート要素にアタッチする関数。`$on` と同じですが、このようなハンドラは一度だけ実行されます。

$emit(イベント, データ)

再利用可能なカスタムコンポーネントでカスタム DOM イベントを発行する関数

const MyComponent = (props, { $emit }) => {
  // emits custom event
  $emit('myevent')
  // ...
}

他の親コンポーネントでは

<my-component @myevent=${doSomething} />
ライフサイクルフック
$onBeforeMountコンポーネントが DOM に追加される直前に呼び出されます
$onMounted

コンポーネントが DOM に追加された直後に呼び出されます

const MyComponent = (props, { $onMounted }) => {
  // do something when component mounted
  $onMounted(() => {
    console.log('component mounted')
  });
  // ...
}
$onBeforeUpdateVDOM がパッチ/更新される直前に呼び出されます
$onUpdatedコンポーネント VDOM がパッチ/更新された直後に呼び出されます
$onBeforeUnmountコンポーネントがアンマウントされる直前 (DOM からデタッチされる直前) に呼び出されます
$onUnmountedコンポーネントがアンマウントされ、破棄されたときに呼び出されます

そのため、ページコンポーネントを使用したルートの例は次のようになります

routes = [
  // ...
  {
    path: '/some-page/',
    // Component
    component: (props, { $h, $f7, $on }) => {
      const title = 'Component Page';
      const names = ['John', 'Vladimir', 'Timo'];

      const openAlert = () => {
        $f7.dialog.alert('Hello world!');
      }

      $on('pageInit', (e, page) => {
        // do something on page init
      });
      $on('pageAfterOut', (e, page) => {
        // page has left the view
      });

      return () => $h`
        <div class="page">
          <div class="navbar">
            <div class="navbar-bg"></div>
            <div class="navbar-inner">
              <div class="title">${title}</div>
            </div>
          </div>
          <div class="page-content">
            <a @click=${openAlert} class="red-link">Open Alert</a>
            <div class="list simple-list">
              <ul>
                ${names.map((name) => $h`
                  <li>${name}</li>
                `)}
              </ul>
            </div>
          </div>
        </div>
      `;
    },
  },
  // ...
]

コンポーネントページイベント

コンポーネントページイベントハンドラは、`$on` コンポーネントイベントハンドラに渡すことができます。これらは通常の DOM ページイベントです。DOM イベントであるため、最初の引数として `event` を、2 番目の引数としてページデータを受け入れます。通常の DOM イベントとの唯一の違いは、イベントハンドラ名を camelCase 形式で指定する必要があることです (`page:init` -> `pageInit`)

const MyComponent = (props, { $on }) => {
  $on('pageMounted', (e, page) => {
    console.log('page mounted');
  });
  $on('pageInit', (e, page) => {
    console.log('page init');
  });
  $on('pageBeforeIn', (e, page) => {
    console.log('page before in');
  });
  $on('pageAfterIn', (e, page) => {
    console.log('page after in');
  });
  $on('pageBeforeOut', (e, page) => {
    console.log('page before out');
  });
  $on('pageAfterOut', (e, page) => {
    console.log('page after out');
  });
  $on('pageBeforeUnmount', (e, page) => {
    console.log('page before unmount');
  });
  $on('pageBeforeRemove', (e, page) => {
    console.log('page before remove');
  });
}

DOM イベント処理

コンポーネントテンプレートに追加の `@` 属性があることに注意してください。これは、指定された要素にイベントリスナーを割り当てるための省略形です。指定されたイベントハンドラは、コンポーネントスコープで検索されます。

このようなイベントハンドラ属性値は関数でなければなりません

const MyComponent = (props, { $h, $update }) => {
  let value = 10;
  const addValue = (number) => {
    value += number;
    $update();
  }
  const onClick = () => {
    console.log('click');
  }

  return () => $h`
    <div class="page">
      <!-- pass function to attribute -->
      <button @click=${onClick}>Button</button>

      <!-- also work -->
      <button @click=${() => onClick()}>Button</button>

      <!-- will not work, attribute value "onClick" is just a string -->
      <button @click="onClick">Button</button>

      <!-- passing dynamic data will work as expected -->
      <button @click=${() => addValue(15)}>Button</button>
    </div>
  `
}

イベントハンドラは、初期レンダリング時、または VDOM でパッチが適用された要素に対してのみ処理されます。**このような要素を手動で DOM に追加しても機能しません!**

const MyComponent = (props, { $h, $on }) => {
  const onClick = () => {
    console.log('click');
  }

  $on('pageInit', (e, page) => {
    // this won't work
    page.$el.append('<a @click="onClick">Link</a>');
  });

  return () => $h`
    <div class="page">
    </div>
  `
}

コンポーネントルート要素

コンポーネントテンプレートまたはレンダー関数は、単一の HTML 要素のみを返す必要があります。そして、それはルーターでサポートされている要素でなければなりません

単一ファイルコンポーネント

特にそのようなルートがたくさんある場合、すべてのコンポーネントルートを同じルート配列の下に指定するのはあまり快適ではありません。そのため、代わりに `componentUrl` を使用して、コンポーネントを単一ファイルに配置できます

routes = [
  ...
  {
    path: '/some-page/',
    componentUrl: './some-page.f7',
  },
  ..
];

`some-page.f7` では

<!-- component template, uses same tagged template literals -->
<template>
  <div class="page">
    <div class="navbar">
      <div class="navbar-bg"></div>
      <div class="navbar-inner">
        <div class="title">${title}</div>
      </div>
    </div>
    <div class="page-content">
      <a @click=${openAlert}>Open Alert</a>
      <div class="list simple-list">
        <ul>
          ${names.map((name) => $h`
            <li>${name}</li>
          `)}
        </ul>
      </div>
    </div>
  </div>
</template>
<!-- component styles -->
<style>
  .red-link {
    color: red;
  }
</style>
<!-- rest of component logic -->
<script>
  // script must return/export component function
  export default (props, { $f7, $on }) => {
    const title = 'Component Page';
    const names = ['John', 'Vladimir', 'Timo'];

    const openAlert = () => {
      $f7.dialog.alert('Hello world!');
    }

    $on('pageInit', () => {
      // do something on page init
    });
    $on('pageAfterOut', () => {
      // page has left the view
    });

    // component function must return render function
    return $render;
  }
</script>

これで、ずっとすっきりしました。<template> タグと <style> タグは、エクスポートされたコンポーネントの同じプロパティに自動的に変換されます。

コンポーネント関数の最後に return $render を記述することは必須です。これはパーサーによって <template> タグの内容に置き換えられます。

Webpack と Vite での使用

Webpack には、framework7-loader という特別なプラグインがあり、シングルファイルコンポーネントをメインバンドルにバンドルし、コンポーネントファイルをロードして解析するたびに XHR (例: componentUrl) を使用しないようにすることができます。

Vite.js にも、シングルファイルコンポーネントをバンドルするための特別な rollup-plugin-framework7 プラグインがあります。

これらのプラグインは、シングルファイルコンポーネントのファイルを解析し、バンドルプロセス中にプレーンな JS オブジェクトに変換します。そのため、実行時の解析とコンパイルがなくなるため、アプリのパフォーマンスが向上する可能性があります。

プラグインが設定されている場合、シングルファイルコンポーネントを .f7 (Webpack の場合は .f7.html) ファイルに保存し、コンポーネントのエクスポートに export default を使用する必要があります。

<template>
  <div class="page">
    ...
  </div>
</template>
<script>
  export default () => {
    let foo = 'bar';

    const doThis = () => {
      // ...
    }

    return $render;
  }
</script>

必要な依存関係とスタイルをインポートすることも可能です。

<template>
  <div class="page">
    ...
  </div>
</template>
<script>
  import './path/to/some-styles.css';
  import utils from './path/to/utils.js';

  export default () => {
    let foo = 'bar';
    let now = utils.now();

    const doThis = () => {
      // ...
    }

    return $render;
  }
</script>

そして、それをインポートしてルートに追加できます。

// routes.js

import NewsPage from './path/to/news.f7';
import ServicesPage from './path/to/services.f7';

export default [
  {
    path: '/news/',
    component: NewsPage,
  },
  {
    path: '/services/',
    component: ServicesPage,
  }
]

JSX

テンプレートリテラルは、HTML ドキュメント内では適切な構文の強調表示がされません。しかし、Webpack や Vite を使用する場合、コンポーネントを JSX 構文 で記述することも可能です。

これを実現するには、コンポーネントを .f7.jsx ファイルに保存し、JSX を使用して記述する必要があります。

export default (props, { $update }) => {
  let value = 10;
  const items = ['Item 1', 'Item 2'];

  const addValue = (number) => {
    value += number;
    $update();
  }

  //- render function should returns JSX
  return () => (
    <div class="page">
      <p>The value is {value}</p>
      <p>
        {/* JSX doesn't support @ in attribute name so event handlers should start from "on" */}
        <button onClick={() => addValue(10)}>Add Value</button>
      </p>
      <ul>
        {items.map((item) => (
          <li>{item}</li>
        ))}
      </ul>
    </div>
  )
}

そして、routes.js で同じ方法でインポートします。

import NewsPage from './path/to/news.f7.jsx';
import ServicesPage from './path/to/services.f7.jsx';

export default [
  {
    path: '/news/',
    component: NewsPage,
  },
  {
    path: '/services/',
    component: ServicesPage,
  }
]

仮想 DOM

仮想 DOM とすべての VDOM 関連機能は、Framework7 バージョン 3.1.0 から利用可能です。

仮想 DOM (VDOM) は、UI の理想的な、つまり「仮想の」表現をメモリに保持し、「実際の」DOM と同期させるプログラミング概念です。これにより、アプリケーションのビューをその状態の関数として表現できます。

VDOM ライブラリは、非常に軽量で高速であり、Framework7 環境に最適であるため、Snabbdom と呼ばれています。

では、Framework7 ルーターコンポーネントの VDOM レンダリングはどのように機能するのでしょうか?コンポーネントテンプレートは、DOM に直接挿入される代わりに、VDOM に変換されます。その後、コンポーネントの状態が変化すると、新しい VDOM が作成され、以前の VDOM と比較されます。そして、その差分に基づいて、変更する必要がある要素と属性のみを変更することで、実際の DOM にパッチを適用します。そして、これはすべて自動的に行われます!

ユーザーデータをリクエストしたときにレイアウトを自動更新するユーザープロフィールコンポーネントの例を見てみましょう。

<template>
  <div class="page">
    <div class="navbar">
      <div class="navbar-bg"></div>
      <div class="navbar-inner">
        <div class="title">Profile</div>
      </div>
    </div>
    <div class="page-content">
      ${user && $h`
        <!-- Show user list when it is loaded -->
        <div class="list simple-list">
          <ul>
            <li>First Name: ${user.firstName}</li>
            <li>Last Name: ${user.lastName}</li>
            <li>Age: ${user.age}</li>
          </ul>
        </div>
      `}
      ${!user && $h`
        <!-- Otherwise show preloader -->
        <div class="block block-strong text-align-center">
          <div class="preloader"></div>
        </div>
      `}
    </div>
  </div>
</template>
<script>
  export default (props, { $on, $f7, $update }) => {
    // empty initial user data
    let user = null;

    $on('pageInit', () => {
      // request user data on page init
      fetch('https://api.website.com/get-user-profile')
        .then((res) => res.json())
        .then((data) => {
          // update user with new data
          user = data;
          // trigger re-render
          $update();
        });
    })

    return $render;
  }
</script>

コンポーネントの状態への直接割り当ては、レイアウトの更新をトリガーしないことに注意してください。コンポーネントのレイアウトを更新する必要がある場合は、必ず $update を使用してください!

リストと自動初期化コンポーネントのキー

VDOM が要素のリストを更新する場合、デフォルトでは「インプレースパッチ」戦略を使用します。データ項目の順序が変更された場合、DOM 要素を項目の順序に合わせて移動する代わりに、各要素をインプレースでパッチ適用し、特定のインデックスでレンダリングされるべき内容を反映するようにします。

このデフォルトモードは効率的ですが、レンダー出力が子コンポーネントの状態または一時的な DOM 状態 (例:フォーム入力値) に依存していない場合にのみ 適しています。

VDOM にヒントを与えて、各ノードの ID を追跡し、既存の要素を再利用して並べ替えられるようにするには、各項目に一意の key 属性を提供する必要があります。

リストをレンダリングする場合、key の理想的な値は各項目の一意の ID です。

<template>
  ...
  <ul>
    ${items.map((item) => $h`
      <li key=${item.id}>...</li>
    `)}
  </ul>
  ...
</template>
<script>
  export default () => {
    const items = [
      {
        id: 1,
        title: 'Item A'
      },
      {
        id: 2,
        title: 'Item B'
      },
    ];

    return $render;
  }
</script>

レンジスライダーゲージ など、DOM に追加されたときに自動的に初期化 (range-slider-initgauge-init がある場合) され、DOM から削除されたときに自動的に破棄される必要がある自動初期化コンポーネントも同様です。そのため、このような要素も一意のキーで識別する必要があります。

<template>
  <div class="page">
    ...
    <div class="page-content">
      ${gaugeVisible && $h`
        <!-- must have unique key -->
        <div key="gauge" class="gauge gauge-init" data-type="circle"
          data-value="0.60"
          data-value-text="60%"
          data-value-text-color="#ff9800"
          data-border-color="#ff9800"
        ></div>
      `}
      ...
      <a href="#" class="button" @click=${showGauge}>Show Gauge</a>
    </div>
  </div>
</template>
<script>
  export default (props, { $update }) => {
    let gaugeVisible = false;

    const showGauge = () => {
      gaugeVisible = true;
      $update();
    }

    return $render;
  }
</script>
  • key 属性は、単一のコンポーネント内で一意である必要があることに注意してください。
  • key 属性が指定されておらず、要素に id 属性がある場合、id 属性が仮想ノードの一意のキーとして使用されます。

innerHTML

HTML 文字列 (たとえば、API エンドポイントから受信したもの) を挿入する必要がある場合は、特別な innerHTML 要素プロパティ/属性を使用する必要があります。

<template>
  <div class="page">
    ...
    <div class="block" innerHTML=${customHTML}></div>
  </div>
</template>
<script>
  export default (props) => {
    const customHTML = '<p>Hello <b>World!</b></p>';

    return $render;
  }
</script>

要素で innerHTML を使用すると、すべての子が上書きされます。

innerHTML に渡される HTML コンテンツは単なる文字列であり、たとえば、コンポーネントイベントハンドラー (@click 属性など) は機能しません。

メインアプリコンポーネント

アプリのレイアウト全体をコンポーネントにすることができます。

VDOM の実装により、自動初期化されたすべてのビュー (view-init クラスを持つビュー) に一意の id または key 属性を追加することを強くお勧めします。

有効にするには、まず、index.html でアプリのルート要素を空にしておく必要があります。

<body>
  <!-- empty app root element -->
  <div id="app"></div>
</body>

次に、メインアプリコンポーネントを作成する必要があります。たとえば、Vite を使用したシングルファイルコンポーネントです。

<!-- app.f7 -->
<template>
  <div id="app">
    ${loggedIn.value && $h`
      <div class="panel panel-left panel-reveal panel-init">
        <!-- every View has unique ID attribute -->
        <div class="view view-init" id="view-panel" data-url="/panel/"></div>
      </div>
      <div class="view view-main view-init" id="view-main" data-url="/"></div>
    `}
    ${!loggedIn.value && $h`
      <div class="login-screen modal-in">
        <div class="view view-init" id="view-auth" data-url="/auth/"></div>
      </div>
    `}
  </div>
</template>
<script>
  export default (props, { $store }) => {
    const loggedIn = $store.getters.loggedIn;

    return $render;
  }
</script>

最後に、Framework7 を初期化するときに、初期化時にアプリコンポーネントを指定する必要があります。

// import main app component
import App from './path/to/app.f7';

var app = new Framework7({
  // specify main app component
  component: App,
})

または、Webpack を使用しない場合は、XHR 経由でロードすることもできます。

var app = new Framework7({
  // load main app component
  componentUrl: './path/to/app.f7',
})

また、メインアプリコンポーネントは、アプリの初期化プロセスが完了する前にマウント (DOM に追加) されることにも注意してください。そのため、Framework7 API をすぐに呼び出す必要がある場合は、$f7ready コールバックを使用してください。

<template>
  <div id="app">
    ...
  </div>
</template>
<script>
  export default (props, { $f7ready, $f7 }) => {
    $f7ready(() => {
      // now it is safe to call Framework7 APIs
      $f7.dialog.alert('Hello!');
    })
  }
</script>

カスタムコンポーネント

コンポーネントの登録

再利用可能なカスタムコンポーネントを作成することができます。Framework7 の初期化前に、以下のメソッドでこれを行う必要があります.

Framework7.registerComponent(tagName, component) - カスタムコンポーネントを登録します。

  • tagName - string。コンポーネントのタグ名。例:my-component (<my-component> として使用されます)。

    カスタムコンポーネントのタグ名には、ハイフン/ダッシュ文字「-」を含める必要があります。

  • component - object または class。コンポーネント関数。

現時点では、カスタムコンポーネントはルーターコンポーネント (ルーターによってロードされるコンポーネント) でのみ使用できることに注意してください。

Framework7.registerComponent(
  // component name
  'my-list-item',

  // component function
  (props, { $h }) => {
    let foo = 'bar';

    return () => $h`
      <li class="item-content" id="${props.id}">...</li>
    `
  }
)

そして、他のコンポーネントで次のように使用します。

<div class="list">
  <ul>
    <my-list-item id="item-1"></my-list-item>
  </ul>
</div>

カスタムコンポーネント要素に渡された属性は、コンポーネントの props で使用できることに注意してください。

ローカルコンポーネント

コンポーネント内にローカルカスタムコンポーネントを作成することができます。

<template>
  <ul>
    <!-- use tag names as variables -->
    <${ListItem} title="Item 1" />
    <${ListItem} title="Item 2" />
    <${ListItem} title="Item 3" />
  </ul>
</template>
<script>
  // create local component
  const ListItem = (props, { $h }) => {
    return () => $h`<li>${props.title}</li>`;
  }

  // export main component
  export default () => {
    return $render;
  }
</script>

または、インポートすることもできます。

<template>
  <ul>
    <!-- use tag names as variables -->
    <${ListItem} title="Item 1" />
    <${ListItem} title="Item 2" />
    <${ListItem} title="Item 3" />
  </ul>
</template>
<script>
  // import component
  import ListItem from 'path/to/list-item.f7';

  // export main component
  export default () => {
    return $render;
  }
</script>

JSX を使用する場合

const ListItem = (props) => {
  return (
    <li>{props.title}</li>
  )
}
/* or
import ListItem from 'path/to/list-item.f7.jsx'
*/

export default () => {
  return () => (
    <ul>
      <ListItem title="Item 1" />
      <ListItem title="Item 2" />
      <ListItem title="Item 3" />
    </ul>
  )
}

JSX では、メインコンポーネント内で作成できます。

export default () => {

  const ListItem = (props) => {
    return (
      <li>{props.title}</li>
    )
  }

  return () => (
    <ul>
      <ListItem title="Item 1" />
      <ListItem title="Item 2" />
      <ListItem title="Item 3" />
    </ul>
  )
}

イベント

テンプレート内のカスタムコンポーネントに、同じ @{event} 構文で DOM イベントを割り当てることができます。イベントハンドラーは、実際にはカスタムコンポーネントのルート要素にアタッチされます。

<template>
  <div class="page">
    ...
    <my-button @click="onClick">Click Me</my-button>
  </div>
</template>
<script>
  return {
    // ...
    methods: {
      onClick: function(e) {
        console.log('clicked');
      }
    },
    // ...
  }
</script>

スロット

子要素 (またはテキスト) をカスタムコンポーネントに渡す必要がある場合は、スロットを使用する必要があります。ここでのスロットの実装は、Web コンポーネントのスロット に似ています。

slot タグを使用して、コンポーネントの子を配置する場所を指定します。たとえば、my-button コンポーネントのテンプレートです。

<a class="button button-fill">
  <slot></slot>
</a>

次のように使用できます。

<my-button>Click Me</my-button>

スロットのデフォルト値 (子が渡されない場合) を指定するには、<slot> タグ内に配置します。

<a class="button button-fill">
  <slot>Default Button Text</slot>
</a>

要素をコンポーネントレイアウト全体に配布するには、名前付きスロットを使用できます。たとえば、my-container コンポーネントのテンプレートです。

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

そして、次のように使用できます。

<my-container>
  <h1 slot="header">Title</h1>

  <p>Text for main content.</p>
  <p>More text for main content.</p>

  <p slot="footer">Footer content</p>
</my-container>

コンポーネントの結果出力は次のようになります。

<div class="container">
  <header>
    <h1>Title</h1>
  </header>
  <main>
    <p>Text for main content.</p>
    <p>More text for main content.</p>
  </main>
  <footer>
    <p>Footer content</p>
  </footer>
</div>

テンプレートレシピ

条件付きレンダリング

JavaScript で条件を実装するには、通常 if (if-else) ステートメントを使用します。テンプレートと JSX 内では、それらを直接使用することはできず、JavaScript 演算子を使用する必要があります。

if

if ステートメントには、論理 AND (&&) 演算子を使用する必要があります。

<template>
  <div class="page">
    ${someVar && $h`
      <p>Text will be visible when "someVar" is truthy</p>
    `}

    ${someVar === 1 && $h`
      <p>Text will be visible when "someVar" equals to 1</p>
    `}
  </div>
</template>
<script>
  export default () => {
    const someVar = 1;

    return $render;
  }
</script>

JSX を使用した場合も同じです。

export default () => {
  const someVar = 1;

  return () => (
    <div class="page">
      {someVar && (
        <p>Text will be visible when "someVar" is truthy</p>
      )}

      {someVar === 1 && (
        <p>Text will be visible when "someVar" equals to 1</p>
      )}
    </div>
  )
}

if-else

if-else には、三項演算子 (?:) または &&! 演算子の組み合わせを使用できます。

<template>
  <div class="page">
    ${someVar ? $h`
      <p>Text will be visible when "someVar" is truthy</p>
    ` : $h`
      <p>Text will be visible when "someVar" is falsy</p>
    `}

    {someVar && (
      <p>Text will be visible when "someVar" is truthy</p>
    )}
    {!someVar && (
      <p>Text will be visible when "someVar" is falsy</p>
    )}
  </div>
</template>
<script>
  export default () => {
    const someVar = 1;

    return $render;
  }
</script>

JSX を使用した場合も同じです。

export default () => {
  const someVar = 1;

  return () => (
    <div class="page">
      {someVar ? (
        <p>Text will be visible when "someVar" is truthy</p>
      ) : (
        <p>Text will be visible when "someVar" is falsy</p>
      )}

      {someVar && (
        <p>Text will be visible when "someVar" is truthy</p>
      )}
      {!someVar && (
        <p>Text will be visible when "someVar" is falsy</p>
      )}

    </div>
  )
}

配列を要素にマッピングする

配列を要素にマッピングするには、配列の .map() メソッドを使用します。

<template>
  <div class="page">
    <ul>
    ${items.map((item) => $h`
      <li>${item}</li>
    `)}
    </ul>
  </div>
</template>
<script>
  export default () => {
    const items = [
      'item 1',
      'item 2',
      'item 3',
    ];

    return $render;
  }
</script>

JSX を使用した場合も同じです。

export default () => {
  const items = [
    'item 1',
    'item 2',
    'item 3',
  ];

  return () => (
    <div class="page">
      <ul>
        {items.map((item) => (
          <li>{item}</li>
        ))}
      </ul>
    </div>
  )
}