React 使用Hooks简化受控组件的状态绑定


Posted in Javascript onMarch 18, 2019

开始之前

阅读本文需要对以下几项有一定了解

ECMAScript 6

文章中大量用到了 ES6 语法,比如解构赋值和函数参数默认值、剩余参数、展开语法、箭头函数等。

Hooks

React 在 16.8 版本中推出了 Hooks,它允许你在“函数组件”中使用“类组件”的一些特性。

React 本身提供了一些 Hooks,比如 useState、useReducer 等。通过在一个以“use”作为命名起始的函数中调用这些 Hooks,就得到了一个 custom Hook(自定义 Hook)。

Custom Hooks 允许我们把任何逻辑封装到其中,以便于复用足够小的组件逻辑。

Controlled Components

当我们把像 <input> <textarea> 和 <select> 这样的 HTML 元素本身的状态交给 React state 去管理,我们就得到了一个“受控组件”。

styled-components

一个与 React 契合良好的 CSS in JS 库。它允许你使用 JS 编写样式,并编译成纯 CSS 文件。
下面代码中所有的样式都是使用它编写的。如果对代码中样式的实现不是很感兴趣的话, 这个可以跳过。

代码实现

Input 组件

首先我们需要实现一个 Input 组件,我们将在该组件的基础上进行输入、校验并提示。

Input.js

import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

const Wrap = styled.div({
 display: 'flex',
 flexDirection: 'column',

 label: { display: 'flex', alignItems: 'center' },

 input: {
  marginLeft: 8,
 },

 p: {
  color: 'red',
 },
});

function Input({ label, type, helperText, error, ...otherProps }) {
 return (
  <Wrap>
   <label>
    {label}:
    <input {...otherProps} type={type} />
   </label>
   {error && <p>{helperText}</p>}
  </Wrap>
 );
}

Input.propTypes = {
 label: PropTypes.string,
 type: PropTypes.string,
 helperText: PropTypes.string,
 error: PropTypes.bool,
};

export default Input;

该组件主要接收以下几个 props:

  • label label 标签的文本
  • type 赋值给原生 input 标签的 type 属性
  • error 数据类型为 Boolean,如果为 true 则表示当前表单域有错误,即验证不通过
  • helperText 当前表单域验证不通过时,显示在表单域下方的提示文字
  • otherProps props 中除了上述四个以外的其他属性,全部赋值给原生 input 标签

Custom Hook

有了 UI 组件之后,就可以开始实现我们的自定义 Hook 了。

useInput.js

import { useState } from 'react';

export default function useInput({
 initValue = '',
 helperText = '',
 validator = () => true,
 validateTriggers = ['onChange'],
} = {}) {
 // 保存用户输入的值,使用 initValue 作为初始值
 const [value, setValue] = useState(initValue);
 // Boolean 类型,表示当前表单项的验证状态
 const [error, setError] = useState(false);

 function onChange(e) {
  const { value } = e.target;

  setValue(value);

  // 根据 validateTriggers 的选项,决定是否要在 onChange 里进行校验
  if (validateTriggers.includes('onChange')) {
   setError(!validator(value));
  }
 }

 /**
  * 根据 validateTriggers 生成相应的事件处理器
  */
 function createEventHandlers() {
  const eventHandlers = {};

  validateTriggers.forEach(item => {
   // 生成相应的事件处理器,并在其中做输入校验。
   eventHandlers[item] = e => {
    const { value } = e.target;
    setError(!validator(value));
   };
  });

  return eventHandlers;
 }

 const eventHandlers = createEventHandlers();

 return {
  value,
  helperText,
  error,
  ...eventHandlers,
  onChange,
 };
}

useInput 接收一个 options 对象作为参数,考虑到扩展性,使用一个配置对象作为参数比较好。

