react antd-mobile ActionSheet+tag實現(xiàn)多選方式
實現(xiàn)效果如下
在手機(jī)下方彈出彈窗
選擇月份點確定后
再次點擊申請月份,之前已選的月份會自動選上
全部功能描述
1、點擊輸入框,自動彈出選擇彈窗(使用actionsheet,這部分不過多闡釋)
2、彈窗可多選的內(nèi)容由后臺接口傳入(或自己寫數(shù)據(jù),看需求)
3、灰色部分為后臺返回的不可選擇的標(biāo)簽,不允許用戶點擊選擇(disabled)
4、選擇一個或多個標(biāo)簽后,點擊確定,保存選擇的標(biāo)簽,并在輸入框中顯示選擇的內(nèi)容。倘若沒有點擊確定,直接點取消或彈框外的區(qū)域關(guān)閉彈窗,則不會保存所選標(biāo)簽
4、再次點擊輸入框,彈窗會自動勾選上之前選過的彈窗。之后操作可如上循環(huán)。
功能實現(xiàn)
一、從后臺獲取所有月份信息
這里就是普通的通過redux的網(wǎng)絡(luò)請求獲取數(shù)據(jù),按實際情況獲取數(shù)據(jù)
getMouth = () => { const {dispatch} = this.props; dispatch({ type: `${NAMESPACE}/getMouth`, }); };
不允許點擊的月份
const disabledMouth = [2,5,8];
按理說,不允許點擊的月份也應(yīng)該通過后臺獲取,此處因為還沒拿到后臺接口,暫時用全局變量充當(dāng)不允許點擊的月份數(shù)組
二、添加組件的state數(shù)據(jù)
this.state = { checkMouth: [],//保存已選擇的月份 allMouth: [],//保存月份標(biāo)簽的屬性數(shù)據(jù) temCheckMouth: [],//臨時保存已選擇的魚粉 temAllMouth: [],//臨時保存月份標(biāo)簽的屬性數(shù)據(jù) sign: false//判斷用戶是否點擊確定 }
三、定義含有標(biāo)簽屬性的數(shù)組allMouth
getAllMouth = () => { //mouth即為第一步通過后臺拿到的model里state的月份值 const mouth = this.props.mouth.data || ''; const newMonth = []; if (mouth && mouth.length > 0) { mouth.map(item => { newMonth.push( { value: item.code,//可理解為每個月份的id label: item.detail,//月份標(biāo)簽需展示的內(nèi)容,如一月、二月等的字樣 selected: false,//月份標(biāo)簽是否被選擇 disabled: false//該月份是否不可點擊 } ) }); } // 修改無法選擇的月份信息,把無法選擇的月份disabled置灰掉,不允許用戶點擊選擇 disabledMouth.map(item => { newMonth[item].disabled = true; }); // console.log(newMonth); // 把每個月份的屬性數(shù)據(jù)數(shù)組存入state this.setState({ allMouth: newMonth, temAllMouth: newMonth }) };
注:
一般后臺傳的數(shù)據(jù)只會有code和detail或其他內(nèi)容,不會有selected和disabled,所以此處我們要在數(shù)組手動添加,給每個月份自己獨有的selected值和disabled值。
在最初默認(rèn)都是未選中和允許點擊狀態(tài)。
四、保存月份屬性數(shù)組allMouth
componentDidMount() { let timer; new Promise(resolve => { this.getMouth(); timer = setInterval(() => { if (this.props.mouth.data) { resolve(); } }, 100); }).then(() => { this.getAllMouth(); clearInterval(timer); }); }
這部分代碼其實我考慮了很久,因為這里有一個前提條件
必須通過getMouth()后臺請求到數(shù)據(jù)之后,getAllMouth()才能訪問數(shù)據(jù),初始化月份數(shù)組數(shù)據(jù)并放到state
但是在這一步我報錯了很久,因為倘若普通寫法(如下)
this.getMouth();× this.getAllMouth();×
這一步雖然顯示網(wǎng)絡(luò)請求成功了,但是props.mouth并沒有拿到數(shù)據(jù)!
這一步該如何寫思考了很久,直接簡單實用promise也不行
componentDidMount() { × new Promise(resolve => { × this.getMouth(); × resolve(); × }).then(() => { × this.getAllMouth(); × }); × } ×
此處getMouth()方法的確是執(zhí)行完了,只是還沒拿到數(shù)據(jù),所以這樣子寫是不行的
而且componentDidMount正常來說只會執(zhí)行一次,所以在componentDidMount執(zhí)行完,而model的state還沒拿到數(shù)據(jù)的時候,盡管過了一段時間拿到數(shù)據(jù)了this.getAllMouth(); 也不會再次執(zhí)行了,所以初始化無法進(jìn)行。
思前想后,能解決這一步的只能多次執(zhí)行,確保拿到數(shù)據(jù)了,再進(jìn)行this.getAllMouth();初始化數(shù)據(jù)。便有了上方代碼的操作。
當(dāng)然,開了定時器記得用完就關(guān)!這一步操作可能性能上不太友好,如果大家有什么更好的方法,請多多指教呀~
五、遍歷顯示月份
上面步驟成功后,可以看到我們初始化的月份數(shù)組allMouth是這樣的,接下來就可以讓他們展示出來了
(注:0號全選是后臺一同傳過來的,大家可忽略)
input框:
<div onClick={this.handleMouth}> <InputItem placeholder="請選擇申請月份" readonly="readonly" clear > 申請月份 </InputItem> </div>
點擊后彈出彈窗
主要是使用ActionSheet彈出彈窗顯示內(nèi)容,不多闡述。詳情可見antd-mobile文檔
handleMouth = () => { const {allMouth} = this.state; //這一步是為了刪去數(shù)組第一條全選數(shù)據(jù),全選不需要顯示。可忽略 const newMouthData = allMouth && allMouth.slice(1); const modalData = ( <div className={styles.modalStyle}> <div className={styles['tag-body']}> <div style={{textAlign: 'left'}}>可選擇月份(可多選)</div> <div className={styles['tag-box']}> { newMouthData && newMouthData.map((item) => { return ( <Tag data-seed="logId" onChange={(selected) => this.handleOnTagChange(selected, item.value)} // 用戶點擊不同月份時觸發(fā)的方法(詳情見下方標(biāo)題六) selected={item.selected}// 控制不同月份的選中狀態(tài) disabled={item.disabled}// 控制不同月份是否可點擊 > {item.label} </Tag> ) }) } </div> </div> </div> ) const button1 = ( <div className={styles.buttonStyle}> <span>取消</span> <span onClick={this.handleConfirm}>確定</span>//詳情請見標(biāo)題七的內(nèi)容 </div> ) const BUTTONS = [modalData]; ActionSheet.showActionSheetWithOptions({ options: [button1], message: BUTTONS, }, // 關(guān)閉頁面執(zhí)行的方法 (buttonIndex) => { this.closePage();//詳情請見標(biāo)題八的內(nèi)容 }); };
六、選擇不同月份觸發(fā)事件
handleOnTagChange = (selected, value) => { /** * 用戶在彈窗選擇月份時,雖然當(dāng)前處于選中狀態(tài)。 * 但是倘若用戶選擇完畢時不點擊確定,而且直接關(guān)閉頁面 * 則之前選中的月份都不應(yīng)該保存!所以此處用臨時數(shù)組進(jìn)行保存 * 只有用戶點擊確定,才把選擇的所有內(nèi)容保存到正式的數(shù)組中 */ const {temAllMouth, temCheckMouth} = this.state; // 修改月份對應(yīng)的selected屬性 const newAllMouth = JSON.parse(JSON.stringify(temAllMouth)); newAllMouth[value].selected = selected; // console.log(newAllMouth); // 記錄選中的月份 const newCheckMouth = JSON.parse(JSON.stringify(temCheckMouth)); if (selected) {//如果選中,把選中的月份添加進(jìn)數(shù)組 newCheckMouth.push(parseInt(value)); } else {//如果是取消選中,需要把之前的選中從數(shù)組中刪除 const deleteMouth = newCheckMouth.findIndex(item => item == value); newCheckMouth.splice(deleteMouth, 1); } // console.log(newCheckMouth); this.setState({ temCheckMouth: newCheckMouth, temAllMouth: newAllMouth }); };
關(guān)于const newAllMouth = JSON.parse(JSON.stringify(temAllMouth))的解釋:
這一步其實我繞了很多彎路,最后一步步排除錯誤才發(fā)現(xiàn)我犯了個超級低級的錯誤!
因為這個方法中,我們很明顯需要修改數(shù)組,但是我們不能直接修改state的數(shù)據(jù)
所以必須在方法中深拷貝state的數(shù)組,對新數(shù)組進(jìn)行修改,最后再通過setstate把新數(shù)組賦值給state的數(shù)組。
而在寫代碼最初,我是直接
const {temAllMouth, temCheckMouth} = this.state; const newAllMouth = temAllMouth;×××××錯誤范例?。?!
很明顯這樣寫是錯誤的!直接賦值是把temAllMouth的地址賦值給了newAllMouth ,兩者其實沒有區(qū)別,修改newAllMouth 也會修改到state的數(shù)據(jù)temAllMouth,故出現(xiàn)問題。
所以最后直接把temAllMouth通過JSON深拷貝一份,保證newAllMouth與temAllMouth不相同,互不干擾。
七、點擊確定觸發(fā)實現(xiàn)
handleConfirm = () => { const {temCheckMouth, temAllMouth} = this.state; /** * 對選中月份進(jìn)行排序 * 因為關(guān)閉彈窗后需要在input框中顯示用戶選擇的月份 * 而用戶選擇的順序是不定,所以temCheckMouth保存的選中月份也是亂的 * 故此處為了美觀,對選中月份進(jìn)行了排序 * 使用的是最簡單的冒泡排序,可以忽略 */ const newCheckMouth = JSON.parse(JSON.stringify(temCheckMouth)); if (newCheckMouth && newCheckMouth.length > 1){ newCheckMouth.map((item,index) => { for (let j = 0; j<newCheckMouth.length-index;j+=1){ if (newCheckMouth[j] > newCheckMouth[j + 1]){ const temp = newCheckMouth[j]; newCheckMouth[j] = newCheckMouth[j + 1]; newCheckMouth[j + 1] = temp; } } }); } console.log('選中的月份--' + newCheckMouth); // 此時用戶點擊確定按鈕,需要把之前保存的臨時數(shù)據(jù)賦值給正式的數(shù)組 this.setState({ allMouth: temAllMouth, checkMouth: newCheckMouth, temCheckMouth: newCheckMouth, sign: true //確定頁面 }); // 把選中的月份展示在input框,只有用戶有選擇月份的情況才需要展示 if (temCheckMouth){ this.props.form.setFieldsValue({ sqzzjtyf: newCheckMouth.toString() }); } }
八、關(guān)閉彈窗觸發(fā)事件
注:這里關(guān)閉彈窗并不是點擊取消或者點擊彈窗外的手機(jī)部分關(guān)閉彈窗時才會觸發(fā)?。?/p>
點擊確定之后關(guān)閉彈窗同樣會觸發(fā)?。。。]注意這部分就會出現(xiàn)錯誤!
closePage = () => { setTimeout(()=>{ const {checkMouth, allMouth, sure} = this.state; // sure=false代表用戶沒有點擊確定,則需要把之前保存的臨時數(shù)據(jù)重置,保證用戶下次打開彈窗時數(shù)據(jù)的正確性 if(!sure){ this.setState({ temCheckMouth: checkMouth, temAllMouth: allMouth }); }else{ // 把點擊確定時修改的sure=true修改回false,保證下次再次打開彈窗的正確性 this.setState({ sure: false }); } },50) }
此處使用settimeout的原因:
上面也說了,這里調(diào)用的方法是只要關(guān)閉了彈窗就會執(zhí)行,而該方法的執(zhí)行順序與用戶點擊確定的執(zhí)行順序應(yīng)該是不相上下的(此處未做深入研究,不確定兩者的執(zhí)行順序)
而這里我們有一個必須前提,關(guān)閉彈窗的這個方法必須在點擊確定的方法后面執(zhí)行,原因如下:
在點擊確定方法后,我們會設(shè)置sure=true,并且把checkMouth和allMouth設(shè)置為新的值
而倘若關(guān)閉彈窗方法沒有在確定方法后執(zhí)行,則關(guān)閉彈窗方法執(zhí)行的時候,sure值仍為false!并且checkMouth和allMouth也扔為舊值
這樣,該方法就會執(zhí)行
temCheckMouth: checkMouth(舊值), temAllMouth: allMouth(舊值)
如此,用戶之前點擊確定應(yīng)該要保存的數(shù)據(jù)就沒有保存成功了!錯誤就出現(xiàn)了!
故此處使用settimeout保證該方法執(zhí)行的延后!
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
react native基于FlatList下拉刷新上拉加載實現(xiàn)代碼示例
這篇文章主要介紹了react native基于FlatList下拉刷新上拉加載實現(xiàn)代碼示例2018-09-09React Native第三方平臺分享的實例(Android,IOS雙平臺)
本篇文章主要介紹了React Native第三方平臺分享的實例(Android,IOS雙平臺),具有一定的參考價值,有興趣的可以了解一下2017-08-08React高級指引之Refs and the DOM使用時機(jī)詳解
在典型的React數(shù)據(jù)流中,props是父組件與子組件交互的唯一方式。要修改一個子組件,你需要使用新的props來重新渲染它。但是,在某些情況下,你需要在典型數(shù)據(jù)流之外強(qiáng)制修改子組件2023-02-02React利用路由實現(xiàn)登錄界面的跳轉(zhuǎn)
這篇文章主要介紹了React利用路由實現(xiàn)登錄界面的跳轉(zhuǎn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04react實現(xiàn)同頁面三級跳轉(zhuǎn)路由布局
這篇文章主要為大家詳細(xì)介紹了react實現(xiàn)同頁面三級跳轉(zhuǎn)路由布局,一個路由小案例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-09-09