html js 响应式编程,函数式响应式编程 - Functional Reactive Programming

本文通过实例解析如何使用函数式响应式编程解决HTML表单中复杂交互问题,如密码验证的实时同步与延迟处理。借助rxjs库,代码逻辑更清晰,减少冗余标识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们略过概念,直接看函数式响应式编程解决了什么问题。

故事从下面这个例子展开:

两个密码输入框,一个提交按钮。

61104b83b0ba921971bd13c44b93a0c0.png

密码、确认密码都填写并一致,允许提交;不一致提示错误。

html 如下:

提交

常规做法

初始版

const validate = () => {

const match = pwd.value === confirmpwd.value;

const cansubmit = pwd.value && match;

errorlabel.innertext = match ? "" : "密码不一致";

if (cansubmit) {

submitbtn.removeattribute("disabled");

} else {

submitbtn.setattribute("disabled", true);

}

};

pwd.addeventlistener("input", validate);

confirmpwd.addeventlistener("input", validate);

加强版

问题: 输入密码时,确认密码还是空的,出现密码不一致错误提示,干扰用户输入。

期望: 确认密码没输入过时,不提示错误。

为解决这个问题,用 isconfirmpwdtouched 标识确认密码输入框是否输入过内容。

let isconfirmpwdtouched = false;

pwd.addeventlistener("input", () => {

if (isconfirmpwdtouched) validate();

});

confirmpwd.addeventlistener("input", () => {

isconfirmpwdtouched = true;

validate();

});

测试同学又发现了一个 bug:

不输密码,直接输入确认密码,这时又出现了错误提示。

为解决这个问题,再加入一个标识位 ispwdtouched。

let isconfirmpwdtouched = false;

let ispwdtouched = false;

pwd.addeventlistener("input", () => {

ispwdtouched = true;

if (ispwdtouched && isconfirmpwdtouched) validate();

});

confirmpwd.addeventlistener("input", () => {

isconfirmpwdtouched = true;

if (ispwdtouched && isconfirmpwdtouched) validate();

});

旗舰版

问题: 确认密码输入框输入第一个字符时就会提示密码不一致,干扰用户输入。

期望: 连续输入时,不提示错误。

为解决这个问题,高级一点的做法是使用高阶函数 debounce,否则又要多个标识位。

const debounce = (fn, ms) => {

let timeoutid;

return (...args) => {

if (timeoutid !== undefined) cleartimeout(timeoutid);

timeoutid = settimeout(fn.bind(null, ...args), ms);

};

};

const validate = () => {

const match = pwd.value === confirmpwd.value;

const cansubmit = pwd.value && match;

errorlabel.innertext = match ? "" : "密码不一致";

if (cansubmit) {

submitbtn.removeattribute("disabled");

} else {

submitbtn.setattribute("disabled", true);

}

};

const debouncedvalidate = debounce(validate, 200);

let isconfirmpwdtouched = false;

let ispwdtouched = false;

pwd.addeventlistener("input", () => {

ispwdtouched = true;

if (ispwdtouched && isconfirmpwdtouched) debouncedvalidate();

});

confirmpwd.addeventlistener("input", () => {

isconfirmpwdtouched = true;

if (ispwdtouched && isconfirmpwdtouched) debouncedvalidate();

});

常规做法的问题

可以看出:随着交互越来越复杂,常规做法的标识位越来越多,代码的逻辑越来越难理清。

常规做法实际实现了下图的逻辑:

f386d15757e147ab038d1940d46d1cbe.png

图看起来清晰易懂,但可惜的是 代码和这张图长得并不像。

有没有一种办法,让我们的代码和上图一样逻辑清晰呢?

答案就是:函数式响应式编程。

用它写代码就像是在画上面那张图。

函数式响应式做法

这里使用的库是rxjs。

const { fromevent, combinelatest } = rxjs;

const { map, debouncetime } = rxjs.operators;

const pwd$ = fromevent(pwd, "input").pipe(map(e => e.target.value));

const confirmpwd$ = fromevent(confirmpwd, "input").pipe(

map(e => e.target.value)

);

combinelatest(pwd$, confirmpwd$)

.pipe(

debouncetime(200),

map(([pwd, confirmpwd]) => ({

match: pwd === confirmpwd,

cansubmit: pwd && pwd === confirmpwd

}))

)

.subscribe(({ match, cansubmit }) => {

errorlabel.innertext = match ? "" : "密码不一致";

if (cansubmit) {

submitbtn.removeattribute("disabled");

} else {

submitbtn.setattribute("disabled", true);

}

});

没看出代码和上面那张图有什么相似?我们来拆解一下。

const pwd$ = fromevent(pwd, "input").pipe(map(e => e.target.value));

const confirmpwd$ = fromevent(confirmpwd, "input").pipe(

map(e => e.target.value)

);

38f5353eba21d09bd5b51d1ecedca89d.png

我们把 pwd$, confirmpwd$ 称作流,可以把它们想象成河流,里面流淌着数据。

map 把流中的 input event 转换为输入框的 value。

combinelatest(pwd$, confirmpwd$);

1bdb714db29047b261331794b0a6a216.png

combinlatest 的作用在这里有两个。

combine:把 pwd$, confirmpwd$ 合成一个新流

latest:新流中的数据为 pwd$, confirmpwd$ 最新的数据的组合

pwd$ 产生数据 a 时,confirmpwd$ 还没产生过数据,新流不产生数据;

pwd$ 产生数据 ab 时,confirmpwd$ 还没产生过数据,新流不产生数据;

confirmpwd$ 产生数据 a 时,

由于 pwd$, confirmpwd$ 都产生过数据了,pwd$ 流最新产生的数据为 ab,

新流产生数据 [ab, a];

confirmpwd$ 产生数据 ab 时,

由于 pwd$, confirmpwd$ 都产生过数据了,pwd$ 流最新产生的数据为 ab,

新流产生数据 [ab, ab]。

combinelatest(pwd$, confirmpwd$).pipe(

debouncetime(200),

map(([pwd, confirmpwd]) => ({

match: pwd === confirmpwd,

cansubmit: pwd && pwd === confirmpwd

}))

);

6ca4ae0a62d4dfdb3e3fa1b4e4942725.png

debouncetime(200) 的作用和普通做法里的 debounce 功效一样。

上游流产生 [ab, a] 时,新流不立刻把数据传给下游,而是要延迟 200ms。

200ms 不到,上游流又传来数据 [ab, ab],新流丢弃之前的数据。

200ms 后,上游流没有传来新数据,新流将 [ab, ab] 传给下游。

map 将 [ab, ab] 转化为 { match: true, cansubmit: true }。

再比较一下,是不是很像呢?

f386d15757e147ab038d1940d46d1cbe.png

6ca4ae0a62d4dfdb3e3fa1b4e4942725.png

总结

函数式响应式编程创造的初衷就是解决 listener callback 逻辑表达不直观,代码乱成一团麻 的问题。

至于它为什么叫函数式响应式编程,是因为它的实现借鉴了函数式、响应式编程思想。

例如:

declarative

关注做什么,而不是怎么做。隐藏了很多细节。

reactive

函数式响应式做法,input 输入有变化,button 状态就会跟着变。

相比较 input 输入变了、再调一遍函数、根据函数输出修改 button 状态,要自动化。

这句话说的有漏洞,常规做法也很自动化。先跳过吧,以后写一篇响应式编程的文章。

......

......

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值