Web Components イベント設計チート:`CustomEvent`/`composed`/`bubbles` 実例集
Web Components でカスタムイベントを設計する際、CustomEvent のオプション設定で迷った経験はありませんか。composed や bubbles の違いを理解していないと、Shadow DOM の境界を越えられず、イベントが親コンポーネントに届かないという問題に直面します。
この記事では、Web Components におけるイベント設計の要となる CustomEvent、composed、bubbles の 3 つのキーワードに絞って、実例を交えながら解説していきます。イベント伝播の仕組みを図解とコードで理解し、実務で使えるパターンを身につけましょう。
イベント設計早見表
まず、CustomEvent のオプションとその効果を表にまとめました。実装時のクイックリファレンスとしてご活用ください。
| # | プロパティ | 型 | デフォルト値 | 説明 | 使用シーン |
|---|---|---|---|---|---|
| 1 | bubbles | boolean | false | イベントが DOM ツリーを上方向に伝播するか | 親要素でイベントをキャッチしたい場合 |
| 2 | composed | boolean | false | イベントが Shadow DOM の境界を越えるか | Shadow DOM 外の要素でイベントを検知したい場合 |
| 3 | cancelable | boolean | false | preventDefault() でキャンセル可能か | デフォルト動作を防ぎたい場合 |
| 4 | detail | any | null | イベントに付随するカスタムデータ | イベントと一緒にデータを渡したい場合 |
組み合わせパターン早見表
| # | bubbles | composed | 伝播範囲 | 用途例 |
|---|---|---|---|---|
| 1 | false | false | Shadow DOM 内のみ | コンポーネント内部の状態変更 |
| 2 | true | false | Shadow DOM 内で上方向 | Shadow 内の親要素への通知 |
| 3 | false | true | Shadow DOM 境界を越えるが上昇しない | 外部への通知(バブリング不要) |
| 4 | true | true | 全範囲(境界を越えて上昇) | グローバルイベント(クリック、入力など) |
この早見表を見ながら、以下の詳細解説を読み進めていただくと理解が深まります。
背景
Web Components とイベントシステム
Web Components は、カスタム要素、Shadow DOM、HTML テンプレートという 3 つの技術で構成される標準仕様です。特に Shadow DOM は、スタイルや DOM ツリーをカプセル化できる強力な機能ですが、同時にイベントの伝播にも影響を与えます。
通常の DOM では、子要素で発生したイベントは親要素へと自動的に伝播(バブリング)していきます。しかし、Shadow DOM が絡むと、この挙動が変わるのです。
以下の図は、通常の DOM と Shadow DOM におけるイベント伝播の違いを示しています。
mermaidflowchart TB
subgraph Light["Light DOM(通常の DOM)"]
L1["document"]
L2["body"]
L3["div"]
L4["button"]
end
subgraph Shadow["Shadow DOM 環境"]
S1["document"]
S2["body"]
S3["custom-element<br/>(Shadow Host)"]
S4["#shadow-root"]
S5["button"]
end
L4 -->|"イベント<br/>バブリング"| L3
L3 -->|"イベント<br/>バブリング"| L2
L2 -->|"イベント<br/>バブリング"| L1
S5 -.->|"境界で<br/>ブロック"| S4
S4 -.->|"composed: true<br/>なら通過"| S3
S3 -->|"イベント<br/>バブリング"| S2
S2 -->|"イベント<br/>バブリング"| S1
通常の DOM ではイベントが自然に上昇しますが、Shadow DOM では #shadow-root という境界が存在します。この境界を越えるかどうかを制御するのが composed プロパティです。
イベント伝播の 2 つの段階
JavaScript のイベントシステムには、3 つのフェーズがあります。
- キャプチャフェーズ:イベントが document からターゲット要素へ下降
- ターゲットフェーズ:イベントがターゲット要素に到達
- バブリングフェーズ:イベントがターゲット要素から document へ上昇
bubbles プロパティは、このバブリングフェーズでイベントが伝播するかを制御します。composed プロパティは、Shadow DOM の境界を越える際に適用されるのです。
課題
Shadow DOM 境界でイベントが届かない問題
Web Components を初めて実装する際、最も頻繁に遭遇するのが「イベントが親に届かない」という問題です。これは CustomEvent のデフォルト設定が原因で発生します。
以下は、よくある失敗例です。
typescript// custom-button.ts(失敗例)
class CustomButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<button>クリック</button>
`;
const button = this.shadowRoot.querySelector('button');
button.addEventListener('click', () => {
// デフォルト設定のイベント
const event = new CustomEvent('custom-click');
this.dispatchEvent(event);
});
}
}
このコンポーネントを使用する側で、以下のようにイベントをキャッチしようとします。
typescript// 親コンポーネントまたは HTML
const customButton =
document.querySelector('custom-button');
customButton.addEventListener('custom-click', () => {
console.log('イベントが発火!'); // 実行されない
});
この実装では、イベントが親要素に届きません。なぜなら、CustomEvent のデフォルト設定では bubbles: false、composed: false となっているからです。
次の図は、この問題を視覚化したものです。
mermaidflowchart TB
Doc["document"]
Body["body"]
Host["<custom-button><br/>(Shadow Host)"]
Root["#shadow-root"]
Btn["<button>"]
Btn -->|"1. click"| Btn
Btn -.->|"2. CustomEvent<br/>dispatched"| Host
Root -.->|"❌ composed: false<br/>境界で停止"| Host
style Root fill:#ffcccc
style Host fill:#ffcccc
イベントは Shadow Root の境界で止まってしまい、外部のリスナーには届きません。この問題を解決するには、bubbles と composed の両方を理解する必要があります。
イベント設計の複雑さ
もう 1 つの課題は、「どのイベントにどの設定を使うべきか」という判断の難しさです。すべてのイベントに bubbles: true, composed: true を設定すれば動作しますが、これはカプセル化の原則に反します。
例えば、以下のような状況を考えてみましょう。
| # | シチュエーション | 望ましい動作 | 設定の組み合わせ |
|---|---|---|---|
| 1 | ユーザーがボタンをクリック | グローバルに検知したい | bubbles: true, composed: true |
| 2 | コンポーネント内部の状態変更 | 外部に漏らしたくない | bubbles: false, composed: false |
| 3 | フォーム送信イベント | 親フォーム要素で検知 | bubbles: true, composed: true |
| 4 | スロットコンテンツの変更 | Shadow 内の親のみ | bubbles: true, composed: false |
このように、状況に応じて適切な設定を選択する必要がありますが、その判断基準が明確でないと実装に迷ってしまいます。
解決策
bubbles の役割と使い分け
bubbles プロパティは、イベントが DOM ツリーを上方向に伝播するかを制御します。true に設定すると、イベントは親要素、その親要素、さらにその親要素へと伝播していきます。
以下のコードは、bubbles の違いを示しています。
typescript// bubbles: false の場合
const nonBubblingEvent = new CustomEvent('non-bubble', {
bubbles: false,
detail: { message: 'このイベントは伝播しない' },
});
typescript// bubbles: true の場合
const bubblingEvent = new CustomEvent('bubble', {
bubbles: true,
detail: { message: 'このイベントは伝播する' },
});
bubbles: true を使うべきシーンは以下の通りです。
- ユーザーインタラクション(クリック、入力など)を親要素で処理したい
- イベント委譲(Event Delegation)パターンを使いたい
- 複数の親要素でイベントを監視したい
逆に、bubbles: false が適切なシーンは以下です。
- コンポーネント内部のみで完結する処理
- 特定の要素でのみキャッチすべきイベント
- パフォーマンスを最適化したい場合(不要な伝播を防ぐ)
composed の役割と使い分け
composed プロパティは、イベントが Shadow DOM の境界を越えるかを制御します。これは Web Components 特有の設定で、通常の DOM イベントにはない概念です。
以下の図は、composed の有無による動作の違いを示しています。
mermaidflowchart LR
subgraph Inside["Shadow DOM 内部"]
Elem["イベント発生元"]
end
Border["Shadow Root<br/>(境界)"]
subgraph Outside["Light DOM(外部)"]
Host["Shadow Host"]
Parent["親要素"]
end
Elem -->|"イベント発火"| Border
Border -->|"composed: false<br/>❌ ブロック"| BlockedPath[ ]
Border -->|"composed: true<br/>✅ 通過"| Host
Host --> Parent
style Border fill:#ffffcc
style BlockedPath fill:#ffcccc
style Host fill:#ccffcc
style Parent fill:#ccffcc
composed: true を使うべきシーンは以下の通りです。
typescript// ユーザーインタラクションイベント
class InteractiveButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
emitClickEvent() {
// 外部でキャッチする必要があるイベント
const event = new CustomEvent('button-clicked', {
bubbles: true,
composed: true, // Shadow DOM 境界を越える
detail: { timestamp: Date.now() },
});
this.dispatchEvent(event);
}
}
composed: false を使うべきシーンは以下です。
typescript// 内部状態の変更イベント
class InternalComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
notifyInternalChange() {
// Shadow DOM 内部でのみキャッチ
const event = new CustomEvent('internal-update', {
bubbles: true,
composed: false, // カプセル化を維持
detail: { state: this.internalState },
});
this.shadowRoot.dispatchEvent(event);
}
}
4 つの組み合わせパターン
bubbles と composed の組み合わせにより、4 つの基本パターンが生まれます。以下の表でそれぞれの特徴をまとめました。
| # | パターン | 設定 | 伝播範囲 | 使用例 |
|---|---|---|---|---|
| 1 | 内部限定 | bubbles: falsecomposed: false | ディスパッチした要素のみ | デバッグイベント、内部ログ |
| 2 | Shadow 内バブリング | bubbles: truecomposed: false | Shadow DOM 内の親要素まで | スロット変更、内部状態同期 |
| 3 | 境界越え単発 | bubbles: falsecomposed: true | Shadow Host まで(上昇なし) | 初期化完了通知 |
| 4 | グローバル伝播 | bubbles: truecomposed: true | すべての親要素まで | クリック、フォーカス、入力 |
それぞれのパターンを実装例で見ていきましょう。
typescript// パターン 1: 内部限定イベント
const internalEvent = new CustomEvent('debug-log', {
bubbles: false,
composed: false,
detail: {
level: 'info',
message: 'Internal state changed',
},
});
this.dispatchEvent(internalEvent);
typescript// パターン 2: Shadow 内バブリング
const shadowEvent = new CustomEvent('slot-changed', {
bubbles: true,
composed: false,
detail: { slotName: 'header' },
});
this.dispatchEvent(shadowEvent);
typescript// パターン 3: 境界越え単発
const readyEvent = new CustomEvent('component-ready', {
bubbles: false,
composed: true,
detail: { version: '1.0.0' },
});
this.dispatchEvent(readyEvent);
typescript// パターン 4: グローバル伝播
const clickEvent = new CustomEvent('item-selected', {
bubbles: true,
composed: true,
detail: { itemId: 42 },
});
this.dispatchEvent(clickEvent);
これらのパターンを理解することで、目的に応じた適切なイベント設計ができるようになります。
detail によるデータ受け渡し
CustomEvent の detail プロパティを使うと、イベントと一緒に任意のデータを渡せます。これにより、イベントリスナー側で必要な情報を取得できます。
typescript// イベント発火側
class DataEmitter extends HTMLElement {
sendData() {
const event = new CustomEvent('data-updated', {
bubbles: true,
composed: true,
detail: {
userId: 123,
userName: 'Taro Yamada',
timestamp: new Date().toISOString(),
metadata: {
source: 'user-profile',
action: 'update',
},
},
});
this.dispatchEvent(event);
}
}
typescript// イベント受信側
element.addEventListener('data-updated', (event) => {
// event.detail から情報を取得
console.log(event.detail.userId); // 123
console.log(event.detail.userName); // 'Taro Yamada'
console.log(event.detail.metadata); // { source: ..., action: ... }
});
detail には、オブジェクト、配列、プリミティブ値など、任意の JavaScript 値を設定できます。型安全性を高めたい場合は、TypeScript で型定義を行うと良いでしょう。
具体例
実例 1: カスタムボタンコンポーネント
まず、最も基本的なカスタムボタンコンポーネントを実装します。このボタンは、クリックイベントを外部に伝播させます。
typescript// custom-button.ts
class CustomButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.setupEventListeners();
}
typescript // レンダリング処理
render() {
this.shadowRoot.innerHTML = `
<style>
button {
padding: 12px 24px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background: #0056b3;
}
</style>
<button>
<slot>ボタン</slot>
</button>
`;
}
typescript // イベントリスナー設定
setupEventListeners() {
const button = this.shadowRoot.querySelector('button');
button.addEventListener('click', (e) => {
// カスタムイベントを発火
const customEvent = new CustomEvent('custom-click', {
bubbles: true, // DOM ツリーを上昇
composed: true, // Shadow DOM 境界を越える
detail: {
originalEvent: e,
timestamp: Date.now(),
buttonText: button.textContent
}
});
this.dispatchEvent(customEvent);
});
}
}
customElements.define('custom-button', CustomButton);
このボタンコンポーネントを使う側では、以下のようにイベントをキャッチできます。
html<!-- HTML での使用例 -->
<custom-button id="myButton">送信</custom-button>
<script>
const button = document.getElementById('myButton');
button.addEventListener('custom-click', (event) => {
console.log('ボタンがクリックされました!');
console.log('タイムスタンプ:', event.detail.timestamp);
console.log('ボタンテキスト:', event.detail.buttonText);
});
</script>
次の図は、このイベントフローを示しています。
mermaidsequenceDiagram
participant User as ユーザー
participant Btn as Shadow内button
participant CB as custom-button
participant Doc as document
User->>Btn: クリック
Btn->>CB: click イベント
CB->>CB: CustomEvent 作成<br/>(bubbles: true, composed: true)
CB->>Doc: custom-click 発火
Doc->>Doc: イベントリスナー実行
Doc-->>User: 処理完了
このパターンは、ユーザーインタラクションを外部に通知する最も一般的な実装です。
実例 2: フォーム入力コンポーネント
次に、入力値の変更を通知するフォームコンポーネントを実装します。このコンポーネントは、入力値が変更されるたびにイベントを発火します。
typescript// custom-input.ts
class CustomInput extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._value = '';
}
connectedCallback() {
this.render();
this.setupEventListeners();
}
typescript render() {
this.shadowRoot.innerHTML = `
<style>
.input-wrapper {
display: flex;
flex-direction: column;
gap: 4px;
}
label {
font-size: 14px;
font-weight: bold;
color: #333;
}
input {
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
input:focus {
outline: none;
border-color: #007bff;
}
</style>
<div class="input-wrapper">
<label>${this.getAttribute('label') || 'Input'}</label>
<input type="text" />
</div>
`;
}
typescript setupEventListeners() {
const input = this.shadowRoot.querySelector('input');
// input イベント(リアルタイム入力)
input.addEventListener('input', (e) => {
this._value = e.target.value;
const event = new CustomEvent('value-changed', {
bubbles: true,
composed: true,
detail: {
value: this._value,
event: 'input'
}
});
this.dispatchEvent(event);
});
typescript // change イベント(フォーカス離脱時)
input.addEventListener('change', (e) => {
const event = new CustomEvent('value-committed', {
bubbles: true,
composed: true,
detail: {
value: this._value,
event: 'change'
}
});
this.dispatchEvent(event);
});
}
// 外部から値を取得・設定できるプロパティ
get value() {
return this._value;
}
set value(val) {
this._value = val;
const input = this.shadowRoot.querySelector('input');
if (input) input.value = val;
}
}
customElements.define('custom-input', CustomInput);
使用例は以下の通りです。
html<!-- HTML での使用例 -->
<custom-input id="nameInput" label="お名前"> </custom-input>
<script>
const input = document.getElementById('nameInput');
// リアルタイムで値の変更を監視
input.addEventListener('value-changed', (event) => {
console.log('入力中:', event.detail.value);
});
// 確定時の値を取得
input.addEventListener('value-committed', (event) => {
console.log('確定値:', event.detail.value);
});
</script>
このパターンでは、2 種類のイベントを使い分けることで、入力中と確定時の処理を分離しています。両方のイベントで bubbles: true, composed: true を設定することで、フォーム全体で入力を管理できます。
実例 3: アコーディオンコンポーネント(内部イベント活用)
最後に、内部イベントを活用したアコーディオンコンポーネントを実装します。このコンポーネントは、開閉状態を内部で管理しつつ、外部には状態変更のみを通知します。
typescript// custom-accordion.ts
class CustomAccordion extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._isOpen = false;
}
connectedCallback() {
this.render();
this.setupEventListeners();
}
typescript render() {
this.shadowRoot.innerHTML = `
<style>
.accordion {
border: 1px solid #ddd;
border-radius: 4px;
}
.header {
padding: 16px;
background: #f5f5f5;
cursor: pointer;
user-select: none;
display: flex;
justify-content: space-between;
align-items: center;
}
.header:hover {
background: #e0e0e0;
}
.icon {
transition: transform 0.3s;
}
.icon.open {
transform: rotate(180deg);
}
.content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.content.open {
max-height: 500px;
padding: 16px;
}
</style>
<div class="accordion">
<div class="header">
<slot name="title">タイトル</slot>
<span class="icon">▼</span>
</div>
<div class="content">
<slot name="content">コンテンツ</slot>
</div>
</div>
`;
}
typescript setupEventListeners() {
const header = this.shadowRoot.querySelector('.header');
const content = this.shadowRoot.querySelector('.content');
const icon = this.shadowRoot.querySelector('.icon');
header.addEventListener('click', () => {
// 内部状態変更イベント(Shadow 内のみ)
const internalEvent = new CustomEvent('accordion-toggling', {
bubbles: true,
composed: false, // 内部イベント
detail: {
currentState: this._isOpen,
nextState: !this._isOpen
}
});
this.shadowRoot.dispatchEvent(internalEvent);
// 状態を更新
this._isOpen = !this._isOpen;
content.classList.toggle('open');
icon.classList.toggle('open');
typescript // 外部通知イベント(状態変更完了)
const externalEvent = new CustomEvent('accordion-toggled', {
bubbles: true,
composed: true, // 外部に通知
detail: {
isOpen: this._isOpen,
timestamp: Date.now()
}
});
this.dispatchEvent(externalEvent);
});
}
get isOpen() {
return this._isOpen;
}
}
customElements.define('custom-accordion', CustomAccordion);
使用例は以下の通りです。
html<!-- HTML での使用例 -->
<custom-accordion id="myAccordion">
<span slot="title">詳細情報</span>
<div slot="content">
<p>ここにアコーディオンの内容が表示されます。</p>
<p>複数の段落を含めることができます。</p>
</div>
</custom-accordion>
<script>
const accordion = document.getElementById('myAccordion');
// 外部イベントのみをキャッチ
accordion.addEventListener(
'accordion-toggled',
(event) => {
console.log(
'アコーディオン状態:',
event.detail.isOpen ? '開' : '閉'
);
// アナリティクス送信などの処理
if (event.detail.isOpen) {
sendAnalytics('accordion_opened');
}
}
);
</script>
このコンポーネントの特徴は、内部イベント(accordion-toggling)と外部イベント(accordion-toggled)を使い分けている点です。内部イベントは Shadow DOM 内でのみ伝播し、デバッグや内部コンポーネント間の連携に使えます。
次の図は、このイベントフローを示しています。
mermaidflowchart TB
User["ユーザークリック"]
Header["header 要素"]
subgraph Shadow["Shadow DOM 内"]
Internal["内部イベント<br/>accordion-toggling<br/>(composed: false)"]
State["状態更新<br/>UI 変更"]
end
External["外部イベント<br/>accordion-toggled<br/>(composed: true)"]
Parent["親要素・document"]
User --> Header
Header --> Internal
Internal --> State
State --> External
External --> Parent
style Shadow fill:#ffffcc
style Internal fill:#ffcccc
style External fill:#ccffcc
この設計により、カプセル化を維持しながら、必要な情報のみを外部に公開できます。
TypeScript での型安全な実装
実務では TypeScript を使って型安全なイベント設計を行うことが推奨されます。以下は、カスタムイベントの型定義例です。
typescript// event-types.ts(型定義)
interface CustomClickDetail {
timestamp: number;
buttonText: string;
originalEvent: MouseEvent;
}
interface ValueChangedDetail {
value: string;
event: 'input' | 'change';
}
interface AccordionToggledDetail {
isOpen: boolean;
timestamp: number;
}
typescript// 型安全なイベント作成ヘルパー
function createTypedEvent<T>(
eventName: string,
detail: T,
options: Partial<CustomEventInit<T>> = {}
): CustomEvent<T> {
return new CustomEvent<T>(eventName, {
bubbles: true,
composed: true,
...options,
detail,
});
}
typescript// 使用例
class TypedButton extends HTMLElement {
emitClick(e: MouseEvent) {
const event = createTypedEvent<CustomClickDetail>(
'custom-click',
{
timestamp: Date.now(),
buttonText: this.textContent || '',
originalEvent: e,
}
);
this.dispatchEvent(event);
}
}
typescript// イベントリスナー側(型推論が効く)
button.addEventListener(
'custom-click',
(event: CustomEvent<CustomClickDetail>) => {
// detail の型が推論される
console.log(event.detail.timestamp); // number
console.log(event.detail.buttonText); // string
}
);
この型定義により、IDE の補完機能が使えるようになり、実装ミスを防げます。
まとめ
Web Components のイベント設計では、CustomEvent、composed、bubbles の 3 つのキーワードを正しく理解することが重要です。それぞれの役割を改めてまとめます。
CustomEvent の基本
- カスタムイベントを作成するための標準 API
detailプロパティで任意のデータを渡せるbubbles、composed、cancelableなどのオプションで動作を制御
bubbles の使い分け
true:DOM ツリーを上方向に伝播させたい場合(ユーザーインタラクション、イベント委譲)false:特定の要素でのみキャッチしたい場合(内部処理、パフォーマンス最適化)
composed の使い分け
true:Shadow DOM 境界を越えて外部に通知したい場合(グローバルイベント)false:Shadow DOM 内でカプセル化を維持したい場合(内部イベント)
4 つの組み合わせパターン
| パターン | bubbles | composed | 用途 |
|---|---|---|---|
| 内部限定 | false | false | デバッグ、内部ログ |
| Shadow 内バブリング | true | false | 内部状態同期 |
| 境界越え単発 | false | true | 初期化通知 |
| グローバル伝播 | true | true | ユーザーインタラクション |
これらの知識を活用することで、カプセル化を維持しながら、必要な情報を適切に外部に公開できるようになります。実装時は早見表を参考にしながら、目的に応じた設定を選択してください。
TypeScript を使った型安全な実装により、さらに保守性の高いコードを書けます。Web Components のイベント設計をマスターして、再利用可能なコンポーネントを作成しましょう。
関連リンク
articleWeb Components イベント設計チート:`CustomEvent`/`composed`/`bubbles` 実例集
articleWeb Components のポリフィル戦略:@webcomponents 系を最小限で入れる判断基準
articleWeb Components と Declarative Shadow DOM の現在地:HTML だけで描くサーバー発 UI
articleWeb Components のパッケージ配布戦略:types/CEM(Custom Elements Manifest)/ドキュメント自動
articleWeb Components が “is not a constructor” で落ちる時:定義順序と複数登録の衝突を解決
articleWeb Components vs Lit:素の実装とフレームワーク補助の DX/サイズ/速度を実測比較
articleZod 合成パターン早見表:`object/array/tuple/record/map/set/intersection` 実例集
articleバックアップ戦略の決定版:WordPress の世代管理/災害復旧の型
articleYarn 運用ベストプラクティス:lockfile 厳格化・frozen-lockfile・Bot 更新方針
articleWebSocket のペイロード比較:JSON・MessagePack・Protobuf の速度とコスト検証
articleWeb Components イベント設計チート:`CustomEvent`/`composed`/`bubbles` 実例集
articleWebRTC SDP 用語チートシート:m=・a=・bundle・rtcp-mux を 10 分で総復習
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来