options 对象拥有以下几个属性:

  • initValue 输入框的初始值
  • helperText 当表单验证不通过时显示的字符串
  • validator 用于进行表单验证的函数,接收 value 作为参数,并返回一个 Boolean 值,表示表单验证是否通过
  • validateTriggers 字符串数组,表明在哪个或哪几个事件中调用 validator 进行输入校验。

在函数体中,我们调用两次 useState 来初始化 value 和 error 的值,分别保存用户输入的值和当前表单域的校验结果。
然后,声明一个 onChange 方法用来绑定 input 元素的 change 事件,在该方法中,我们把用户输入的值赋值给 value,同时根据 validateTriggers 的值,决定是否要在该方法中进行输入校验。该方法随后会被返回出去,再作为 props 传递给相应的组件,完成受控组件的状态绑定。

我们还需要声明一个 createEventHandlers 方法,该方法通过遍历 validateTriggers,生成相应的事件处理器,并在这些事件处理器中进行输入校验。

最后我们调用 createEventHandlers 方法,并把生成的 eventHandlers(事件处理器) 通过扩展运算符,插入到最终返回的对象中。

注意:这里我们需要把 onChange 放在最后,以免带有状态绑定的 onChange 方法被 eventHandlers 中的 onChange 覆盖掉。

具体使用

现在让我们来看看实际该如何使用:

import React from 'react';
import Input from './Input';
import useInput from './useInput';

// 用于验证邮箱的正则表达式
const EMAIL_REG = /\S+@\S+\.\S+/;

export default function Form() {
 const email = useInput({
  initValue: '',
  helperText: '请输入有效的邮箱!',
  validator: value => EMAIL_REG.test(value),
  validateTriggers: ['onBlur'],
 });

 const password = useInput({
  initValue: '',
  helperText: '密码长度需要在 6-20 之间!',
  validator: value => value.length >= 6 && value.length <= 20,
  validateTriggers: ['onChange', 'onBlur'],
 });

 /**
  * 判断是否禁用按钮
  */
 function isButtonDisabled() {
  // 当邮箱或密码未填写时,或者邮箱或密码输入校验未通过时,禁用按钮
  return !email.value || !password.value || email.error || password.error;
 }

 /**
  * 处理表单提交
  */
 function handleButtonClick() {
  console.log('邮箱:', email.value);
  console.log('密码:', password.value);
 }

 return (
  <div>
   <Input {...email} label="邮箱" type="email" />
   <Input {...password} label="密码" type="password" />

   <button disabled={isButtonDisabled()} onClick={handleButtonClick}>
    登录
   </button>
  </div>
 );
}

这里调用了两次 useInput,初始化 email 和 password 表单域数据。

然后使用扩展运算符,把值全部赋给 Input 组件。只用了几行代码就完成了定义初始值和受控组件的绑定,是不是很方便?

当我们输入邮箱的时候,并不会出现校验提示,但是一旦从邮箱输入框失去焦点以后,输入的值就会被校验,并根据校验结果显示相应的提示。而密码输入框,则会在输入的过程中和失焦后都进行校验。

总结

上面这个例子已经可以处理基本的表单验证,至于格式化用户输入的数据以及自定义收集表单域的值的时机等其他需求,我就不再演示了,大家可以自行设计。这也是 Hooks 的特殊之处,它让我们可以更容易的复用逻辑代码,可以根据需要自行编写 custom Hooks。

文章中关于 useInput 的 API 设计只是众多方案中的一种,只是为大家提供一些参考。你也可以把整个表单的状态封装到一个 useForm 方法中,统一管理所有表单域的状态。

