> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# フック

> Aftersell Cart の動作そのものを変更します: ラインの変換、Storefront データによるエンリッチ、サブスクリプションオプションの加工、カート追加の制御。

[イベント](/ja/aftersell/cart/sdk-events)がカートに*反応*させ、[アクション](/ja/aftersell/cart/sdk-actions)がカートを*変更*させるのに対し、**フック**はカート自体の動作を変更します。ラインのレンダリング方法、ラインが持つデータ、カート追加時に何が起こるかです。

フックは `window.aftersell.cart.hooks` の下にあります。

<Note>
  フックは買い物客が**見るもの**を変え、アクションは**カートの中身**を変えます。変換で無料ギフトのラインを非表示にしても、それはカートと合計金額に残ります。[`removeItem`](/ja/aftersell/cart/sdk-actions#removeitemkey) で削除すれば、実際に取り除かれます。
</Note>

<Note>
  フックはセットアップの呼び出しなので、`ready()` を待たずにスクリプトの冒頭で安全に登録できます。カートの **Initialization** スクリプトで登録してください（[カスタムスクリプト](/ja/aftersell/cart/custom-scripts)を参照）。
</Note>

<div id="how-registration-works">
  ## 登録の仕組み
</div>

すべてのフックは `register*` メソッドです。あなたの関数を渡して呼び出すと、それを削除するために呼び出せる**登録解除関数**が返されます。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const off = window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);

// later: off();
```

登録は**追加式**なので、あなたの関数は他のすべての関数と並行して実行されます。これが重要なのは、ページ上のスクリプトがあなたのものだけであることはまれだからです。サブスクリプションアプリ、バンドルアプリ、テーマ自体が、すべて同じフックに登録しているかもしれません。どれもあなたの関数を置き換えることはできず、あなたが登録したものが、後から読み込まれるものによって静かに落とされることもありません。

| フック                                                                                       | 役割                                                                                          | 複数登録された場合                            |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------ |
| [`registerLineTransform`](#registerlinetransform)                                         | 個々のラインを非表示にしたりラベルを変えたりします。                                                                  | すべてが登録順に実行されます。                      |
| [`registerLineComparator`](#registerlinecomparator)                                       | レンダリングされるラインを並べ替えます。                                                                        | タイブレーカーとして合成されます。                    |
| [`registerCartEnricher`](#registercartenricher)                                           | 各ラインに追加の Storefront データを付与します。                                                              | すべて実行されます。各 `id` が独自の名前空間です。         |
| [`registerSubscriptionOptionsTransform`](#registersubscriptionoptionstransform)           | ラインの販売プランを非表示にしたり名前を変えたりします。                                                                | すべて実行されます。パッチはプランごと・フィールドごとにマージされます。 |
| [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector) | どのプランを事前選択するかを決めます。                                                                         | 最初の非 `null` の回答が採用されます。              |
| [`registerSkipAddToCartRule`](#registerskipaddtocartrule)                                 | 特定のフォームにカートをバイパスさせます。[カート追加のインターセプト](/ja/aftersell/cart/add-to-cart-interception)を参照してください。 | いずれかのルールが `true` を返すとスキップされます。       |

throw したフックや関数でないフックはスキップされ、残りは引き続き実行され、カートは動き続けます。1 つの壊れた連携が、カート追加、サブスクリプションピッカー、並び替えを止めることはできません。

裏を返せば、あなたの壊れたフックは**静かに**失敗します。ブラウザのコンソールには何も届きません。そうした失敗がどこに現れるかは、[デバッグ](/ja/aftersell/cart/sdk-overview#debugging)を参照してください。

***

<div id="registerlinetransform">
  ## registerLineTransform
</div>

`registerLineTransform(fn)` は、レンダリング前のすべてのカートラインに対して実行されます。買い物客のカートの実際の中身に触れることなく、ラインを非表示にしたり表示を変えたりするために使います。

関数は読み取り専用のラインとセッターを受け取ります。戻り値は登録解除関数です。

| セッター                              | 効果                                                                                                            |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `setHidden(bool)`                 | ラインをドロワーから非表示にします。カートと合計金額には残ります。                                                                             |
| `setTitle(string)`                | 表示タイトルを変更します。                                                                                                 |
| `setVariantTitle(string \| null)` | 表示されるバリアントラベルを変更します。                                                                                          |
| `setInternalProperties(obj)`      | レンダリング専用のプロパティをマージします。Shopify に永続化されることはありません。[バンドルラインのグループ化](/ja/aftersell/cart/sdk-use-case-bundles)に使われます。 |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Hide free gift lines from the drawer. The cart total is unaffected.
const off = window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.finalLinePrice === 0) {
    line.setHidden(true);
  }
  if (line.sellingPlan) {
    line.setVariantTitle(`Delivered ${line.sellingPlan.name.toLowerCase()}`);
  }
});

// later: off();
```

