You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React TypeScript:如何用自定义接口替换多个useState?

问题描述

我是React TypeScript新手,在项目中使用useState初始化状态,希望用自定义的interface或type替换这些分散的useState,但查阅相关文档后未得到理想结果。当前代码使用多个useState分别管理items、value、error,希望改用自定义的接口统一管理这些状态。

当前代码:

import React, { useRef, useState } from "react";
import ReactDOM from "react-dom";
import Chip from "@material-ui/core/Chip";
import TextField from "@material-ui/core/TextField";

interface details{
    value: string;
    emails: Array<string>;
    error?: string | null;
    item?:string;
}

export const TagActions = () => {
    const [items, setItem] = useState<string[]>([]); 
    const [value, setValue] = useState('')
    const [error, setError]= useState('')
    const divRef = useRef<HTMLDivElement>(null)

    const handleDelete = (item:any) => {
        console.log("handleDelete", item)
        const result = items.filter(i => i !== item)
        setItem(result)
    };
    
    const handleItemEdit = (item:any) =>{
        console.log("handleItemEdit", item)
        const result = items.filter(i => i !== item)
        setItem(result)
        setValue(item)
        console.log("value", value)
        
    };

    const handleKeyDown = (evt:any) => {
        if (["Enter", "Tab", ","].includes(evt.key)) {
          evt.preventDefault();

          var test = value.trim();
    
          if (test && isValid(test)) {
            items.push(test)
            setValue("")
           
          }
        }
    };

    const isValid = (email:any)=> {
        let error = null;
    
        if (isInList(email)) {
          error = `${email} has already been added.`;
        }
    
        if (!isEmail(email)) {
          error = `${email} is not a valid email address.`;
        }
    
        if (error) {
            setError(error);
    
          return false;
        }
    
        return true;
    }

    const isInList = (email:any)=> {
        return items.includes(email);
      }
    
    const isEmail = (email:any)=> {
        return /[\w\d\.-]+@[\w\d\.-]+\.[\w\d\.-]+/.test(email);
    }

    const handleChange = (evt:any) => {
        setValue(evt.target.value)
        // setError("")
        
    };

    const handlePaste = (evt:any) => {
        evt.preventDefault();
    
        var paste = evt.clipboardData.getData("text");
        var emails = paste.match(/[\w\d\.-]+@[\w\d\.-]+\.[\w\d\.-]+/g);
    
        if (emails) {
          var toBeAdded = emails.filter((email:any) => !isInList(email));
            
            setItem(toBeAdded)
        
        }
    };
    

    return (
        <>
          <div>
          <TextField id="outlined-basic" variant="outlined"
          InputProps={{
            startAdornment: items.map(item => (
              <Chip
                key={item}
                tabIndex={-1}
                label={item}
                onDelete={() => handleDelete(item)}
                onClick={() => handleItemEdit(item)}
              />
            )),
  
          }}
            ref={divRef}
            value={value}
            placeholder="Type or paste email addresses and press `Enter`..."
            onKeyDown={(e) => handleKeyDown(e)}
            onChange={(e) => handleChange(e)}
            onPaste={(e) => handlePaste(e)}
          />
          </div>
  
          {error && <p className="error">{error}</p>}
        </>
      );
}
解决方案

1. 修正接口定义

首先调整接口,遵循TS惯例首字母大写,移除无用的item字段,确保字段名和实际状态对应:

interface Details {
  value: string;
  emails: string[]; // 对应原来的items
  error: string | null; // 明确类型,避免undefined
}

2. 合并分散的useState为统一状态

将三个独立的useState替换为一个,用Details类型初始化:

const [state, setState] = useState<Details>({
  value: '',
  emails: [],
  error: null
});

3. 修改状态更新逻辑

