摘要:支持回調(diào)函數(shù),當(dāng)內(nèi)的表單輸入發(fā)生變化時(shí)可以即時(shí)通知其父組件。這是第一個(gè)問題,為了解決這個(gè)問題呢,需要對它的提兩個(gè)條件屬性上必須要接收一個(gè)回調(diào)函數(shù),函數(shù)接收一個(gè)對象參數(shù),參數(shù)結(jié)構(gòu)如下的值為當(dāng)前項(xiàng)產(chǎn)出的數(shù)據(jù),可能是個(gè)對象也可能是字符串或者數(shù)值。
前言
最近在寫一個(gè)面向 React 初學(xué)者的系列教程玩轉(zhuǎn) React,內(nèi)容對有 React 開發(fā)經(jīng)驗(yàn)的同學(xué)來說可能太過于基礎(chǔ)和啰嗦,不太感興趣。所以我打算同時(shí)開始另外一個(gè)系列文章《React 開發(fā)實(shí)戰(zhàn)》。該系列主要面向有 React 開發(fā)經(jīng)驗(yàn)的同學(xué),更側(cè)重 React 實(shí)戰(zhàn),每一篇文章會跟大家一起開發(fā)一個(gè) React 組件或者一個(gè)簡單有趣的 React 應(yīng)用,這些組件或者應(yīng)用往往滿足如下特點(diǎn):
在我的實(shí)際項(xiàng)目中用到過的。
在常見的開源組件庫中沒有的。
有點(diǎn)小眾,但是在特定的業(yè)務(wù)場景下能很大地提高項(xiàng)目的開發(fā)效率。
可能還比較有趣。
如果這些組件能直接應(yīng)用到大家的實(shí)際開發(fā)中去,那再好不過了;如果不能,能給大家一點(diǎn)啟發(fā),我覺得這件事情也是很有價(jià)值的。
另外,每一篇文章后面都會附有本篇文章的完整示例和代碼。
問題描述大家應(yīng)該都見過這種應(yīng)用場景,頁面上的某一部分,需要能夠讓用戶添加任意多項(xiàng)。
可能是表單中的一個(gè)字段,如下所示。
也可能是表單的一部分,如下所示,用戶可以在一個(gè)表單內(nèi)增加多個(gè)用戶信息,然后將用戶信息批量進(jìn)行保存。
還有更{{BANNED}}的,如下所示,一個(gè)表單內(nèi)用戶信息部分可以添加多份,每一個(gè)用戶信息中地址也可以添加多份。(Oh, My God. PM,你殺了我吧。)
還好,React 應(yīng)付這種需求,還是小菜一碟。但是在一個(gè) web 應(yīng)用中有這么多的相似場景的話,如果我們挨個(gè)實(shí)現(xiàn)一遍,那真是太枯燥了,與搬磚無異。遇到這種情況,就需要我們把相同的功能抽象出來,做成組件,這將極大地提升你的開發(fā)效率。
基于這個(gè)場景,我們今天就開發(fā)一個(gè)能讓其 children 重復(fù)任意多份的組件,我們就稱之為 Repeat 吧。
你期望 Repeat 組件該怎么用在開發(fā)一個(gè)組件的時(shí)候,不要著急寫代碼,先想想你要把這個(gè)組件做成什么樣子,例如這個(gè) Repeat 組件,我希望有如下特性:
Repeat 組件提供默認(rèn)的,添加、移除按鈕。
點(diǎn)擊添加,將 React 的 children 復(fù)制一份,點(diǎn)擊移除將某一項(xiàng)移除。
當(dāng)只有一項(xiàng)時(shí)不能移除。
Repeat 支持 onChange 回調(diào)函數(shù),當(dāng) Repeat 內(nèi)的表單輸入發(fā)生變化時(shí)可以即時(shí)通知其父組件。
然后在代碼中我期望可以這樣來用 Repeat 這個(gè)組件:
class App extends React.Component { handleChange(items) { console.info(items); } render() {this.handleChange(items)}> } }
OK,就是這么簡單,這樣 Input 組件就可以重復(fù)加添多份了?;谶@個(gè)構(gòu)想,我們來實(shí)現(xiàn) Repeat 這個(gè)組件。
開始實(shí)現(xiàn) Repeat 組件class Repeat extends React.Component { constructor(props) { super(props); this.state = { items: [""], }; } handleChange(e, index) { const items = [...this.state.items]; items[index] = e.target.value; this.setState({ items }); this.props.onChange(items); } handleAddItem(e, index) { e.preventDefault(); const items = [...this.state.items]; items.splice(index, 0, ""); this.setState({ items }); } handleRemoveItem(e, index) { e.preventDefault(); if (this.state.items.length === 1) return; const items = [...this.state.items]; items.splice(index, 1); this.setState({ items }); } render() { const children = React.Children.only(this.props.children); const elementItems = this.state.items.map((item, index) => ({ React.cloneElement(children, { onChange: e => this.handleChange(e, index), value: item, }) })); return{elementItems}; } }
代碼很簡單,簡單解釋一下:
組件的 state 中持有 items 字段來保存每一個(gè)項(xiàng)的數(shù)據(jù)。
render 時(shí)先獲取到唯一的 children,然后 map 組件 state 中的 items,將每一項(xiàng)映射為 children 的一個(gè)副本。并為這個(gè)副本傳入兩個(gè)屬性,onChange 接收每一項(xiàng)的數(shù)據(jù)變化,value 傳遞每一項(xiàng)當(dāng)前應(yīng)展示的值。
另外 Repeat 為每一項(xiàng)準(zhǔn)備了一個(gè)“添加”按鈕和一個(gè)“移除”按鈕,用來在當(dāng)前項(xiàng)位置新增一項(xiàng)或者移除當(dāng)前項(xiàng)。原理就是將 this.state.items 中對應(yīng)下標(biāo)處的數(shù)組元素刪掉就好了。
到此,Repeat 是不是大致有模有樣了呢。需要提醒大家的是,React.cloneElement 和 React.Children.xxx 這些 api 通常只會在這種公共組件中使用,在大部分場景,盡量少用。
跟 children 有個(gè)約定有些同學(xué)可能已經(jīng)發(fā)現(xiàn)了,上面例子中, Repeat 的 children 是個(gè) input,那如果是一個(gè)其他的組件不就完蛋了嘛。
這是第一個(gè)問題,為了解決這個(gè)問題呢,Repeat 需要對它的 children 提兩個(gè)條件:
屬性上必須要接收一個(gè) onChange 回調(diào)函數(shù),函數(shù)接收一個(gè)對象參數(shù),參數(shù)結(jié)構(gòu)如下:
{ target: { value: "xxxx" } }
value 的值為當(dāng)前項(xiàng)產(chǎn)出的數(shù)據(jù),可能是個(gè)對象也可能是字符串或者數(shù)值。沒錯(cuò),我就是為了兼容 input event 的數(shù)據(jù)結(jié)構(gòu)。你當(dāng)然可以用任何你喜歡的且方便處理的數(shù)據(jù)結(jié)構(gòu)。
children 組件需要接收一個(gè) value 屬性,以展示其擁有的值。也就是說 children 組件應(yīng)當(dāng)是一個(gè)受控的(controlled)組件。
這就是一個(gè)協(xié)議,你希望某個(gè)組件內(nèi)通過 Repeat 組件方便地添加多份并能獲取到一組數(shù)據(jù),那就必須要遵守這個(gè)協(xié)議。有同學(xué)可能會說為什么不搞的智能一點(diǎn)呢?嗯,這里我想分享一點(diǎn)個(gè)人經(jīng)驗(yàn):有些時(shí)候,尤其是在業(yè)務(wù)開發(fā)過程中,把公共部分抽取出來復(fù)用即可,點(diǎn)到為止,沒有必要搞得那么“強(qiáng)大”,剩下的事情讓一個(gè)很容易遵守的協(xié)議來完成,其實(shí)效率會更高,更容易讓人理解。
其實(shí)在計(jì)算機(jī)的世界中處處充滿了協(xié)議,例如你想讓 HTTP Server 返回正確的響應(yīng),你必須要遵循 http 協(xié)議來和它通信;你生產(chǎn)的顯卡能買的出去,必須要遵守相應(yīng)的協(xié)議,要能插到別人家生產(chǎn)的主板上。
扯遠(yuǎn)了!收!
對,有了上面這個(gè)約定以后,Repeat 一行代碼未加,是不是感覺功能完善了許多?嗯,就是這個(gè)目的?,F(xiàn)在我們來實(shí)現(xiàn)一下文章開始時(shí)候說的第二個(gè)場景。
聰明的你一定已經(jīng)知道該怎么做了,沒錯(cuò),只要我們實(shí)現(xiàn)一個(gè) UserForm 組件,并讓他滿足上面的約定即可。請看代碼:
class UserForm extends React.Component { handleFieldChange(e) { const { name, value } = e.target; const formData = { ...this.props.value, [name]: value, } this.props.onChange({ target: { value: formData, } }); } render() { const formData = this.props.value || {}; return () } }this.handleFieldChange(e)} />this.handleFieldChange(e)} />
為了讓代碼更簡潔,我把 UserForm 這個(gè)組件實(shí)現(xiàn)為了一個(gè)支持受控的組件,但是在目前的業(yè)務(wù)場景下已經(jīng)足夠了,在實(shí)際情況下,你可以按需調(diào)整。
通過這個(gè)例子,還希望大家能體會到組件拆分的一個(gè)好處。就是,UserForm 和 Repeat 拆分成兩個(gè)組件以后,UserForm 的復(fù)用性會更強(qiáng)??梢韵胂笠幌?,當(dāng)用戶被批量添加以后,是不是有可能在編輯單個(gè)用戶的時(shí)候,可以繼續(xù)使用這個(gè)組件。
好啦,關(guān)于第三個(gè)場景我想就沒有必要再實(shí)現(xiàn)一遍了,Repeat 嵌套多少層其實(shí)都是可以的。
更進(jìn)一步實(shí)際上在實(shí)際應(yīng)用中,Repeat 這個(gè)組件還需要做進(jìn)一步完善,其中一個(gè)就是樣式,還有可能在不同的場景下,雖然交互都是這樣,但樣式會有所差異。另外默認(rèn)是“添加”、“移除”兩個(gè)文字按鈕,說不定實(shí)際業(yè)務(wù)場景中是兩個(gè) +,- 的圖標(biāo)按鈕;還有可能“添加”、“移除”的位置為有所變化。
這些問題怎么處理呢?下面給大家描述下思路,具體代碼就不寫了,如果有什么疑問可以給我留言。
關(guān)于樣式,你可以給 Repeat 添加 itemClassName 和 buttonsClassName 兩個(gè)屬性分別為每一項(xiàng)和按鈕區(qū)域的 css class。這樣你就可以在不同的場景下指定不同的樣式了。
關(guān)于如何將文字按鈕改為圖標(biāo)按鈕,你可以給 Repeat 添加 renderButtons 這樣一個(gè)函數(shù)屬性,如果未指定則用默認(rèn)的方式渲染按鈕,如果有則勇氣返回值渲染屬性。
最后這是本篇文章的代碼:https://codepen.io/Sarike/pen...
好啦,文章就到這吧,如果有什么疑問可以給我留言。謝謝大家,祝大家國慶、中秋節(jié)快樂。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/51330.html
摘要:支持回調(diào)函數(shù),當(dāng)內(nèi)的表單輸入發(fā)生變化時(shí)可以即時(shí)通知其父組件。這是第一個(gè)問題,為了解決這個(gè)問題呢,需要對它的提兩個(gè)條件屬性上必須要接收一個(gè)回調(diào)函數(shù),函數(shù)接收一個(gè)對象參數(shù),參數(shù)結(jié)構(gòu)如下的值為當(dāng)前項(xiàng)產(chǎn)出的數(shù)據(jù),可能是個(gè)對象也可能是字符串或者數(shù)值。 前言 最近在寫一個(gè)面向 React 初學(xué)者的系列教程玩轉(zhuǎn) React,內(nèi)容對有 React 開發(fā)經(jīng)驗(yàn)的同學(xué)來說可能太過于基礎(chǔ)和啰嗦,不太感興趣。所...
摘要:前端日報(bào)精選劉海打理指北中的錯(cuò)誤處理模式與反模式譯圖解和譯你并不知道中文裝飾器讓你的代碼更簡潔眾成翻譯第期每個(gè)程序員第一份工作前應(yīng)該知道的件事中的不變性眾成翻譯寫的一次小結(jié)掘金內(nèi)部機(jī)制探秘和文末附彩蛋和源碼前端雜談開發(fā)實(shí)戰(zhàn) 2017-09-30 前端日報(bào) 精選 iPhone X 劉海打理指北React16中的錯(cuò)誤處理ES6 Promise:模式與反模式「譯」圖解 ArrayBuffer...
摘要:組件將元素作為結(jié)果返回。是把數(shù)據(jù)從項(xiàng)目傳到的有效載荷。有以下職責(zé)維持應(yīng)用的提供方法獲取提供方法更新通過注冊監(jiān)聽器通過返回的函數(shù)注銷監(jiān)聽器。系列目錄前端大統(tǒng)一時(shí)代即將來臨項(xiàng)目實(shí)戰(zhàn)環(huán)境搭建項(xiàng)目實(shí)戰(zhàn)基本原理項(xiàng)目實(shí)戰(zhàn)登錄頁面編輯中 React相關(guān) React 是一個(gè)采用聲明式,高效而且靈活的用來構(gòu)建用戶界面的框架。 JSX 本質(zhì)上來講,JSX 只是為React.createElement(co...
閱讀 1057·2021-11-25 09:43
閱讀 1426·2021-11-18 10:02
閱讀 1869·2021-11-02 14:41
閱讀 2381·2019-08-30 15:55
閱讀 1080·2019-08-29 16:18
閱讀 2564·2019-08-29 14:15
閱讀 1400·2019-08-26 18:13
閱讀 746·2019-08-26 10:27