希望本文能为大家带来一些关于如何使用 Hooks 的灵感,即使从来没有使用过 Hooks,也强烈建议大家尝试一下。我已经在项目中大量使用 Hooks 了,并且它也为我带来了很好的效果。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
ExtJS 2.2.1的grid控件在ie6中的显示问题
May 04 Javascript
JavaScript 继承详解(二)
Jul 13 Javascript
javascript 触发HTML元素绑定的函数
Sep 11 Javascript
js实现格式化金额,字符,时间的方法
Feb 26 Javascript
javascript弹出窗口实现代码
Nov 12 Javascript
谈谈我对JavaScript原型和闭包系列理解(随手笔记6)
Dec 20 Javascript
两种js监听滚轮事件的实现方法
May 13 Javascript
JS文件上传神器bootstrap fileinput详解
Jan 28 Javascript
用ES6的class模仿Vue写一个双向绑定的示例代码
Apr 20 Javascript
详解Vue前端对axios的封装和使用
Apr 01 Javascript
JS实现表单中点击小眼睛显示隐藏密码框中的密码
Apr 13 Javascript
Vue+element-ui添加自定义右键菜单的方法示例
Dec 08 Vue.js
JavaScript显式数据类型转换详解
Mar 18 #Javascript
浅谈js中的bind
Mar 18 #Javascript
node微信开发之获取access_token+自定义菜单
Mar 17 #Javascript
JavaScript中this用法学习笔记
Mar 17 #Javascript
通过JavaScript下载文件到本地的方法(单文件)
Mar 17 #Javascript
微信小程序登录session的使用
Mar 17 #Javascript
Javascript读写cookie的实例源码
Mar 16 #Javascript
You might like
php 需要掌握的东西 不做浮躁的人
2009/12/28 PHP
深入PHP异步执行的详解
2013/06/03 PHP
PHP实现连接设备、通讯和发送命令的方法
2015/10/13 PHP
PHP实现清除wordpress里恶意代码
2015/10/21 PHP
PHP模板引擎Smarty之配置文件在模板变量中的使用方法示例
2016/04/11 PHP
LNMP部署laravel以及xhprof安装使用教程
2017/09/14 PHP
AppBaseJs 类库 网上常用的javascript函数及其他js类库写的
2010/03/04 Javascript
JS倒计时代码汇总
2014/11/25 Javascript
JavaScript实现Flash炫光波动特效
2015/05/14 Javascript
Javascript实现单例模式
2016/01/24 Javascript
jquery-mobile表单的创建方法详解
2016/11/23 Javascript
深入理解Angularjs中的$resource服务
2016/12/31 Javascript
nodejs开发——express路由与中间件
2017/03/24 NodeJs
Vue+Jwt+SpringBoot+Ldap完成登录认证的示例代码
2018/05/21 Javascript
详解webpack运行Babel教程
2018/06/13 Javascript
获取layer.open弹出层的返回值方法
2018/08/20 Javascript
vue实现购物车的监听
2020/04/20 Javascript
使用pdb模块调试Python程序实例
2015/06/02 Python
Python实时获取cmd的输出
2015/12/13 Python
Python2.7简单连接与操作MySQL的方法
2016/04/27 Python
Python的Tornado框架实现图片上传及图片大小修改功能
2016/06/30 Python
浅谈python内置变量-reversed(seq)
2017/06/21 Python
Python实现的微信公众号群发图片与文本消息功能实例详解
2017/06/30 Python
python实现八大排序算法(2)
2017/09/14 Python
Python如何实现MySQL实例初始化详解
2017/11/06 Python
Pytorch使用MNIST数据集实现CGAN和生成指定的数字方式
2020/01/10 Python
python 代码运行时间获取方式详解
2020/09/18 Python
用HTML5制作一个简单的桌球游戏的教程
2015/05/12 HTML / CSS
中东地区为妈妈们提供一切的头号购物目的地:Sprii
2018/05/06 全球购物
应用化学专业职业生涯规划书
2013/12/31 职场文书
本科毕业生求职自荐信
2014/02/03 职场文书
浪费资源的建议书
2014/03/12 职场文书
数学高效课堂实施方案
2014/03/29 职场文书
学习十八届四中全会精神思想汇报
2014/10/23 职场文书
鲁冰花观后感
2015/06/10 职场文书
Win11 Beta 22621.601 和 22622.601今日发布 KB5017384修复内容汇总
2022/09/23 数码科技