Shared state (namespace)

默认每个 ```interact 块独立持有自己的 state——拖一个块的滑块不影响别的块。 用 namespace <name> 让多个 interact 块共享同一组变量——一次声明 sliders / computed,多块复用。


When to use

  • ✅ 同一组 sliders 驱动多块输出(例:3 个滑块控本金/利率/年限,文本块 + chart 块都用)
  • ✅ 正文穿插的多 chart(写一段、放一个 chart、写一段、再一个 chart——全用同一组参数)
  • ✅ Layout / Tabs 内多 cards 共享数据(左卡 sliders,右卡 chart)
  • ✅ 避免重复声明(5 个 interact 块都要同一个 slider,写 5 次维护噩梦)
  • ❌ 真隔离的两个独立计算器(两个 BMI 计算器互不干扰,不要同 namespace)
  • ❌ 用作"全局变量库"(namespace 应该聚焦单一上下文,别放进所有变量)

Basic syntax

加 namespace <name> 在 ```interact 后:

\```interact namespace finance
slider rate 0 0.15 0.05 0.01
slider years 1 30 10 1
\```

正文段落...

\```interact namespace finance
template stl:
[利率] -> [{(rate*100):.1f}%]
[年限] -> [{years} 年]
\```

渲染:第 1 块出现 2 个 sliders;第 2 块没有 sliders(它只复用 namespace 里的),只有 template 输出。拖 1 块 slider → 2 块的输出实时更新。


namespace 命名规则

  • ASCII 字母 + 数字 + 下划线
  • 大小写敏感(finance ≠ Finance)
  • 推荐用语义名(finance / physics / bmi / chart_demo)
  • 跨页面不共享——namespace 只在单页有效(关 page state 就丢)

Examples

Example 1:参数 + 输出 + chart 三块共用

**复利计算器** —— 拖滑块看变化。

\```interact namespace finance
slider principal 1000 100000 10000 1000
slider rate 0 0.15 0.05 0.01
slider years 1 30 10 1
computed compound = principal * pow(1+rate, years)
computed gain = compound - principal
\```

数字结果:

\```interact namespace finance
template stl:
[本金] -> [${principal:.0f}]
[终值] -> [${compound:.0f}]
[净收益] -> [${gain:.0f}]
\```

增长曲线:

\```interact namespace finance
template vega-lite:
{
  "data": {"sequence": {"start": 0, "stop": {years}, "step": 1, "as": "year"}},
  "transform": [{"calculate": "{principal} * pow(1+{rate}, datum.year)", "as": "value"}],
  "mark": "line",
  "encoding": {"x": {"field": "year"}, "y": {"field": "value", "type": "quantitative"}}
}
\```

3 个块共用 finance namespace。第 1 块定义 sliders + computed;第 2/3 块只是 template,复用所有变量。拖第 1 块 slider 同步驱动第 2/3 块。

Example 2:Layout + namespace 配合

\```layout grid cols=2

:::card accent=blue
**输入**

\```interact namespace bmi
slider weight 40 120 70 1
slider height 1.4 2.1 1.7 0.01
computed bmi_value = weight / (height * height)
template stl:
[体重] -> [{weight} kg]
[身高] -> [{height:.2f} m]
\```
:::

:::card accent=green
**结果**

\```interact namespace bmi
template stl:
[BMI] -> [{bmi_value:.1f}]
[分类] -> [{bmi_value < 18.5 ? "偏瘦" : bmi_value < 24 ? "正常" : "超重"}]
\```
:::
\```

Layout 提供视觉布局,namespace 提供 state 共享——两者正交合作。

Example 3:跨章节共用一组参数

## 财务模拟

我们看一个简单的退休规划。先设定参数:

\```interact namespace retire
slider currentAge 20 60 30 1
slider retireAge 50 75 65 1
slider monthly 100 5000 1000 100
slider annualRate 0 0.1 0.05 0.005
computed years = retireAge - currentAge
computed total = monthly * 12 * years * pow(1 + annualRate/12, 12*years)
\```

### 数字结果

\```interact namespace retire
template stl:
[当前年龄] -> [{currentAge}]
[退休年龄] -> [{retireAge}]
[投资期] -> [{years} 年]
[退休时本金] -> [${total:.0f}]
\```

### 资金成长

\```interact namespace retire
template vega-lite: {...}
\```

### 关键洞察

可以看到,把 `monthly` 调高 ¥100,退休本金涨 ${...}(实时)。
**早开始** + **持续投** > 短期内多投。

整个章节 4 个交互块共用同一 namespace,读者拖一处 slider 整章数字 + chart + 推论全部更新。

Example 4:多个独立 namespace(隔离)

**比较两个投资方案** —— A 和 B 互不干扰。

A 方案:

\```interact namespace planA
slider rate 0 0.15 0.05 0.01
slider years 1 30 10 1
template stl:
[A 终值] -> [${1000 * pow(1+rate, years):.0f}]
\```

B 方案:

\```interact namespace planB
slider rate 0 0.15 0.08 0.01
slider years 1 30 15 1
template stl:
[B 终值] -> [${1000 * pow(1+rate, years):.0f}]
\```

读者可以同时看到两个方案在不同参数下的结果。

两个 namespace 隔离——A 块的 rate 跟 B 块的 rate 完全独立。


Plain-text fallback behavior

namespace 在不支持 reader 里显示为代码块字面量——读者看到 \```interact namespace finance,理解原文档想跨块共享。

\```interact namespace finance
slider rate 0 0.15 0.05 0.01
\```
Reader 渲染效果
Rho 完整跨块 state 共享
GitHub web 代码块(namespace 字面量可见)
Obsidian 同上
VS Code 默认预览 同上
cat / less plain text

namespace 名称在源文件里自文档化——读者看到 namespace finance 就知道相关块属于同一 context。


Common pitfalls

1. 忘加 namespace → state 隔离

\```interact
slider rate 0 0.15 0.05 0.01
\```

\```interact
template stl: [利率] -> [{rate}]    ← ❌ 第 2 块的 rate 未声明
\```

→ 第 2 块抛 "undefined rate" 或显示 {rate} 字面量。两块都加同一 namespace 才能共享:

\```interact namespace foo
slider rate 0 0.15 0.05 0.01
\```

\```interact namespace foo
template stl: [利率] -> [{rate}]    ← ✅
\```

2. namespace 大小写不一致

\```interact namespace Finance
slider rate ...
\```

\```interact namespace finance     ← ❌ 大小写不同 → 不共享
template ...
\```

→ namespace 大小写敏感。Finance ≠ finance。

3. 同 namespace 重复声明同变量

\```interact namespace foo
slider rate 0 0.15 0.05 0.01
\```

\```interact namespace foo
slider rate 0 0.20 0.10 0.05      ← ❌ 重复声明 rate
\```

→ 第 2 次声明行为未定义(可能覆盖、可能报错、可能保留第一次)。namespace 内每个变量只声明一次——后续块只引用,不重声明。

4. namespace 滥用 → 全部全局

\```interact namespace shared
slider a 0 10 5 1
slider b 0 10 5 1
slider c 0 10 5 1
... (40 个 sliders)
\```

→ 单 namespace 装太多 → 读者无法理解哪块跟哪块相关。一个 namespace 应聚焦单一上下文(一个计算器 / 一个章节的参数面板)。多 context 用多 namespace。

5. 跨页面期待持久化

<!-- 在 page A: -->
\```interact namespace foo
slider x 0 10 5 1
\```
<!-- 在 page B: -->
\```interact namespace foo
template stl: [{x}]      ← ❌ x 未在 B 声明,namespace 不跨页
\```

→ namespace 只在单页有效——跳到 B page 时,A 的 foo namespace 没了。每页 namespace 独立。

6. namespace 跟外部 JS 混用

\```interact namespace foo
slider rate 0 0.15 0.05 0.01
\```

<script>
  // 想从 JS 读 foo.rate
</script>

→ namespace state 不暴露给外部 JS——它在 Rho hydration 内部管理。Rho 不是 JS framework——要 JS 集成走 Developer Reference 的 hydration API。

7. 嵌套不能突破 namespace 隔离

\```layout
:::card
\```interact namespace foo
slider x 0 10 5 1
\```
:::

:::card
\```interact namespace bar
template stl: [{x}]      ← ❌ namespace 不同,看不到 foo 的 x
\```
:::
\```

→ 嵌套不影响 namespace 规则——namespace 是隔离条件,跟嵌套无关。要跨 card 共享,两个 interact 块同 namespace。


Best practices

1. namespace 名带语义

✅ finance / bmi_calc / physics_demo / retire_planning ❌ ns1 / tmp / data / state

2. 第一块声明,后续块只引用

\```interact namespace foo
slider a 0 10 5 1
slider b 0 10 5 1
computed sum = a + b
\```

\```interact namespace foo
template stl: [{sum}]     ← 只 template,不重新声明
\```

3. computed 在第一块定义

把 computed 放跟 sliders 同一块(最早)—— 后续块直接引用 computed name,不重复表达式:

\```interact namespace bmi
slider weight 40 120 70 1
slider height 1.4 2.1 1.7 0.01
computed bmi_value = weight / (height * height)
computed category = bmi_value < 18.5 ? "thin" : ...
\```

\```interact namespace bmi
template stl: [{bmi_value:.1f}] [{category}]
\```

4. 控制 namespace 范围

一个 namespace 覆盖一个章节 / 一个 demo——不要让一个 namespace 横跨整个文档。多章节用多 namespace。


See also