<Warning>
  変換が変えるのはレンダリングされるものだけです。価格、数量、ラインの同一性は変えられません。それには[アクション](/ja/aftersell/cart/sdk-actions)を使ってください。
</Warning>

**用途:** 購入特典ギフトやアプリが挿入したラインの非表示、サブスクリプションラインのラベル変更、割引アイテムのタグ付け、買い物客が個別に操作すべきでないバンドル構成品の非表示などです。

`setInternalProperties` はバンドルグループ化の裏側にあるセッターです。正規のバンドルプロパティを各ラインに刻印することで、サードパーティアプリの個別のカートラインを 1 つのアイテムとしてレンダリングさせます。[他のアプリのバンドルラインをグループ化する](/ja/aftersell/cart/sdk-use-case-bundles)を参照してください。

<div id="registerlinecomparator">
  ## registerLineComparator
</div>

`Array.prototype.sort` が期待するのと同じ形のコンパレータです。非表示とリネームの後に実行されるため、変換後のラインを見ます。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Subscriptions first, then everything else.
window.aftersell.cart.hooks.registerLineComparator((lineA, lineB) => {
  return (lineB.sellingPlan ? 1 : 0) - (lineA.sellingPlan ? 1 : 0);
});
```

コンパレータは**タイブレーカーとして合成されます**。最初にゼロ以外の値を返したものがそのペアを決定し、残りは同点の場合にのみ参照されます。意見のないペアには `0` を返してください。それが、順序を強制する代わりに次のコンパレータに判断を委ねる方法です。

**用途:** サブスクリプションや高額アイテムを上部に浮かせる、無料ギフトやアドオンを下部に沈める、スポンサー商品を先頭に保つなどです。

<div id="registercartenricher">
  ## registerCartEnricher
</div>

`registerCartEnricher(registration)` は、Shopify Storefront API から追加の商品またはバリアントのデータを取得し、一致する各カートラインの `line.metadata[id]` に付与します。メタフィールド、タグ、その他 Storefront API が公開するあらゆるものを、Aftersell 側のコード変更なしで表示するために使います。

| フィールド      | 型                                  | 説明                                                                        |
| ---------- | ---------------------------------- | ------------------------------------------------------------------------- |
| `id`       | `string`                           | 結果の名前空間。`line.metadata[id]` に格納されます。一意である必要があり、同じ `id` での 2 回目の登録は無視されます。 |
| `onType`   | `'Product'` または `'ProductVariant'` | フラグメントが対象とするノード。結合キー（商品 ID かバリアント ID か）でもあります。                            |
| `fragment` | `string`                           | Storefront クエリに差し込まれる GraphQL のフィールド選択（外側の波括弧なし）。波括弧はバランスしている必要があります。     |

戻り値は**登録解除関数**です。

カートが読み込まれるか変更されるたびに、Aftersell はカート上のすべての商品またはバリアントについてフラグメントを取得し、結果を付与します。この取得はノンブロッキングです。カートは即座にレンダリングされ、データが届くと `cart_updated` を再発行します。遅い、あるいは失敗するフラグメントがカートを遅らせたり壊したりすることはありません。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'pricing',
  onType: 'ProductVariant',
  fragment: `
    anchorPrice: metafield(namespace: "custom", key: "anchor_price") { value }
    subscriberPrice: metafield(namespace: "custom", key: "subscriber_price") { value }
  `,
});

// Read it once the data arrives.
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    const anchor = line.metadata.pricing?.anchorPrice;
    if (anchor) console.log(line.title, 'anchor price', anchor.value);
  });
});
```

エンリッチメントは非同期なので、読み取りは必ずガードしてください。最初の取得が解決するまで `line.metadata.pricing` は `undefined` で、`metadata` 自体のデフォルトは `{}` です。

**用途:** メタフィールドをすべてのラインに引き込み（配送見積もり、成分リスト、「別送」フラグ、ロイヤルティ倍率）、[Custom code ブロック](/ja/aftersell/cart/custom-code-blocks)を通じてレンダリングすることです。[カートラインにメタフィールドデータを表示する](/ja/aftersell/cart/sdk-use-case-metafields)を参照してください。

<Note>
  各 `id` が独自の名前空間なので、複数のエンリッチャーは問題なく共存し、データが衝突することはありません。
</Note>

<Warning>
  エンリッチされた値は Storefront API からそのまま返され、サニタイズ**されていません**。生の HTML としてではなく、テキストとしてレンダリングしてください。
</Warning>

