---
title: Signals Deep Dive
tags: 
author: [Takuro Nishino](https://image.docswell.com/user/nishitaku)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/LJ1YDGNDEG.jpg?width=480
description: フロントエンド・PHPカンファレンス北海道
published: June 05, 26
canonical: https://image.docswell.com/s/nishitaku/53JE21-frontend_phpcon_do
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/LJ1YDGNDEG.jpg)

Signals Deep Dive
フロントエンド・PHPカンファレンス北海道 2026
nishitaku


# Page. 2

![Page Image](https://bcdn.docswell.com/page/GJWGYK4872.jpg)

初めまして
西濃 拓郎（にしの たくろう）
@nishitaku
フリーランス（10年目）
2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/4EZLXZ2973.jpg)

Signals 知ってる人〜
（Vue.jsのref、SvelteのRuneなど含む）
3


# Page. 4

![Page Image](https://bcdn.docswell.com/page/Y76W4ZVD7V.jpg)

これから話すこと
Signals がどういう仕組みで
どうやって必要な箇所だけを更新しているのか
持ち帰ってもらいたいこと
Signals の内部実装を理解して
リアクティブシステムの勘所を掴む
4


# Page. 5

![Page Image](https://bcdn.docswell.com/page/G75MQWD874.jpg)

目次
1. Signals 概要
2. Signals の特徴
特徴① 依存関係の自動追跡
特徴② Push-Pull ハイブリッド方式
特徴③ メモ化
3. Signal Polyfill の実装を読む
5


# Page. 6

![Page Image](https://bcdn.docswell.com/page/9J29PQ8VER.jpg)

Signals 概要


# Page. 7

![Page Image](https://bcdn.docswell.com/page/DEY45W6QJM.jpg)

Signals 概要
const counter = new Signal.State(0);
const isEven = new Signal.Computed(() =&gt; (counter.get() &amp; 1) == 0);
effect(() =&gt; { element.innerText = isEven.get() });
状態と依存関係を効率的に扱うリアクティブモデル
各種フレームワーク(Angular/Preact/Svelte/Vue ...etc)で採用
基本3要素
State: 手動で設定される値
Computed: Stateに依存して計算される値
Effect: StateやComputedに依存して実行されるコールバック
TC39 proposal-signals (Stage 1)
7


# Page. 8

![Page Image](https://bcdn.docswell.com/page/VJNYN9Z278.jpg)

Signals の特徴①
依存関係の自動追跡


# Page. 9

![Page Image](https://bcdn.docswell.com/page/YE9PR2ZDJ3.jpg)

Vanilla JSの場合
let counter = 0;
const setCounter = (value) =&gt; {
counter = value;
render();
};
const isEven = () =&gt; (counter &amp; 1) == 0;
const parity = () =&gt; isEven() ? &quot;even&quot; : &quot;odd&quot;;
const render = () =&gt; element.innerText = parity();
setInterval(() =&gt; setCounter(counter + 1), 1000);
開発者が依存関係を知っておく必要がある
→ 変更時の対応漏れや過剰更新が問題になり、スケールしない 9


# Page. 10

![Page Image](https://bcdn.docswell.com/page/GE8DW515ED.jpg)

Signals の場合
const counter = new Signal.State(0);
const isEven = new Signal.Computed(() =&gt; (counter.get() &amp; 1) == 0);
const parity = new Signal.Computed(() =&gt; isEven.get() ? &quot;even&quot; : &quot;odd&quot;);
effect(() =&gt; {
element.innerText = parity.get();
});
setInterval(() =&gt; counter.set(counter.get() + 1), 1000);
依存関係を自動で追跡してくれる
→ 依存関係が増えてもスケールしやすい
10


# Page. 11

![Page Image](https://bcdn.docswell.com/page/LELMNYQ37R.jpg)

宣言的UIの場合
// JavaScript
let name = &quot;太郎&quot;;
let age = 20;
const validatedName = () =&gt; f(name); // nameに依存
const parity = () =&gt; (age % 2 === 0 ? &#039;偶数&#039; : &#039;奇数&#039;); // ageに依存
// HTML
&lt;p&gt;名前は{{ validatedName() }}です&lt;/p&gt;
&lt;p&gt;年齢は{{ parity() }}です。&lt;/p&gt;
依存関係がわからないと、
だけでなく
parity
age
が変更されたときに
も再計算する必要がある
validatedName
11


# Page. 12

![Page Image](https://bcdn.docswell.com/page/4JMYXN1MJW.jpg)

Signals の場合
// JavaScript
const name = signal(&quot;太郎&quot;);
const age = signal(20);
const validatedName = computed(() =&gt; f(name()));
const parity = () =&gt; (age() % 2 === 0 ? &#039;偶数&#039; : &#039;奇数&#039;);
// HTML
&lt;p&gt;名前は{{ validatedName() }}です&lt;/p&gt;
&lt;p&gt;年齢は{{ parity() }}です。&lt;/p&gt;
効率的に必要な計算だけを再実行できる
12


# Page. 13

![Page Image](https://bcdn.docswell.com/page/PJR9NDLL79.jpg)

Signals を使うことで
自動で依存関係を追跡できる


# Page. 14

![Page Image](https://bcdn.docswell.com/page/PEXQN266JX.jpg)

Signals の特徴②
Push-Pull ハイブリッド方式


# Page. 15

![Page Image](https://bcdn.docswell.com/page/3EK9NM1GED.jpg)

データを誰が主導して渡すか
Push型：データを持っている側が通知する
例）Pub/Sub、WebSocket、Observerパターン、EventEmitter
常に最新状態
無駄な再計算が発生しやすい
Pull型：データを使う側が取りにいく
例）ポーリング、getter、関数呼び出し
必要なときに取得して計算
毎回取得コストが発生してしまう
15


# Page. 16

![Page Image](https://bcdn.docswell.com/page/L73WVYQ575.jpg)

Signals は Push-Pull ハイブリッド方式
const counter = new Signal.State(0);
const isEven = new Signal.Computed(() =&gt; (counter.get() &amp; 1) == 0);
Stateの変更は即座に通知されるPush型
Computedの評価はPull型
16


# Page. 17

![Page Image](https://bcdn.docswell.com/page/87DK859YJG.jpg)

Signals の特徴③
メモ化


# Page. 18

![Page Image](https://bcdn.docswell.com/page/VJPK8GZ2E8.jpg)

Computed のメモ化
const parity = () =&gt; (age() % 2 === 0 ? &#039;偶数&#039; : &#039;奇数&#039;);
依存先のdirtyフラグをキャッシュキーとしている
age
age
のdirtyフラグが
のdirtyフラグが
true
false
→ 再計算した値を返す
→ キャッシュ値を返す
18


# Page. 19

![Page Image](https://bcdn.docswell.com/page/2EVVN6WXEQ.jpg)

Signal Polyfill の実装を読む


# Page. 20

![Page Image](https://bcdn.docswell.com/page/57GLKNDREL.jpg)

Signal Polyfill とは
TC39 proposal-signals のポリフィル実装
import { Signal } from &quot;signal-polyfill&quot;;
import { effect } from &quot;./effect.js&quot;;
const counter = new Signal.State(0);
const isEven = new Signal.Computed(() =&gt; (counter.get() &amp; 1) == 0);
const parity = new Signal.Computed(() =&gt; (isEven.get() ? &quot;even&quot; : &quot;odd&quot;));
effect(() =&gt; console.log(parity.get())); // Console logs &quot;even&quot; immediately.
setInterval(() =&gt; counter.set(counter.get() + 1), 1000); // Changes the counter every 1000ms.
// effect triggers console log &quot;odd&quot;
// effect triggers console log &quot;even&quot;
// effect triggers console log &quot;odd&quot;
// ...
20


# Page. 21

![Page Image](https://bcdn.docswell.com/page/4EQYNQ1YJP.jpg)

依存関係の追跡を実現するための３ステップ
依存グラフの構築
読むことで依存を登録する
Push
「古くなった」を伝播する
Pull
必要になったときだけ再計算する
21


# Page. 22

![Page Image](https://bcdn.docswell.com/page/KJ4WGNRZ71.jpg)

const counter = new Signal.State(0);
依存グラフの構築
const parity = new Signal.Computed(() =&gt; (counter.get() % 2) == 0 ? &quot;even&quot; : &quot;odd&quot;);
effect(() =&gt; { element.innerText = parity.get() });
Computed(parity)
runtime
State(counter)
activeConsumer = parity
counter.get()
producerAccessed(counter)
subscribers.add(parity)
dependencies.add(counter)
activeConsumer = null
Computed(parity)
runtime
State(counter)
22


# Page. 23

![Page Image](https://bcdn.docswell.com/page/LE1YD5QD7G.jpg)

const counter = new Signal.State(0);
Pushフェーズ
effect
const parity = new Signal.Computed(() =&gt; (counter.get() % 2) == 0 ? &quot;even&quot; : &quot;odd&quot;);
effect(() =&gt; { element.innerText = parity.get() });
Computed(parity)
State(counter)
App
counter.set(1)
mark dirty
dirty = true
mark dirty
dirty = true
effect
Computed(parity)
State(counter)
App
23


# Page. 24

![Page Image](https://bcdn.docswell.com/page/GEWGY5M8J2.jpg)

const counter = new Signal.State(0);
Pullフェーズ
const parity = new Signal.Computed(() =&gt; (counter.get() % 2) == 0 ? &quot;even&quot; : &quot;odd&quot;);
effect(() =&gt; { element.innerText = parity.get() });
effect
Computed(parity)
parity.get()
alt
[dirty]
recompute
value
effect
Computed(parity)
24


# Page. 25

![Page Image](https://bcdn.docswell.com/page/47ZLXNQ9J3.jpg)

Signal Polyfill の実装
依存関係の追跡は、双方向の依存グラフによって実現
データを保持する側 は
データを取得する側 は
producer.subscribers
に
に
consumer.dependencies
consumer
を登録
を登録
producer
25


# Page. 26

![Page Image](https://bcdn.docswell.com/page/YJ6W486DJV.jpg)

Signals Deep Dive のまとめ
Signals は「誰が誰を読んだか」の依存グラフを構築する
依存グラフにより変更伝搬を自動化する
Push-Pull ハイブリッド方式により必要時だけ再計算する
26


# Page. 27

![Page Image](https://bcdn.docswell.com/page/GJ5MQ968J4.jpg)

ご清聴ありがとうございました
nishitaku


# Page. 28

![Page Image](https://bcdn.docswell.com/page/9E29P2MV7R.jpg)

Why Signals Deep Dive？
Angular と zone.js
v16 で 導入された Signals によって環境が激変
Signals の魔法に感動
28


# Page. 29

![Page Image](https://bcdn.docswell.com/page/D7Y45YXQEM.jpg)

Runtime Dependency Tracking
ビルド時ではなく、実行時に依存を解決している
const value = computed(() =&gt; {
if (flag()) {
return count()
}
return another()
})
29


# Page. 30

![Page Image](https://bcdn.docswell.com/page/VENYNP12J8.jpg)

グリッチフリー
count = 1
count = 2
count = 3
グリッチ：中間の不正確な値が表示されること
Push型リアクティブモデルの場合、変更が即座にUIに反映される
Signals はフレームワークが UI を描画するタイミングで必要な更新
だけを取りに行くため、グリッチが発生しずらい
「損失がある」ことの裏返しでもある。
30


# Page. 31

![Page Image](https://bcdn.docswell.com/page/Y79PR35DE3.jpg)

Signals のトレードオフ
「誰が誰を読むか」が実行時に決まる
更新の流れを追うのが難しいことがある
細かく分割しすぎると複雑になりやすい
Signals を使わない方がシンプルな場合もある
31


# Page. 32

![Page Image](https://bcdn.docswell.com/page/G78DWMY57D.jpg)

Signals は observer pattern？
広義のobserver pattern を含んでいる
しかし本質は dependency graph
Signalsは「読むことで依存登録」が重要
computed による派生状態を扱える
Push/Pull Hybrid によって lazy に再計算する
32


# Page. 33

![Page Image](https://bcdn.docswell.com/page/L7LMN343JR.jpg)

TC39 proposal-signals Stage 1
フレームワーク共通のリアクティブモデルを目指している
いくつかの課題をクリアしてから Stage 2 へ
複数の production-grade polyfill
複数 framework への統合検証
パフォーマンス検証
33


# Page. 34

![Page Image](https://bcdn.docswell.com/page/4EMYXDKMEW.jpg)

React vs Signals
設計思想が異なる
Async React の設計思想と Signal の違いを Transition を中心に考
える
React vs Signals: 10 Years Later
34


# Page. 35

![Page Image](https://bcdn.docswell.com/page/PER9N4XLJ9.jpg)

References
TC39 Signals
tc39/proposal-signals
signal-polyfill
A TC39 Proposal for Signals
35


