lua实现类vue响应式和依赖收集

375 阅读2分钟

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

lua简单仿写vue:step1 粗略实现操作拦截和响应

step1 拦截赋值和取值操作

拦截数据访问和获取关系

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实现依赖关系

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
endfunction Dep:addWatcher(watcher)
    table.insert(self.listeners, watcher)
endfunction 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)
endfunction Dep:notify(newValue, oldValue)
    for _, v in ipairs(self.listeners) do
        v(newValue, oldValue)
    end
end
Dep.Target = nilreturn Dep

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

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
endfunction 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
endlocal function doWatch(effect, callback)
    Dep.Target = callback
    effect()
    Dep.Target = nil
end

step3 测试代码

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;