<div id="registersubscriptionoptionstransform">
  ## registerSubscriptionOptionsTransform
</div>

ラインで提示される販売プランを非表示にしたり名前を変えたりします。関数は読み取り専用のオプションとセッターを受け取り、何も返しません。

| セッター              | 効果                 |
| ----------------- | ------------------ |
| `setHidden(bool)` | プランをピッカーから非表示にします。 |
| `setName(string)` | 表示されるプラン名を変更します。   |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSubscriptionOptionsTransform((options, context) => {
  // context: { productId, variantId }
  options.forEach((option) => {
    if (option.discountPercent === 0) option.setHidden(true);
    option.setName(option.name.replace('Every ', ''));
  });
});
```

**リストを返すのではなくセッターを使うのは、複数のスクリプトが共存できるようにするためです。** このフックが配列を返す仕様だったら、1 つのプランにしか関心のない変換が自然に `options.filter(...)` を書き、他のアプリのプランをすべて静かに削除してしまうでしょう。セッターなら自分の編集だけを記述できます。パッチはプランごと・フィールドごとにマージされ、同じプランの同じフィールドで実際に競合した場合は最後の書き込みが勝ちます。throw した変換は何も反映されず、他の変換は引き続き適用されます。

すべての変換は、途中までパッチされたビューではなく*元の*オプションを見るため、登録順序が読み取る内容を変えることはありません。

<Note>
  プランの順序は Shopify が返したままなので、変換で並べ替えることはできません。どのプランを最初に提示するか（そして単発購入のアップグレードボタンがどのプランを購読するか）を制御するには、選んだものを先頭に昇格させる [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector) を使ってください。
</Note>

プランを*追加*したり価格を変更したりもできません。`discountPercent` にセッターがないのは、チェックアウトで Shopify が受け付けないプランは、ピッカーの中の破られた約束にしかならないからです。

<div id="registerdefaultsubscriptionoptionselector">
  ## registerDefaultSubscriptionOptionSelector
</div>

ラインでどのプランを事前選択するかを決めます。プランの `id` を返すか、判断を委ねる場合は `null` を返します。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerDefaultSubscriptionOptionSelector((options) => {
  const best = options
    .slice()
    .sort((optionA, optionB) => optionB.discountPercent - optionA.discountPercent)[0];
  return best ? best.id : null;
});
```

**利用可能なプランの id を最初に返したセレクターが勝つ**ため、関心のないラインには推測せず `null` を返してください。それにより、上書きするのではなく次のセレクターに判断が渡されます。ライン上のどのプランにも一致しない id は `null` と同じ扱いになり、同様に判断を委ねるため、古い id がピッカーを空白にすることはありません。

関数は `(options, context)` を受け取ります。`context` はオプション変換が受け取るものと同じです。

<div id="registerskipaddtocartrule">
  ## registerSkipAddToCartRule
</div>

`true` を返すと、特定の商品フォームは Aftersell を完全にバイパスして通常どおりカートに追加します。独自のリダイレクトや処理が必要なフォームに便利です。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);
```

**いずれかが `true` ならスキップされる**ため、ルールは狭く保ち、自分が管理する特定のフォームだけに一致させ、それ以外にはすべて `false` を返してください。ルールは登録順に評価され、最初の `true` で停止するため、ルールに副作用を入れないでください。あなたのルールが実行されるかどうかは、その前に登録されたものに依存します。

<Tip>
  フォームのマークアップを管理できるなら、フックは一切不要です。`<form>` にクラス **`aftersell-cart-skip-atc`** を追加すれば、Aftersell はそのフォームに手を出しません。マークアップを編集できない場合や、判断がコードだけが知っている条件に依存する場合に、このフックを使ってください。
</Tip>

**用途:** 独自のリダイレクトが必要な予約注文や見積もりフォーム、サブスクリプションアプリのカスタムフロー、チェックアウトに直行すべき「今すぐ購入」ボタンなどです。ページ全体のインターセプトをオフにするには [`skip_add_to_cart_interceptor`](/ja/aftersell/cart/sdk-configure#skip_add_to_cart_interceptor) を使えますが、指定したフォームにスコープされるこのフックの方が望ましいです。

<div id="where-to-go-next">
  ## 次のステップ
</div>

* **[カートオブジェクト](/ja/aftersell/cart/sdk-cart-object)**: 変換が受け取るラインの構造。
* **[イベント](/ja/aftersell/cart/sdk-events)**: 購読できるすべてのもの。
* **[アクション](/ja/aftersell/cart/sdk-actions)**: カートの読み取りと変更。
* **[ユースケース](/ja/aftersell/cart/sdk-use-cases)**: よくある要望に対する完全なソリューション。
