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
相关产品推荐
相关产品推荐