所有状态更新都通过setState进行,使用函数式更新(prev => {...prev, ...changes})保证不可变性,避免直接修改原状态:

  • 删除邮箱时:
    const handleDelete = (item: string) => {
      setState(prev => ({
        ...prev,
        emails: prev.emails.filter(i => i !== item)
      }));
    };
    
  • 更新输入框值时:
    const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
      setState(prev => ({
        ...prev,
        value: evt.target.value,
        error: null // 输入时清空错误提示
      }));
    };
    

4. 替换所有any类型

为函数参数添加具体类型,提升类型安全性:

  • evt参数使用React.KeyboardEvent<HTMLInputElement>、React.ChangeEvent<HTMLInputElement>等
  • email和item参数使用string类型

完整修改后代码

import React, { useRef, useState } from "react";
import Chip from "@material-ui/core/Chip";
import TextField from "@material-ui/core/TextField";

interface Details {
  value: string;
  emails: string[];
  error: string | null;
}

export const TagActions = () => {
  const [state, setState] = useState<Details>({
    value: '',
    emails: [],
    error: null
  });
  const divRef = useRef<HTMLDivElement>(null);

  const handleDelete = (item: string) => {
    setState(prev => ({
      ...prev,
      emails: prev.emails.filter(i => i !== item)
    }));
  };

  const handleItemEdit = (item: string) => {
    setState(prev => ({
      ...prev,
      emails: prev.emails.filter(i => i !== item),
      value: item
    }));
  };

  const handleKeyDown = (evt: React.KeyboardEvent<HTMLInputElement>) => {
    if (["Enter", "Tab", ","].includes(evt.key)) {
      evt.preventDefault();
      const test = state.value.trim();

      if (test && isValid(test)) {
        setState(prev => ({
          ...prev,
          emails: [...prev.emails, test],
          value: ''
        }));
      }
    }
  };

  const isValid = (email: string): boolean => {
    let error: string | null = null;

    if (isInList(email)) {
      error = `${email} has already been added.`;
    }

    if (!isEmail(email)) {
      error = `${email} is not a valid email address.`;
    }

    if (error) {
      setState(prev => ({ ...prev, error }));
      return false;
    }

    return true;
  };

  const isInList = (email: string): boolean => {
    return state.emails.includes(email);
  };

  const isEmail = (email: string): boolean => {
    return /[\w\d\.-]+@[\w\d\.-]+\.[\w\d\.-]+/.test(email);
  };

  const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
    setState(prev => ({
      ...prev,
      value: evt.target.value,
      error: null
    }));
  };

  const handlePaste = (evt: React.ClipboardEvent<HTMLInputElement>) => {
    evt.preventDefault();
    const paste = evt.clipboardData.getData("text");
    const emails = paste.match(/[\w\d\.-]+@[\w\d\.-]+\.[\w\d\.-]+/g);

    if (emails) {
      const toBeAdded = emails.filter(email => !isInList(email));
      setState(prev => ({
        ...prev,
        emails: [...prev.emails, ...toBeAdded]
      }));
    }
  };

  return (
    <>
      <div>
        <TextField
          id="outlined-basic"
          variant="outlined"
          InputProps={{
            startAdornment: state.emails.map(item => (
              <Chip
                key={item}
                tabIndex={-1}
                label={item}
                onDelete={() => handleDelete(item)}
                onClick={() => handleItemEdit(item)}
              />
            ))
          }}
          inputRef={divRef}
          value={state.value}
          placeholder="Type or paste email addresses and press `Enter`..."
          onKeyDown={handleKeyDown}
          onChange={handleChange}
          onPaste={handlePaste}
        />
      </div>

      {state.error && <p className="error">{state.error}</p>}
    </>
  );
};

关键说明

  • 使用函数式更新setState(prev => ...)确保基于最新状态进行修改,避免状态滞后问题
  • 所有状态更新都保持不可变性,通过展开运算符创建新对象/数组,符合React状态更新规则
  • 移除了原代码中直接修改数组的items.push(test),改用[...prev.emails, test]创建新数组
  • 修正了TextField的ref用法,Material-UI的TextField需要用inputRef而非ref

内容的提问来源于stack exchange,提问作者user15361826

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 07:30:47