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

开始之前

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

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 了,并且它也为我带来了很好的效果。

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

(0)

相关推荐

  • 浅谈react受控组件与非受控组件(小结)

    我们都知道,有许多的web组件可以被用户的交互发生改变,比如:<input>,<select>,或者是我现在正在使用的富文本编辑器.这些组件在日常的开发中很不显眼,我们可以很轻易的通过输入一些内容或者设置元素的value属性来改变组件的值.但是,因为React是单向数据流绑定的,这些组件可能会变得失控: 1.一个维护它自己state里的value值的<Input>组件无法从外部被修改: 2.一个通过props来设置value值的<Input>组件只能通过外部

  • 浅谈React深度编程之受控组件与非受控组件

    受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意.这恰恰显示React的威力,满足不同规模大小的工程需求.譬如你只是做ListView这样简单的数据显示,将数据拍出来,那么for循坏与 {} 就足够了,但后台系统存在大量报表,不同的表单联动,缺了受控组件真的不行. 受控组件与非受控组件是React处理表单的入口.从React的思路来讲,作者肯定让数据控制一切,或者简单的理解为,页面的生成与更新得忠实地执行JSX的指令. 但是表单元素有其特殊之处,用户可以通过键盘

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

    开始之前 阅读本文需要对以下几项有一定了解 ECMAScript 6 文章中大量用到了 ES6 语法,比如解构赋值和函数参数默认值.剩余参数.展开语法.箭头函数等. Hooks React 在 16.8 版本中推出了 Hooks,它允许你在"函数组件"中使用"类组件"的一些特性. React 本身提供了一些 Hooks,比如 useState.useReducer 等.通过在一个以"use"作为命名起始的函数中调用这些 Hooks,就得到了一个

  • 详解在React中跨组件分发状态的三种方法

    当我问自己第一百次时,我正在研究一个典型的CRUD屏幕:"我应该将状态保留在这个组件中还是将其移动到父组件?". 如果需要对子组件的状态进行轻微控制.您可能也遇到了同样的问题. 让我们通过一个简单的例子和​​三种修复方法来回顾它.前两种方法是常见的做法,第三种方法不太常规. 问题: 为了向您展示我的意思,我将使用一个简单的书籍CRUD(译者注:增加(Create).读取查询(Retrieve).更新(Update)和删除(Delete))屏幕(如此简单,它没有创建和删除操作). 我们有

  • 浅析React中的受控组件和非受控组件

    非受控组件 表单数据由DOM本身处理.即不受setState()的控制,与传统的HTML表单输入相似,input输入值即显示最新值(使用 ref从DOM获取表单值) 1.非受控组件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body&g

  • 使用 React Hooks 重构类组件的示例详解

    目录 1. 管理和更新组件状态 2. 状态更新后的操作 3. 获取数据 4. 卸载组件时清理副作用 5.  防止组件重新渲染 6. Context API 7. 跨重新渲染保留值 8. 如何向父组件传递状态和方法? 9. 小结 最初,在 React 中可以使用 createClass 来创建组件,后来被类组件所取代.在 React 16.8 版本中,新增的 Hooks 功能彻底改变了我们编写 React 程序的方式,使用 Hooks 可以编写更简洁.更清晰的代码,并为创建可重用的有状态逻辑提供了

  • React Native可复用 UI分离布局组件和状态组件技巧

    目录 引言 包装 Context.Provider 作为父组件 使用 Context Hook 来实现子组件 使用 React 顶层 API 动态设置样式 复用 Context,实现其它子组件 抽取共同状态逻辑 自由组合父组件与子组件 示例 引言 单选,多选,是很常见的 UI 组件,这里以它们为例,来讲解如何分离布局组件和状态组件,以实现较好的复用性. 假如我们要实现如下需求: 这类 UI 有如下特点: 不管是单选还是多选,都可以有网格布局,我们可以把这个网格布局单独抽离出来,放到一个独立的组件

  • React受控组件与非受控组件详细介绍

    目录 1. 受控组件 1.1 介绍 1.2 受控组件简写 1.3 在表单中使用受控组件 1.4 综合案例 2. 非受控组件介绍 非受控组件的应用 1. 受控组件 1.1 介绍 概述: 将 state 与表单项中的 value 值绑定在一起,有 state 的值来控制表单元素的值,称为受控组件. 绑定步骤: 在state中添加一个状态,作为表单元素的value值 给表单元素绑定 change 事件,将表单元素的值设置为 state 的值 语法: # value={this.state.xx} 事件

  • React学习之受控组件与数据共享实例分析

    本文实例讲述了React学习之受控组件与数据共享.分享给大家供大家参考,具体如下: 在HTML当中,像<input>,<textarea>, 和 <select>这类表单元素会自己储存值,并且根据用户输入进行更新.但在React中,可变的值通常保存在组件的state中,并且只能用 setState() 方法进行更新.为了解决二者的矛盾,可以让HTML元素不再自己储存数据,而使用来自于react的state.也就是说HTML元素把数据保存在react组件的state中,并

  • React 组件的状态下移和内容提升的操作方法

    目录 前言 状态下移 内容提升 前言 本专栏的另一篇文章,讲到了 useMemo 有一定的开销,不能滥用,本篇文章讲解两个简单实用的优化组件渲染性能的方法: 状态下移 内容提升 在讲解这两种方法之前,我们需要先手动创建一个有严重渲染性能的组件,如下所示: import { useState } from 'react'; export default function App() { let [color, setColor] = useState('red'); return ( <div>

随机推荐