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

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

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


# Page. 2

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

はじめまして、nishitakuです
西濃 拓郎（にしの たくろう）
フリーランスエンジニア
2


# Page. 3

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

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


# Page. 4

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

イントロダクション
Deep Dive の価値
想定外の挙動にであったとき、「なぜそうなるか」を理解できる
これから話すこと
Signals がどういう仕組みで
どうやって必要な箇所だけを更新しているのか
持ち帰ってもらえること
現代のリアクティブシステムの勘所を掴む
4


# Page. 5

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

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


# Page. 6

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

Signals 概要


# Page. 7

![Page Image](https://bcdn.docswell.com/page/3JK9N8ZNJD.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/LE3WV2DZE5.jpg)

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


# Page. 9

![Page Image](https://bcdn.docswell.com/page/8EDK8ZP47G.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/V7PK8DNVJ8.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/2JVVN1KRJQ.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/5EGLK2Q6JL.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/4JQYNPK27P.jpg)

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


# Page. 14

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

Signals の特徴②
Push-Pull ハイブリッド方式による遅延評価


# Page. 15

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

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


# Page. 16

![Page Image](https://bcdn.docswell.com/page/GJWGYWQK72.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/4EZLX5WN73.jpg)

Signals の特徴③
メモ化


# Page. 18

![Page Image](https://bcdn.docswell.com/page/Y76W49G97V.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/G75MQNVD74.jpg)

Signal Polyfill の実装を読む


# Page. 20

![Page Image](https://bcdn.docswell.com/page/9J29P5XMER.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/DEY45K8PJM.jpg)

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


# Page. 22

![Page Image](https://bcdn.docswell.com/page/VJNYNRQM78.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/YE9PRMDWJ3.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/GE8DWL3RED.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/LELMNLV27R.jpg)

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


# Page. 26

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

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


# Page. 27

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

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


# Page. 28

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

28


# Page. 29

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

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


