createTransformer
createTransformer
由 mobx-utils
包提供。
createTransformer<A, B>(transformation: (value: A) => B, onCleanup?: (result: B, value?: A) => void): (value: A) => B
createTransformer
将一个函数(此函数值 A
转换为值 B
)转换为响应式且有记忆功能的函数。 换句话说,如果给 transformation
函数一个具体的A
值,那么它将计算出B
值,只要保持A
值不变,那么今后任何时候的转换调用返回的B
值也是不变的。 但是,如果A
值改变了,将会重新应用转换,以便相应地更新B
值。 最后但同样重要的是,如果没有人在使用具体A
值的转换,则此条转换将从记忆表中移除。
使用 createTransformer
可以很容易的将一个完整数据图转换成另外一个数据图。 转换函数可以组合,这样你可以使用很多小的转换函数来构建一棵响应树。 生成的数据图会一直保持更新,它将通过对结果图应用小补丁来与源同步。 这使得它很容易实现强大的模式类似于 sideways data loading
、map-reduce、使用不可变的数据结构跟踪状态历史等等。
可选的 onCleanup
函数可用于在不再需要对象的转换时获取通知。 如果需要,这可以用于清理附加到结果对象上的资源。
永远在 @observer
或 autorun
这样的 reaction 中使用转换。 如同任何其他计算值一样,如果没有被某些东西观察的话,这些转换也将会回退到惰性求值,尽管有点违背使用它们的初衷。
这一切可能仍然有点模糊,因此这里通过用两个使用小的响应式函数将一个数据结构转换成另一个数据结构的示例来解释这转换的整体思路:
使用不可变的、同享的数据结构来追踪可变状态
本示例取自 Reactive2015 conference demo:
/*
store 保存了我们的领域对象: boxes 和 arrows
*/
const store = observable{
boxes: [],
arrows: [],
selection: null
}
/**
每次更改会把 store 序列化成 json 并将其添加到状态列表中
*/
const states = [];
autorun(() => {
states.push(serializeState(store)
}
const serializeState = createTransformer(store => {
boxes: store.boxes.map(serializeBox),
arrows: store.arrows.map(serializeArrow),
selection: store.selection ? store.selection.id : null
})
const serializeBox = createTransformer(box => {...box})
const serializeArrow = createTransformer(arrow => {
id: arrow.id,
to: arrow.to.id,
from: arrow.from.id
})
Copy
在本示例中,state 是通过组合三个不同的转换函数序列化过的。 autorunner 触发 store
对象的序列化,也就是依次将 boxes 和 arrows 序列化。 我们来仔细看下一个示例 box#3 假想的生命周期。
- 首先 box#3 是通过
map
传递给serializeBox
的,执行serializeBox
转换然后包含 box#3 的条目及其序列化表示会被添加到serializeBox
的内部记忆表中。
所以在这里我们使用不可变的、共享的数据结构有效地实现了状态追踪。 所有的 boxes 和 arrows 都映射并简化到一个状态树。 每次变化都会导致 states
数组中产生一个新条目,但不同的条目将分享几乎所有的 box 和 arrow 状态表现。
将数据图转换为响应式数据图
转换函数返回的是普通值,作为替代还可以返回 observable 对象。 这个可以用来把一个 observable 数据图转换成另外一个 observable 数据图,得到的又可以进行转换...你懂得。
这有个小示例,是对一个响应式文件探测器进行编码,它将在每次更改时进行更新。 以这种方式构建的数据图通常反应更快,并且与使用你自己的代码更新的衍生数据图相比,代码会更为直观,。参见一些示例的性能测试。
不同于前一个示例的是,transformFolder
在文件夹保持可见的情况下只会运行一次。DisplayFolder
对象追踪 Folder
对象本身相关的东西。
在下面的示例中, state
图的所有变化都会被自动处理。 一些示例:
- 改变文件夹的名称会更新对应的
path
属性和其所有后代的path
属性。
var m = require('mobx')
function Folder(parent, name) {
this.parent = parent;
m.extendObservable(this, {
name: name,
children: m.observable.shallow([]),
}
}
function DisplayFolder(folder, state) {
this.state = state;
this.folder = folder;
m.extendObservable(this, {
collapsed: false,
get name() {
return this.folder.name;
},
get isVisible() {
return !this.state.filter || this.name.indexOf(this.state.filter) !== -1 || this.children.some(child => child.isVisible
},
get children() {
if (this.collapsed)
return [];
return this.folder.children.map(transformFolder).filter(function(child) {
return child.isVisible;
})
},
get path() {
return this.folder.parent === null ? this.name : transformFolder(this.folder.parent).path + "/" + this.name;
})
}
}
var state = m.observable{
root: new Folder(null, "root"),
filter: null,
displayRoot: null
}
var transformFolder = m.createTransformer(function (folder) {
return new DisplayFolder(folder, state
}
// 返回每个文件夹的字符串列表
var stringTransformer = m.createTransformer(function (displayFolder) {
var path = displayFolder.path;
return path + "\n" +
displayFolder.children.filter(function(child) {
return child.isVisible;
}).map(stringTransformer).join(''
}
function createFolders(parent, recursion) {
if (recursion === 0)
return;
for (var i = 0; i < 3; i++) {
var folder = new Folder(parent, i + ''
parent.children.push(folder
createFolders(folder, recursion - 1
}
}
createFolders(state.root, 2 // 3^2
m.autorun(function() {
state.displayRoot = transformFolder(state.root
state.text = stringTransformer(state.displayRoot)
console.log(state.text)
}
state.root.name = 'wow'; // 改变文件夹名称
state.displayRoot.children[1].collapsed = true; // 折叠文件夹
state.filter = "2"; // 搜索
state.filter = null; // 取消搜索