关于 migrating-motoko
migrating-motoko 是用于 Motoko Actor 升级时迁移状态的技能,面向需要调整持久化字段或数据结构的开发者。它通过附加到 Actor 的迁移表达式处理显式转换,并说明可兼容变更的隐式迁移情形。
主要功能
- 01区分隐式与显式迁移需求
- 02通过迁移函数转换 Actor 状态
- 03提供字段重命名与类型转换模式
- 04说明迁移函数的输入输出规则
- 05列出构建与检查步骤
适用场景
- 01升级时重命名持久化字段
- 02变更字段类型或状态结构
- 03转换集合中的数据
- 04为新增字段设置默认值
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dfinity/ icskills --skill migrating-motoko安装后在 Claude Code 中输入/migrating-motoko确认已加载。
详细介绍
内联 Actor 迁移
通过附加到 Actor 的迁移表达式,在 canister 升级时迁移 Actor 状态。每次升级最多只能有一个迁移函数。
如需使用 migrations/ 目录进行多次迁移,请改为加载 migrating-motoko-enhanced。
何时使用
隐式迁移(无需编写代码)
新程序与旧程序兼容时,运行时允许升级:
- 添加 Actor 字段
- 移除 Actor 字段
- 更改可变性(
var↔let) - 添加变体构造器
- 扩宽类型(
Nat→Int)
需要显式迁移
- 重命名字段
- 更改字段类型(例如,
Bool→ 变体、Int→Float) - 重构状态(拆分/合并字段)
- 转换集合值
语法
紧邻 Actor 之前的括号表达式:
import Migration "migration";
(with migration = Migration.run)
actor {
var newState : Float = 0.0;
};
或内联:
import Int "mo:core/Int";
(with migration = func(old : { var state : Int }) : { var newState : Float } {
{ var newState = old.state.toFloat() }
})
actor {
var newState : Float = 0.0;
};
或者,当导入的模块导出 migration 字段时,使用简写形式:
import { migration } "migration";
(with migration)
actor { ... };
迁移函数规则
- 类型:
func (old : { ... }) : { ... }— 必须是本地、非泛型函数,且两个记录都必须使用可持久化类型(不能包含函数或可变数组) - 定义域:旧 Actor 字段(上一版本中的名称和类型)
- 陪域:新 Actor 字段(必须存在于新 Actor 中,且类型兼容)
- 仅在升级时运行——全新安装时,初始化器会正常运行
- 如果迁移触发陷阱,升级会中止,canister 保留旧版本
字段语义
| 字段出现位置 | 效果 |
|---|---|
| 输入和输出 | 转换该字段 |
| 仅输出 | 由迁移生成新字段 |
| 仅输入 | 使用该字段(编译器会警告可能的数据丢失) |
| 两者都不 | 保留该字段或通过声明初始化 |
迁移模块模式
将迁移放在单独的模块中。直接内联定义旧类型——不要从旧代码路径导入:
// migration.mo
import Types "types";
import Map "mo:core/Map";
module {
type OldTask = { id : Nat; title : Text; completed : Bool };
type OldActor = {
var tasks : Map.Map<Nat, OldTask>;
var nextId : Nat;
};
type NewActor = {
var tasks : Map.Map<Nat, Types.Task>;
var nextId : Nat;
};
public func run(old : OldActor) : NewActor {
let tasks = old.tasks.map<Nat, OldTask, Types.Task>(
func(_, task) {
{
id = task.id;
title = task.title;
due = 0;
var status = if (task.completed) #completed else #pending;
}
}
);
{ var tasks; var nextId = old.nextId };
};
};
// main.mo
import Map "mo:core/Map";
import Types "types";
import Migration "migration";
(with migration = Migration.run)
actor {
var tasks = Map.empty<Nat, Types.Task>();
var nextId : Nat = 0;
};
字段必须有初始化器——迁移函数仅在升级时运行。全新安装时会使用初始化器。
常见模式
添加带默认值的字段
old.users.map<Nat, OldUser, NewUser>(
func(_, u) { { u with zipCode = "" } }
)
添加可选字段
{ task with var assignee = null : ?Principal }
将 Bool 转为变体
var status = if (task.completed) #completed else #pending;
重命名字段
使用旧名称,并生成新名称:
func(old : { var state : Int }) : { var value : Int } {
{ var value = old.state }
}
删除字段
在输入中使用该字段,在输出中省略。编译器会发出警告——请确认这是有意的数据丢失。
检查清单
- 决定使用隐式迁移(兼容性变更)还是显式迁移(迁移函数)
- 如果使用显式迁移:在
migration.mo中内联定义旧类型 - 迁移类型:
func (old : RecordIn) : RecordOut,且使用可持久化类型 - 在 Actor 之前附加
(with migration = Migration.run) - 不要使用
preupgrade/postupgrade迁移数据 - 使用
mops check --fix和mops build验证
更多参考
- 加载
motoko,查看 Motoko 语言通用参考和 mo:core API - 加载
migrating-motoko-enhanced,使用--enhanced-migration进行多次迁移 - 加载
mops-cli,了解mops check、mops build和工具链设置
评论
支持 Markdown