react表單受控的實(shí)現(xiàn)方案
背景
數(shù)據(jù)的受控控制一直是react里的一個(gè)痛點(diǎn),當(dāng)我想要實(shí)現(xiàn)一個(gè)輸入框的受控控制時(shí),我需要定義一個(gè)onChange和value,手動(dòng)去實(shí)現(xiàn)數(shù)據(jù)的綁定。當(dāng)受控的元素一多,便會(huì)出現(xiàn)滿屏的set。
筆者所在的公司業(yè)務(wù)比較大,偏向于后臺(tái)管理的sass系統(tǒng),用戶群體比較大,其中就包括有谷歌這些用戶。自然迭代更新速度也比較快,而隨著不斷的迭代更新,項(xiàng)目也是日益龐大。在日常需求中,表單的開發(fā)就占據(jù)了大部分場景,而在用react開發(fā)表單這塊,特別是當(dāng)表單字段過于復(fù)雜,表單過于龐大時(shí),開發(fā)受控表單也要投入不小的開發(fā)生產(chǎn)力和不少的受控代碼,不說優(yōu)雅和后期的維護(hù),對(duì)于頁面響應(yīng)速度來說,也是會(huì)隨著字段的增加而變的越來越慢,即使拆分成顆粒度最小的組件。
在一個(gè)表單業(yè)務(wù)中,字段A依賴于字段B,字段C又依賴于字段A的變化,而字段C追蹤依賴后又要實(shí)時(shí)渲染在視圖里。這是很常見的需求場景,當(dāng)組織這些依賴的時(shí)候,隨之而來的考慮的是一個(gè)性能問題,我們很常見的一個(gè)做法便是狀態(tài)的提升,將它們都放到頂層容器中,統(tǒng)一管理。但是這樣會(huì)隨著依賴的不斷增加,而造成當(dāng)前渲染的樹不斷渲染,當(dāng)越來越多的字段沉積,不斷的重新渲染,直到最后頁面奔潰,內(nèi)存溢出。
當(dāng)這種做法產(chǎn)生了較大的副作用后,我們這時(shí)候會(huì)開始考慮改變做法,優(yōu)化代碼,考慮在各自的組件中定義onchange和value維護(hù)這個(gè)字段,然后在注入到全局狀態(tài)管理中,在需要用到的組件中訂閱這個(gè)字段,這樣就減少了重復(fù)渲染的次數(shù)。
在這個(gè)過程中,我們通常需要定義一系列受控代碼,以達(dá)到我們的預(yù)期。
第三方庫
當(dāng)然,如果在使用第三方UI庫的時(shí)候,通常會(huì)提供Form組件,比如antd。而 antd的Form強(qiáng)依賴于它本身的表單控件,而且對(duì)于定義一個(gè)表單而言,所定義的受控邏輯略微繁瑣。表單業(yè)務(wù)復(fù)雜時(shí),需要傳入一系列的prop和方法。當(dāng)需要與別的UI結(jié)合的時(shí)候,F(xiàn)orm組件就失去了他的意義。
筆者之前所用的是react-hook-form,react-hook-form能夠輕易集成第三方UI,同樣的,對(duì)于數(shù)據(jù)受控以及數(shù)據(jù)訂閱方面,rect-hook-form需要定義的代碼同樣不少,比如使一個(gè)表單項(xiàng)受控,需要顯示引入它的Controller組件包裹。比如需要一個(gè)狀態(tài)去實(shí)時(shí)反應(yīng)到表單之外的視圖的時(shí)候,要另外定義一個(gè)變量結(jié)合它的react-hook-form的watch去維護(hù)這個(gè)變量的狀態(tài)。
而其實(shí)在開發(fā)過程中,我們并不想要關(guān)心這種受控過程,只想要知道受控的結(jié)果和應(yīng)用受控狀態(tài)。當(dāng)定義好一套表單模型數(shù)據(jù)時(shí),并將這套模型與他對(duì)應(yīng)的表單項(xiàng)視圖關(guān)聯(lián)起來的時(shí)候,它們之間應(yīng)該自動(dòng)建立起一個(gè)受控的橋梁和紐帶,開發(fā)者不需要知道這個(gè)橋梁是怎么建立的,不需要去維護(hù)這個(gè)橋梁,開發(fā)者需要做的,就是去應(yīng)用這套模型。而這套模型,永遠(yuǎn)是最新的。并且不影響到它們外部的其他元素。
React-form-simple
react-form-simple是一個(gè)基于react的可受控可擴(kuò)展的輕量級(jí)表單庫,以最快的速度以及最精簡的代碼渲染出一個(gè)可受控的表單。React-form-simple除了集成自身功能之外,還具有非??蓴U(kuò)展的接口, 并可與第三方ui集成使用。
受 react-hook-form 啟發(fā), 得源于 react-hook-form 的設(shè)計(jì)靈感,筆者所在團(tuán)隊(duì)花費(fèi)了兩個(gè)月時(shí)間梳理了項(xiàng)目的全部表單,綜合整理并開發(fā)出了一款輕量級(jí)表單庫。該庫已重寫了項(xiàng)目的大部分表單,并已在生產(chǎn)環(huán)境中使用了大半年之久。
react-form-simple 基于 es6
的 Proxy 創(chuàng)建一個(gè)可觀察的表單模型對(duì)象,該庫有如下特點(diǎn):
通過創(chuàng)建一個(gè)可觀察對(duì)象來觀察表單的模型操作, 表單項(xiàng)的受控直接通過
_.
賦值。簡單幾行代碼就可以完成表單受控, 無需關(guān)心受控邏輯, 無需關(guān)心受控過程, 只需要知道受控結(jié)果和如何應(yīng)用你的受控狀態(tài)。
每個(gè)表單項(xiàng)之間的渲染自動(dòng)完全隔離, 不需要自行組織組件隔離。這將能夠更快的處理表單輸入后的響應(yīng)速度, 以及很大程度的避免在大型動(dòng)態(tài)數(shù)據(jù)下造成的頁面卡頓。
具有數(shù)據(jù)觀測功能, 可以在某些場景下對(duì)整個(gè)表單或者某個(gè)具體的表單項(xiàng)進(jìn)行單一或者統(tǒng)一的觀察監(jiān)測, 可以在你需要用表單項(xiàng)最新的值進(jìn)行渲染的地方進(jìn)行值的訂閱。
靈活的使用方式, 靈活的頁面布局組合, 開發(fā)者可以根據(jù)自己的喜好和場景使用某種方式以及內(nèi)置布局。在大多數(shù)場景下, 無需開發(fā)者手動(dòng)布局。
簡約的 API 設(shè)計(jì), 在操作表單的過程中, 簡單的只需要引入兩個(gè) API, 就可以完成大部分工作。
高度可擴(kuò)展的表單接口, 在一些復(fù)雜需求或者定制化場景中, 開發(fā)者可以自行定制表單的控制邏輯。
可以輕易集成在你的 UI 或者 第三方庫中。
完整的類型推斷。
單元測試覆蓋
使用
npm install react-form-simple -S
簡化表單受控
react-form-simple 暴露出一個(gè) render 方法,在一般情況下, 傳入表單模型字段和渲染視圖,便能搭建它們之間的受控橋梁。
import React, { useEffect } from 'react'; import { useForm } from 'react-form-simple'; export default function App() { const { render, model } = useForm({ name: '' }); const renderName = render('name')(<input />); const onSubmit = () => void console.log(model); return ( <> {renderName} <button onClick={onsubmit}>submit</button> </> ); }
如上例子,創(chuàng)建一個(gè)受控表單只需要兩行代碼。
- 通過useForm創(chuàng)建一個(gè)表單數(shù)據(jù)模型。
- 使用useForm暴露出的render方法創(chuàng)建表單項(xiàng)與渲染視圖的受控橋梁。
開發(fā)者不需要知道name
字段與input
的受控過程,不需要關(guān)心他們是如何受控的,開發(fā)者需要做的,就是將model 模型數(shù)據(jù)如何運(yùn)用在代碼中。更多用法請(qǐng)查看文檔
而在name
字段與 input
視圖受控時(shí),它們之間的變化不會(huì)重新導(dǎo)致外部的任何重復(fù)渲染。也就是說,表單的渲染都是完全相互隔離的。
訂閱最新值
在一個(gè)表單開發(fā)中,通常有A字段依賴于B字段的場景,比如在B字段的值發(fā)生改變后,A字段需要做相應(yīng)的邏輯處理,并將A字段的值實(shí)時(shí)渲染在視圖里。
在 react-form-simple 里,表單項(xiàng)的受控并不能直接引起外部視圖的刷新,可以借助 useSubscribe
來訂閱某個(gè)字段或者整個(gè)表單,以將它渲染在表單視圖之外。
import React from 'react'; import { useForm } from 'react-form-simple'; export default function App() { const { render, useSubscribe } = useForm({ name: 'name' }); const renderName = render('name')(<input />); const subscribeName = useSubscribe(({ model }) => model.name); console.log({ subscribeName }); return ( <> {renderName} </> ); }
如上所示,在 name
發(fā)生變化的時(shí)候,subscribeName 便會(huì)實(shí)時(shí)打印。
但是一般不推薦在父級(jí)組件中來訂閱, 因?yàn)檫@會(huì)引起整個(gè)渲染樹的更新, 推薦的做法是只用在需要訂閱的地方, 可以通過 props 透傳, 也可以將它注入到全局狀態(tài)管理中。
下面這個(gè)例子展示的是有兩個(gè)輸入框,當(dāng)其中一個(gè)輸入框的值等于 amount 的時(shí)候,便顯示另外一個(gè)輸入框。
import { useForm } from "react-form-simple"; export default function App() { const { render, useSubscribe } = useForm({ name: "", amount: "" }); const renderName = render("name")(<input />); const nameValue = useSubscribe(({ model }) => model.name); const renderAmount = nameValue === "amount" && render("amount")(<input />); return ( <> {renderAmount} {renderName} </> ); }
watch監(jiān)聽
react-form-simple 提供數(shù)據(jù)觀測功能,使用 useWatch 可以觀察某個(gè)字段或者整個(gè)表單的變化。
import { useForm } from 'react-form-simple'; export default function App() { const { render, useWatch } = useForm({ name: 'name', age: 'age' }); const renderName = render('name')(<input className="input" />); const renderAge = render('age')(<input className="input" />); useWatch( ({ model }) => [model?.name, model?.age], (value, preValue) => { console.log({ value, preValue }); }, ); return ( <> {renderName} {renderAge} </> ); }
表單校驗(yàn)
通過 useForm 暴露出的 validate
方法可以快速的對(duì)表單模型進(jìn)行校驗(yàn)。
import Button from '@components/Button'; import React from 'react'; import { useForm } from 'react-form-simple'; export default function App() { const { render, validate, model, clearValidate, setError } = useForm({ name: '', age: '', }); const renderName = render('name', { rules: { required: 'Please Input' }, requireIndicator: true, label: 'name', })(<input className="input" />); const renderAge = render('age', { label: 'age', rules: [ { required: 'Please Input' }, { validator(value) { if (value < 10) { return 'Min 10'; } return ''; }, }, ], })(<input className="input" />); const renderSubmit = ( <Button onClick={async () => { await validate(); console.log(model); }} > Submit </Button> ); const renderclear = ( <Button onClick={async () => { clearValidate(); }} > clear </Button> ); return ( <> {renderName} {renderAge} {renderSubmit} {renderclear} </> ); }
集成第三方UI
在實(shí)際項(xiàng)目中,我們通常需要用到第三方UI來渲染視圖,rect-form-simple 可以很輕易的與這些UI庫集成在一起,無論什么UI庫。
下面的例子是集成 antd
的例子。
import Button from '@components/Button'; import { Checkbox, Input, Select } from 'antd'; import React from 'react'; import { useForm } from 'react-form-simple'; export default function App() { const { render, model, validate } = useForm( { name: '', select: 'jack', checkbox: true }, { labelPosition: 'top' }, ); const renderName = render('name', { label: 'name', rules: { required: 'please Input' }, requireIndicator: true, defineProps(options) { return { status: options.isError ? 'error' : '' }; }, })(<Input style={{ width: '300px' }} placeholder="Please Input" />); const renderSelect = render('select', { label: 'age', formatChangeValue: (e) => e, })( <Select options={[ { value: 'jack', label: 'Jack' }, { value: 'lucy', label: 'Lucy' }, { value: 'Yiminghe', label: 'yiminghe' }, ]} style={{ width: '300px' }} />, ); const renderCheckbox = render('checkbox', { label: 'Checkbox', labelPosition: 'row', })(<Checkbox />); const renderSubmit = ( <Button onClick={async () => { await validate(); console.log(model); }} > Submit </Button> ); return ( <> {renderName} {renderSelect} {renderCheckbox} <div>{renderSubmit}</div> </> ); }
無論開發(fā)者使用的什么UI庫,react-form-simple 都可以很好的與它們集成在一起。
組件形式
在開發(fā)者使用的習(xí)慣上,可能有些開發(fā)人員習(xí)慣于以組件的形式來渲染視圖,這可以更加直觀的組織代碼。 react-form-simple 暴露出了兩個(gè)組件 Form 和 FormItem 來提供給開發(fā)人員使用組件形式來創(chuàng)建表單。在需要定制化表單,或者處理一些額外的邏輯的時(shí)候,這兩個(gè)組件將非常有用。
開發(fā)者可以基于此來封裝適合自己使用習(xí)慣的 useForm,以此來定制化的開發(fā)人員的表單hook。
使用 FormItem 例子,關(guān)于更多介紹請(qǐng)查看FormItem
import Button from '@components/Button'; import React from 'react'; import { FormItem, useForm } from 'react-form-simple'; export default function App() { const { contextProps, model, validate } = useForm({ name: '', age: 'age', }); return ( <> <FormItem defaultValue={model.name} rules={{ required: 'Please Input' }} bindId="name" getContent={({ attrs }) => <input {...attrs} className="input" />} contextProps={contextProps} /> <FormItem defaultValue={model.age} rules={{ required: 'Please Select' }} bindId="age" getContent={({ attrs }) => { return ( <select {...attrs}> <option value="name">name</option> <option value="age">age</option> <option value="email">email</option> </select> ); }} contextProps={contextProps} /> <Button onClick={async () => { await validate(); console.log(model); }} > Submit </Button> </> ); }
定制化表單
開發(fā)者可以傳入一個(gè)普通的表單對(duì)象完全自定義表單的受控邏輯來定制化表單,而無需依賴于 useForm hook。
import Button from '@components/Button'; import React, { useEffect, useRef } from 'react'; import { Form, FormItem, type ContextProps, type FormApis, } from 'react-form-simple'; export default function App() { const formRef = useRef<FormApis>(null); const model = useRef({ name: '', }) as any; const contextProps = useRef<ContextProps>({ updated({ bindId, value }) { model.current[bindId] = value; }, reset({ bindId }) { model.current[bindId] = ''; formRef.current?.setValue(bindId, ''); }, }); useEffect(() => { const values = { name: 'name' }; model.current = values; formRef.current?.setValues(values); }, []); const renderName = ( <FormItem bindId="name" rules={{ required: 'Please Input' }} label="name" getContent={({ attrs }) => { return ( <input placeholder="Please Input" {...attrs} className="input" /> ); }} /> ); return ( <Form ref={formRef} contextProps={contextProps.current} direction="column" labelWidth="40px" > {renderName} <FormItem label=" "> <Button onClick={() => { console.log(model.current); }} > submit </Button> <Button style={{ marginLeft: '15px' }} plain onClick={() => { formRef.current?.reset(); }} > reset </Button> </FormItem> </Form> ); }
關(guān)于更多定制化表單,請(qǐng)查看定制化表單
結(jié)語
本文簡單的探討了react表單的受控實(shí)現(xiàn)方案以及react-form-simple的使用,構(gòu)建可維護(hù)的代碼。以及在新技術(shù)領(lǐng)域保持學(xué)習(xí)的動(dòng)力。通過這些話題,我們不僅僅是在談?wù)摯a本身,更是在談?wù)撘环N持續(xù)的挑戰(zhàn)與成長的過程。
在編碼的旅程中,我們常常會(huì)面臨新的問題,需要找到創(chuàng)新的解決方案。正是通過解決這些挑戰(zhàn),我們才能不斷提升自己的技能,并在技術(shù)的海洋中航行得更遠(yuǎn)。每一行代碼都是一次思考的結(jié)果,每一個(gè)問題都是一次成長的機(jī)會(huì)。
在未來的代碼之旅中,愿我們能夠保持對(duì)技術(shù)的熱愛,持續(xù)學(xué)習(xí),不斷挑戰(zhàn)自己。編碼不僅僅是一項(xiàng)技能,更是一場不斷演化的冒險(xiǎn)。感謝你閱讀本文,期待與你在下一篇文章中再次相遇。
以上就是react表單受控的實(shí)現(xiàn)方案的詳細(xì)內(nèi)容,更多關(guān)于react表單受控的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- React如何利用Antd的Form組件實(shí)現(xiàn)表單功能詳解
- react使用antd的上傳組件實(shí)現(xiàn)文件表單一起提交功能(完整代碼)
- React表單容器的通用解決方案
- react?表單數(shù)據(jù)形式配置化設(shè)計(jì)
- react實(shí)現(xiàn)動(dòng)態(tài)表單
- React事件處理和表單的綁定詳解
- React?Hook?Form?優(yōu)雅處理表單使用指南
- React實(shí)現(xiàn)表單提交防抖功能的示例代碼
- React中重新實(shí)現(xiàn)強(qiáng)制實(shí)施表單的流程步驟
- react實(shí)現(xiàn)動(dòng)態(tài)增減表單項(xiàng)的示例代碼
- React 實(shí)現(xiàn)表單組件的示例代碼
相關(guān)文章
詳解React Native 采用Fetch方式發(fā)送跨域POST請(qǐng)求
這篇文章主要介紹了詳解React Native 采用Fetch方式發(fā)送跨域POST請(qǐng)求,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11關(guān)于?React?中?useEffect?使用問題淺談
本文主要介紹了關(guān)于React中useEffect使用問題淺談,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06React中常見的TypeScript定義實(shí)戰(zhàn)教程
這篇文章主要介紹了React中常見的TypeScript定義實(shí)戰(zhàn),本文介紹了Fiber結(jié)構(gòu),F(xiàn)iber的生成過程,調(diào)和過程,以及 render 和 commit 兩大階段,需要的朋友可以參考下2022-10-10react中使用ant組件庫的modal彈窗報(bào)錯(cuò)問題及解決
這篇文章主要介紹了react中使用ant組件庫的modal彈窗報(bào)錯(cuò)問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03React函數(shù)式組件Hook中的useEffect函數(shù)的詳細(xì)解析
useEffect是react v16.8新引入的特性。我們可以把useEffect hook看作是componentDidMount、componentDidUpdate、componentWillUnmounrt三個(gè)函數(shù)的組合2022-10-10詳解React??App.js?文件的結(jié)構(gòu)和作用
在React應(yīng)用中,App.js文件通常是項(xiàng)目的根組件文件,它負(fù)責(zé)組織和渲染其他組件,是應(yīng)用的核心部分,本文將詳細(xì)介紹App.js文件的結(jié)構(gòu)、作用和最佳實(shí)踐,感興趣的朋友跟隨小編一起看看吧2024-08-08