【React】手写并封装受控组件自定义hook表单验证器(附组件库线上体验地址)

前言

  • 有小伙伴说写组件库封装表单没有思路。我一开始感觉确实有点不太好写,如果自己写了自己用也就算了,做成封装给别人用的那考虑东西就有点多。后来有人发了个react-hook-form的仓库,我一看,这种利用自定义hook做验证的思路很不错啊,于是自己手写个。

组件库线上体验地址

原理

  • react-hook-form的使用方式需要拿到组件实例,对于我的组件库基本都是用函数组件写的且并未用forwardRef包装的轻量级组件库是不起作用的,除非我放弃原来封装好的组件,重新写,那肯定不划算。所以自己写个类react-hook-form且支持受控组件就是这次目标。
  • 我并没有照抄react-hook-form的形式,毕竟那个库好几个文件,每个文件都接近千行,我都懒得看,但我从它的使用方式大致推测了干了啥事。而我操作受控组件也是这样做就可以了。
  • 对于受控组件,也就是状态在组件内进行维护,一般暴露给外界的接口就是onchange接口,当用户输入或者点击时,onchange会被触发,我的组件库封装的组件暴露的onchange接口改了个名,叫callback。

难点1:如何把不知道数量的受控组件的回调转移到自定义hook中?

  • 这个感觉是最大难点,解决了这个问题,基本上就出来了。
  • 这个难点可以拆成2个小问题,一个是组件数量不知道,一个是受控组件回调转移。
  • react-hook-form上直接拿实例,就不存在回调转移问题,但有组件数量不知道问题,所以react-hook-form让用户自己调用其方法去register组件。我也可以利用这种思路解决这个问题,就是让用户自己调。
  • 解决数量问题,剩下就是回调问题,这个问题让我想了挺久的,后来想到这么个思路:自定义hook是可以接收参数的,那么我让用户把参数填好,吐出个对象,用户再用这个对象上的方法去回调受控组件的回调,这样就解决了。当然,组件数量不同,用的方法不同,所以让用户在输入参数的时候制定name,吐出来的对象里使用对应的name去回调就完成了。

难点2:回调传入受控组件如何收集状态?

  • 这个问题其实挺好解决的,在自定义hook内部建立状态,当用户输入触发回调,就会过一遍自定义hook,将数据setState就ok了。

难点3:如何传入多个验证器,并且反馈对应的验证结果?

  • 这个主要是数据格式问题,我一开始没理清反馈格式,后来发现验证器应该可以传多个,并且每个验证器会有对应的提示信息。
  • 正常来说,使用组件的人不需要关心哪个验证器通过哪个验证器没通过,只要把你返回的验证结果输出就可以了。所以对于返回的验证结果,使用数组字符串形式解决。感觉any真是TS最伟大发明,先全any,保证能使用再说,然后再慢慢精确格式。
  • 在触发回调时候,同时让验证器走一遍,拿到结果,设置到状态里,吐出状态给使用的组件。使用的组件就拿到验证结果,最后使用结果进行相应渲染。

难点4:如何解决dirty脏数据验证?

  • 这个也好解决,根据难点1的方式,吐出个对象,让用户传给onBlur触发Blur,函数里存入状态并进行判定,如果blur状态里没这个组件,说明没dirty,加上状态,如果有这个组件,说明dirty了。
  • 验证逻辑就加在第一次dirty处即可。 后续不加验证。

代码:

interface ValidateType{
    validate:(e:any)=>boolean
    message:string;
}

interface UseFormProps{
    name:string;
    validate?:Array<ValidateType>;
}

interface UserData{
    [key:string]:any
}
interface BlurDataType{
    [key:string]:boolean
}
interface FnObjType{
    [key:string]:(e:any)=>void
}
interface ValidataType{
    [key:string]:string[]
}

type UseFormType=[(fn: any) => void,FnObjType,ValidataType,FnObjType]

function useForm(args:UseFormProps[]):UseFormType{
    const [state,setState]=useState<UserData>()
    const [validata,setValidata]=useState<ValidataType>(()=>args.reduce((p,n)=>{p[n.name]=[];return p},{} as ValidataType))
    const [blurData,setBlurData]=useState<BlurDataType>({})
    const returnObj=useMemo(()=>{
        let obj:FnObjType={}
        let blurobj:FnObjType={}
        args.forEach((o)=>{
            obj[o.name]=(e:any)=>{
                if(o.validate){
                    let resArr:string[]=[]
                    o.validate.forEach((v)=>{
                        let sign = v.validate(e)
                        if(!sign){//true验证过
                            resArr.push(v.message)
                        }
                    })
                    setValidata({...validata,...{[o.name]:resArr}})
                }
                setState({...state,...{[o.name]:e}})
            }
            blurobj[o.name]=(e:any)=>{
                if(blurData&&blurData[o.name]){
                }else{
                    setBlurData({...blurData,...{[o.name]:true}})
                    if(o.validate){
                        let resArr:string[]=[]
                        o.validate.forEach((v)=>{
                            let sign = v.validate(e)
                            if(!sign){//true验证过
                                resArr.push(v.message)
                            }
                        })
                        setValidata({...validata,...{[o.name]:resArr}})
                    }
                }
            }
        })
        return[obj,blurobj]
    },[args, blurData, state, validata])
    const handleSubmit=(fn:any)=>{
        fn(state)
    }
    return [handleSubmit,returnObj[0],validata,returnObj[1]]
}


export default useForm;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章