仿写 vue - 实现数据拦截和简单的依赖收集

简单仿写 vue

step1 拦截赋值和取值操作

拦截数据访问和获取关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function newProxy(model)
local t = {}
local raw = {}
if type(model) ~= "table" then
return model
end
for k, v in pairs(model) do
raw[k] = newProxy(v)
end
setmetatable(t, {
__index = function(t, key)
print("get key " .. key)
return raw[key]
end,
__newindex = function(t, key, value)
local oldValue = raw[key]
print("set key:" .. key)
raw[key] = newProxy(value)
end,
__len = function()
return #raw
end
})
return t
end

model = {
test1 = "test1",
test2 = "test2"
}
model = newProxy(model)
print(model.test1)
model.test2 = "test2_modify"
model.test3 = "test3"
model.test4 = {
test5 = "test5"
}
model.test4.test5 = "test6"
model[#model + 1] = "test4"
model[#model + 1] = 4
table.insert(model, 5)

重写__index__newindex实现对 raw 数据的获取和赋值拦截,重写__len方法实现数组赋值和table.insert的获取,并且实现深度proxy;

step2 构建依赖关系

基于dep实现依赖关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
local Dep = {}

local TrackedMarkers = {
wasTracked = 0,
newTracked = 1,
}


local createTrack = function(effects)
local dep = new
end

Dep.__index = Dep

function Dep.Dep()
local t = setmetatable({}, Dep)
t.listeners = {}
return t
end

function Dep:addWatcher(watcher)
table.insert(self.listeners, watcher)
end

function Dep:removeWatcher(watcher)
local index = nil
for k,v in pairs(self.listeners) do
if v == watcher then
index = v
break
end
end
table.remove(table, index)
end

function Dep:notify(newValue, oldValue)
for _, v in ipairs(self.listeners) do
v(newValue, oldValue)
end
end
Dep.Target = nil

return Dep

对于每一个 proxy 获取一个新的proxydep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
local Dep = require("Dep")
-- 测试代码
function dumpTable(t)
local res = ""
if t == nil then
return "nil"
end
if type(t) == "table" then
for k, v in pairs(t) do
res = res.."\n"..k..":"..dumpTable(v)
end
else
res = res..tostring(t)
end
return res
end

function newProxy(model)
local t = {}
local raw = {}
-- 捕获的依赖关系
local dep = Dep.Dep()
if type(model) ~= "table" then
return model
end
for k, v in pairs(model) do
raw[k] = newProxy(v)
end
setmetatable(t, {
__index = function (t, key)
-- watch
if Dep.Target then
print("add watcher "..key)
dep:addWatcher(Dep.Target)
end
return raw[key]
end,
__newindex = function (t, key, value)
local oldValue = raw[key]
dep:notify(value, oldValue)
print("key:"..key)
print("value:"..dumpTable(value))
print("oldValue:"..dumpTable(oldValue))
raw[key] = newProxy(value)
end
})
return t
end

local function doWatch(effect, callback)
Dep.Target = callback
effect()
Dep.Target = nil
end

step3 测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
local model = {
test1 = "test1",
test2 = "test2",
test3 = {
test4 = "test3.test4"
}
}

local modela = {
test1 = "test1",
test2 = "test2",
test3 = {
test4 = "test4"
}
}


model = newProxy(model)

modela = newProxy(modela)

local t = nil
doWatch(function()
model.
t = model.test3.test4
modela.test4 = model.test3
end,
function ()
print("do watch when deep value change")
end)


model.test3.test4 = "change test4"

model.test3 = "change test3"

doWatch(function() print("Do watch function ")
local test = model[1]end
, function()
print("model callback") end)

model[2] = 1
model[1] = 2
table.insert(model, 4)

for k, v in pairs(model) do
print(k)
end

TODO:还需要做的地方

目前需要处理的地方主要有几点:

1、这种简单的dep:notify的方式暂时无法解决自增的问题,需要记录当前回调避免递归爆栈;

2、现在还没能实现数组部分的传递,暂时只能实现 hash 部分的操作拦截;

3、pairsipairs操作还是在 proxy 上操作,后续需要尽量让 proxy 的操作和 model 的操作尽可能一致;

4、dep的回调可能会出现多次调用,所以最好实现Set