在前端开发中,表单验证是一个非常重要的环节。然而,当表单中有多个字段需要验证时,我们往往会遇到一个问题:如何动态控制这些字段的验证规则?
传统的实现方式是在表单字段的 change 事件中手动添加、删除验证规则,但这样会导致代码冗长、难以维护。而 RxJS 的出现,为我们提供了一种优雅的解决方案。
RxJS 简介
RxJS 是 ReactiveX 的 JavaScript 版本,它是一个基于可观察序列的编程模型。通过使用 RxJS,我们可以轻松地管理异步数据流,将复杂的事件处理逻辑简化为一些简单的可组合的操作符。
RxJS 中最核心的概念是 Observable,它表示一个可观察的数据流,可以用于处理异步事件。Observable 可以发出三种类型的值:next、error 和 complete。我们可以通过一系列操作符对 Observable 进行处理,例如 map、filter、merge、switch 等等。
动态控制表单验证规则的实现
下面我们来看一个示例,假设我们有一个表单,其中包含用户名、密码和确认密码三个字段。我们需要实现以下功能:
- 当用户名输入框失去焦点时,验证用户名是否已存在。
- 当密码输入框失去焦点时,验证密码是否符合规则。
- 当确认密码输入框失去焦点时,验证确认密码是否与密码相同。
首先,我们需要定义每个表单字段的验证规则。我们使用一个对象来存储这些规则,其中键是表单字段的名称,值是一个数组,表示该字段需要执行的验证函数。
const validators = { username: [checkUsernameExist], password: [checkPasswordFormat], confirmPassword: [checkPasswordMatch], };
接下来,我们需要将每个输入框转换为一个 Observable,以便我们能够对其进行操作。我们可以使用 RxJS 的 fromEvent 操作符来实现这个功能。
const username$ = fromEvent(usernameInput, 'blur'); const password$ = fromEvent(passwordInput, 'blur'); const confirmPassword$ = fromEvent(confirmPasswordInput, 'blur');
现在,我们可以通过 combineLatest 操作符将这三个 Observable 合并成一个,以便我们可以在它们之间进行交互。combineLatest 会在每个 Observable 发出新值时重新计算结果。
// javascriptcn.com 代码示例 const form$ = combineLatest([ username$, password$, confirmPassword$, ]).pipe(map(() => ({ username: usernameInput.value, password: passwordInput.value, confirmPassword: confirmPasswordInput.value, })));
在上面的代码中,我们将每个输入框的值组合成一个对象,并将其作为 combineLatest 的输出。
现在,我们可以使用 switchMap 操作符来根据表单字段的名称动态获取验证规则。我们可以在 switchMap 中使用 map 操作符来获取每个字段的验证规则,并使用 mergeAll 操作符将它们合并成一个 Observable。
// javascriptcn.com 代码示例 const validation$ = form$.pipe( switchMap(form => { return merge( ...Object.entries(form).map(([name, value]) => { const validators = getValidators(name); return from(validators).pipe( concatMap(validator => validator(value)), map(() => ({ [name]: null })), catchError(error => of({ [name]: error })), ); }) ); }) );
在上面的代码中,我们将表单的值传递给 switchMap,然后使用 Object.entries 将表单字段的名称和值转换为一个数组。然后,我们使用 map 操作符获取每个字段的验证规则,并使用 from 将它们转换为 Observable。我们使用 concatMap 操作符将验证函数连接起来,并使用 map 和 catchError 将验证结果转换为一个对象,其中键是字段的名称,值是验证结果。
最后,我们可以使用 subscribe 方法订阅 validation$,并在每个表单字段下面显示验证结果。
// javascriptcn.com 代码示例 validation$.subscribe(results => { for (const [name, result] of Object.entries(results)) { const errorElement = document.querySelector(`[data-for="${name}"]`); if (result) { errorElement.textContent = result; errorElement.classList.add('error'); } else { errorElement.textContent = ''; errorElement.classList.remove('error'); } } });
在上面的代码中,我们遍历验证结果并将其显示在每个表单字段下面。如果验证结果不为空,则显示错误消息并将表单字段的样式设置为错误状态。
总结
RxJS 提供了一种优雅的方式来动态控制多个表单字段的验证规则。使用 RxJS,我们可以轻松地将表单字段转换为 Observable,并使用一系列操作符对它们进行处理。我们可以使用 combineLatest 将多个 Observable 合并成一个,使用 switchMap 根据表单字段的名称动态获取验证规则,使用 mergeAll 将验证规则合并成一个 Observable。最后,我们可以使用 subscribe 方法订阅验证结果,并将其显示在每个表单字段下面。
完整代码示例:
// javascriptcn.com 代码示例 const usernameInput = document.querySelector('#username'); const passwordInput = document.querySelector('#password'); const confirmPasswordInput = document.querySelector('#confirm-password'); const validators = { username: [checkUsernameExist], password: [checkPasswordFormat], confirmPassword: [checkPasswordMatch], }; const username$ = fromEvent(usernameInput, 'blur'); const password$ = fromEvent(passwordInput, 'blur'); const confirmPassword$ = fromEvent(confirmPasswordInput, 'blur'); const form$ = combineLatest([ username$, password$, confirmPassword$, ]).pipe(map(() => ({ username: usernameInput.value, password: passwordInput.value, confirmPassword: confirmPasswordInput.value, }))); const validation$ = form$.pipe( switchMap(form => { return merge( ...Object.entries(form).map(([name, value]) => { const validators = getValidators(name); return from(validators).pipe( concatMap(validator => validator(value)), map(() => ({ [name]: null })), catchError(error => of({ [name]: error })), ); }) ); }) ); validation$.subscribe(results => { for (const [name, result] of Object.entries(results)) { const errorElement = document.querySelector(`[data-for="${name}"]`); if (result) { errorElement.textContent = result; errorElement.classList.add('error'); } else { errorElement.textContent = ''; errorElement.classList.remove('error'); } } }); function getValidators(name) { return validators[name] || []; } function checkUsernameExist(username) { return fetch(`/api/check-username-exist?username=${encodeURIComponent(username)}`) .then(response => { if (response.ok) { return null; } else { return '用户名已存在'; } }); } function checkPasswordFormat(password) { if (/^[a-zA-Z0-9]{6,}$/.test(password)) { return null; } else { return '密码格式不正确'; } } function checkPasswordMatch(confirmPassword) { if (confirmPassword === passwordInput.value) { return null; } else { return '确认密码与密码不一致'; } }
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6586e73dd2f5e1655d13617f