lua实现数据响应式思路分享及代码分析
大家都知道在前端vue框架中,我们只需要修改数据,框架就能够自动的帮我们把数据渲染到页面,他实现了数据与页面的解耦,开发者只需要绑定数据 关注业务逻辑
在研究了vue的部分源码后,有这样一个想法,这种思想和做法能不能用到其他的地方?我们能不能在lua中做到数据响应式呢?什么是数据响应式?
数据响应式
本质是当数据变化时会自动运行一些相关函数,这个只是比较简单的说法
问题
光看这本质描述很多人认为这不是很简单么,无非就是监听数据变化呗,在Js中我们可以用Object.defineProperty做jget、set监听,在lua里面我们可以用原表来完成,但问题是如果我们要写一个自己的通用框架还有很多问题要处理,譬如这个get和set的函数我想交给框架去处理,不然每次使用我都要去手写get、set这样非常麻烦,其次就是这个get和set的监听他只能监听到数值的变化、但是框架并不知道是谁在调用、谁在使用,但是为了开发方便 我们需要在get的时候收集记录是哪个函数在用我、在set的时候去执行用我的函数,完成一个响应式的数据变动
其实这个也不是非常新鲜的事情,对于一个对象而言,在vue框架中已经提供了两个专业的名词来描述这种行为,
依赖收集:记录是哪个函数在用我
派发更新:执行用我的函数那让我们在lua里面实现看看,代码如下
-- 我们用面向对象的思想来封装 响应式数据工具
--定义全局监听类
_G.ObserveClass = {}
--定义全局监听服务类
_G.ObserveServiceClass={}
--定义全局函数调用 --临时调用
_G.__func=null
--实例化监听服务类
function ObserveServiceClass:new()
--实例化监听类
data,addListener = ObserveClass:new()
--监听服务类内部成员变量
local obj={
_data= data,--数据
_function= {},
_property=addListener
}
-- 返回设置了内部变量的原表对象 其中{__index = self}
--代表如果在obj中无法找到某个属性或方法时,Lua将会在关联的表self中查找
return setmetatable(obj, {__index = self})
end
--定义监听服务类的getDate方法 当前方法用于获取设置监听后的table
function ObserveServiceClass:getDate()
--获取 监听服务类 的内部成员变量
return self._data
end
--定义监听服务类的addListener方法 提供一个key 这个方法是让我们可以手动定义get和set的钩子
--在get的时候执行xxx set的时候执行xxx key是table的key值
function ObserveServiceClass:addListener(key)
--_function为当前监听服务类的 监听到的函数 也就是收集到的函数
--如果不存在就创建一个 他是一个二维数组的结构 (两层table)
if not self._function[key] then
self._function[key]={}
end
--依赖收集:记录是哪个函数在用我
function records(arr)
--判断全局函数变量是否有值 并且 arr数组是否存在该函数 如果不存在就写入
if(_G.__func)then
table.insert(arr,_G.__func)
end
end
--派发更新:执行用我的函数
function applyFun(arr)
for i = 1, #arr do
arr[i]();
end
end
--钩子 这个函数是用于传递get的执行事件
-- 获取属性时的回调函数
function get(value)
records(self._function[key])
--print('SET ---- >>> ',value)
end
--钩子 这个函数是用于传递set的执行时间
-- 设置属性时的回调函数
function set(value)
applyFun(self._function[key])
--print('GET ---- >>> ' ,value)
end
--设置回调GET、SET 这个是在监听类里面用的
self._property(key,"set",set)
self._property(key,"get",get)
return true
end
--监听服务类 向外暴露一个自动执行函数 这个函数的意义是
--我们一个包含有受监听的数据的函数 他会在调用之前去拿到调用该变量的fun
--这个fun被挂载到全局 执行完毕后销毁,依靠这种方法就可以在get的时候拿到用我们的函数
function ObserveServiceClass:autoRun(fn)
_G.__func=fn; --记录函数
fn();
_G.__func=null; --最后再将全局变量设置为null
end
--实例化监听类
function ObserveClass:new()
-- 存储表的数据 自动帮我们创建一个table这个table是会返回给外部用的
local data = {}
-- 存储监听的回调函数
local observers = {}
--设置元表内容 设置STE、GET钩子
local mt = {
__index = function(_, key)
Get(key) -- 读取属性时触发相应的观察者回调
return data[key]
end,
__newindex = function(_, key, value)
local oldValue = data[key]
data[key] = value
Set(key, value, oldValue) -- 设置属性时触发相应的观察者回调
end
}
--设置监听对象 这个函数也会返回到外部进行包装 他接受收table的key
-- type 字符串 get、set
-- callback get、set的回调函数
function addObserver(key,type, callback)
if not observers[key] then
observers[key] = {}
end
if not observers[key][type] then
observers[key][type] = {}
end
-- 添加观察者回调函数到对应属性的观察者列表中
table.insert(observers[key][type], callback)
end
function Get(key)
local callbacks = observers[key]["get"]
if callbacks then
for _, callback in ipairs(callbacks) do
-- 触发观察者回调函数,并传递属性值作为参数
callback(data[key])
end
end
end
function Set(key, value, oldValue)
local callbacks = observers[key]["set"]
if callbacks then
for _, callback in ipairs(callbacks) do
-- 触发观察者回调函数,并传递新值和旧值作为参数
callback(value, oldValue)
end
end
end
-- 返回一个设置了元表的空表以及添加观察者的方法
return setmetatable({}, mt), addObserver
end
--使用
--实例化一个观察服务类
observeService = ObserveServiceClass:new()
--服务类里面的data数据table
newData=observeService:getDate()
--设置一个监听的key 当我们去get或 set这个key的时候就会触发事件
observeService:addListener("title")
--定义 当key被修改后需要执行的函数 这个函数会自动执行
function autoFun()
print(newData.title)
end
--挂在这个自动的函数
observeService:autoRun(autoFun)
--当我们去修改这个title的时候就会自动去执行autoFun
newData.title="测试内容....数据响应式"
最后
这个lua的数据响应式只是简单的实现,还存在一些问题,借助这种思路我们可以将其完善
@Author 壹影
WilliambrAsp
2023-12-24 09:59水微晶玻尿酸 - 八千代
https://yachiyo.com.tw/hyadermissmile-injection/