React組件設(shè)計(jì)過(guò)程之仿抖音訂單組件
前言
作為數(shù)據(jù)驅(qū)動(dòng)的領(lǐng)導(dǎo)者react/vue等MVVM框架的出現(xiàn),幫我們減少了工作中大量的冗余代碼, 一切皆組件的思想深得人心。組件就是對(duì)一些具有相同業(yè)務(wù)場(chǎng)景和交互模式代碼的抽象,這就需要我們對(duì)組件進(jìn)行規(guī)范的封裝,掌握高質(zhì)量組件設(shè)計(jì)的思路和方法可以幫助我們提高日常的開發(fā)效率。筆者將會(huì)通過(guò)實(shí)戰(zhàn)抖音訂單組件詳細(xì)的介紹組件的設(shè)計(jì)思路和方法,對(duì)新手特別友好,希望對(duì)前端新手們和有一定工作經(jīng)驗(yàn)的朋友有一定幫助~
前期準(zhǔn)備
在組件設(shè)計(jì)之前,希望你對(duì)css、js具有一定的基礎(chǔ)。在我們的組件設(shè)計(jì)時(shí)需要用到的開源組件庫(kù)有:
(有不了解的小伙伴可以自行查閱資料學(xué)習(xí)一下,在后面用到的時(shí)候我也會(huì)說(shuō)明的)
axios
它是一個(gè)基于 promise 的網(wǎng)絡(luò)請(qǐng)求庫(kù),用于獲取后端數(shù)據(jù),是前端常用的數(shù)據(jù)請(qǐng)求工具;
react-weui
、weui
weui 是微信官方制作的一個(gè)基礎(chǔ)樣式UI庫(kù),我們可以通過(guò)閱讀官方文檔直接使用里面的樣式,而 react-weui 就是將這些樣式封裝成我們可以直接使用的組件;
styled-components
稱之為css in js,現(xiàn)在正在成為在 React 中設(shè)計(jì)組件樣式的新方法。
另外,我們還用到在線接口工具 faskmock
模擬ajax請(qǐng)求。它更加真實(shí)的模擬了前端開發(fā)中后端提供數(shù)據(jù)的方式。
實(shí)現(xiàn)后的組件效果
在這我們先來(lái)看看組件實(shí)現(xiàn)后的組件效果:
1. 組件設(shè)計(jì)思路
在這個(gè)組件中我們需要實(shí)現(xiàn)的業(yè)務(wù)有:
(目前我們就暫時(shí)實(shí)現(xiàn)以下效果,該頁(yè)面的其他功能筆者將會(huì)在后期慢慢完善~)
- tab切換:點(diǎn)擊
tab
,該tab
添加上紅色下劃線樣式,并將該tab
狀態(tài)下的訂單展示在下方。 - 設(shè)置loading狀態(tài):在數(shù)據(jù)還在請(qǐng)求中時(shí),顯示
loading
圖標(biāo) - 搜索訂單:在當(dāng)前
tab
下搜索商品標(biāo)題含有輸入內(nèi)容的訂單。 - 刪除訂單:刪除指定訂單,由于數(shù)據(jù)是在
fastmock
中請(qǐng)求得到,因此刪除只相對(duì)于前端。 - 實(shí)現(xiàn)Empty(空狀態(tài))組件當(dāng)當(dāng)前狀態(tài)下訂單數(shù)量為 0 時(shí),顯示該組件,否則顯示列表組件。
根據(jù)我們的需求,可以劃分出5個(gè)組件模塊組成整個(gè)頁(yè)面:
- 頁(yè)面級(jí)別組件
<Myorder/>
,它是其他組件的父組件; - 顯示數(shù)據(jù)列表組件
<OrderList/>
,單個(gè)數(shù)據(jù)組件<OrderNote/>
; - 空狀態(tài)組件
<EmptyItem/>
; - 推薦商品列表組件
<RecommendList/>
。 - 在
<Myoeder/>
組件中請(qǐng)求數(shù)據(jù),將對(duì)應(yīng)的數(shù)組數(shù)據(jù)通過(guò)props
傳給<OrderList/>
組件和<RecommendList/>
組件;<OrderList/>
組件再將單個(gè)數(shù)據(jù)傳給<OrderNote/>
組件。這樣就規(guī)范的完成了父組件請(qǐng)求數(shù)據(jù),子組件搭建樣式的分工合作了。
分析完組件組成接下來(lái)完成組件目錄的搭建:
2. 實(shí)現(xiàn) Myorder 組件
首先我們先根據(jù)需求將組件框架寫好,這樣后面寫業(yè)務(wù)邏輯會(huì)更清晰:
這個(gè)頁(yè)面級(jí)別組件包括固定在頂部的搜索框+導(dǎo)航欄,以及OrderList
和RecommendList
組件,因此可以寫出如下組件框架:
import React from 'react' import OrderList from '../OrderList' import RecommendList from '../RecommendList' import { OrderWrapper } from './style' import fanhui from '../../assets/images/fanhui.svg' import gengduo from '../../assets/images/gengduo.svg' import sousuo from '../../assets/images/sousuo.svg' export default function Myorder() { return ( <OrderWrapper> // 搜索 + 導(dǎo)航欄 部分 <div className="head"> <div className="searchOrder"> <img src={fanhui} alt="返回"/> <div className='searchgroup'> <input placeholder="搜索訂單" /> <img className="searchimg" src={sousuo} alt="搜索"/> </div> <img src={gengduo} alt="更多"/> </div> <ul> <li>全部</li> <li>待支付</li> <li>待發(fā)貨</li> <li>待收貨/使用</li> <li>評(píng)價(jià)</li> <li>退款</li> </ul> </div> // 訂單列表組件 <OrderList/> // 推薦列表組件 <RecommendList/> </OrderWrapper> ) }
有了這個(gè)框架,我們來(lái)一步步往里面實(shí)現(xiàn)內(nèi)容吧。
2.1 實(shí)現(xiàn)tab切換效果
首先來(lái)完成第一個(gè)需求:當(dāng)點(diǎn)擊某個(gè)tab
時(shí),如'待支付',這個(gè)tab
要有紅色下劃線效果。實(shí)現(xiàn)原理其實(shí)很簡(jiǎn)單,就是當(dāng)我們觸發(fā)該tab
的點(diǎn)擊事件時(shí),就將我們事先寫好的active
樣式加到該tab
上。
這里有兩種方案:
- 第一種實(shí)現(xiàn)方法是定義一個(gè)狀態(tài)
tab
來(lái)控制每個(gè)<li>
的className
的內(nèi)容:
import React,{ useState} from 'react' import { OrderWrapper } from './style' export default function Myorder() { const [tab,setTab] = useState('全部'); const changeTab= (target) => { setTab(target); } return ( <OrderWrapper> ... <ul> <li className={tab=='全部'?'active':''} onClick={changeTab.bind(null,'全部')}>全部</li> <li className={tab=='待支付'?'active':''} onClick={changeTab.bind(null,'待支付')}>待支付</li> <li className={tab=='待發(fā)貨'?'active':''} onClick={changeTab.bind(null,'待發(fā)貨')}>待發(fā)貨</li> <li className={tab=='待收貨/使用'?'active':''} onClick={changeTab.bind(null,'待收貨/使用')}>待收貨/使用</li> <li className={tab=='評(píng)價(jià)'?'active':''} onClick={changeTab.bind(null,'評(píng)價(jià)')}>評(píng)價(jià)</li> <li className={tab=='退款'?'active':''} onClick={changeTab.bind(null,'退款')}>退款</li> </ul> ... </OrderWrapper> ) }
這種方法有一個(gè)明顯的缺點(diǎn),就是只能為其添加一個(gè)樣式名,當(dāng)有多個(gè)樣式類名時(shí),就會(huì)出問(wèn)題了,因此可以采用第二種方法。
- 第二種方法就是用
classnames
了,也是比較推薦的方法,寫法也比較簡(jiǎn)單。
import classnames from 'classnames' import { OrderWrapper } from './style' export default function Myorder() { const [tab,setTab] = useState('全部'); const changeTab= (target) => { setTab(target); } return ( <OrderWrapper> ... <ul> <li className={classnames({active:tab==="全部"})} onClick={changeTab.bind(null,'全部')}>全部</li> <li className={classnames({active:tab==="待支付"})} onClick={changeTab.bind(null,'待支付')}>待支付</li> <li className={classnames({active:tab==="待發(fā)貨"})} onClick={changeTab.bind(null,'待發(fā)貨')}>待發(fā)貨</li> <li className={classnames({active:tab==="待收貨/使用"})} onClick={changeTab.bind(null,'待收貨/使用')}>待收貨/使用</li> <li className={classnames({active:tab==="評(píng)價(jià)"})} onClick={changeTab.bind(null,'評(píng)價(jià)')}>評(píng)價(jià)</li> <li className={classnames({active:tab==="退款"})} onClick={changeTab.bind(null,'退款')}>退款</li> </ul> ... </OrderWrapper> ) }
當(dāng)有多個(gè)類名時(shí),這樣添加:
<li className={classnames('test',{active:tab==="全部"})} onClick={changeTab.bind(null,'全部')}>全部</li>
實(shí)現(xiàn)效果如圖:
2.2 獲取數(shù)據(jù)
這里準(zhǔn)備了兩個(gè)接口,用于獲取訂單數(shù)據(jù)和推薦商品數(shù)據(jù)。
為了便于管理,我們將數(shù)據(jù)請(qǐng)求封裝在api文件中:
- 第一個(gè)接口獲取訂單數(shù)據(jù)。需要根據(jù)
tab
狀態(tài)篩選獲取的數(shù)據(jù),這一步我們也寫在接口文件中:
import axios from 'axios' // 請(qǐng)求訂單數(shù)據(jù) export const getOrder = ({tab}) => axios .get('https://www.fastmock.site/mock/759aba4bef0b02794e330cccc1c88555/beers/order') .then ( res => { let result=res.data; if(tab){ switch(tab) { case "待支付": result=result.filter(item => item.state=="待支付"); break; case "待發(fā)貨": result=result.filter(item => item.state=="待發(fā)貨"); break; case "待收貨/使用": result=result.filter(item => item.state=="待收貨/使用"); break; case "評(píng)價(jià)": result=result.filter(item => item.state=="評(píng)價(jià)"); break; case "退款": result=result.filter(item => item.state=="退款"); break; default: break; } } return Promise.resolve({ result }); } )
- 第二個(gè)接口獲取推薦商品數(shù)據(jù):
import axios from 'axios' // 請(qǐng)求推薦商品數(shù)據(jù) export const getCommend = () => axios.get('https://www.fastmock.site/mock/759aba4bef0b02794e330cccc1c88555/beers/goods')
接口準(zhǔn)備好了,接下來(lái)我們將數(shù)據(jù)分配給子組件,接下來(lái)數(shù)據(jù)如何在頁(yè)面上顯示的任務(wù)就交給子組件<OrderList/>
和<Recommend/>
完成
import React,{useEffect, useState} from 'react' import { OrderWrapper } from './style' import OrderList from './OrderList' import RecommendList from './RecommendList' export default function Myorder() { const [list,setList] =useState([]); const [recommend,setRecommend] = useState([]); // 從接口中獲取推薦商品數(shù)據(jù) useEffect(()=> { (async()=> { const {data} = await getCommend(); setRecommend([...data]); })() }) // 從接口中獲取訂單數(shù)據(jù),每次tab切換都重新拉取 useEffect(()=>{ (async()=>{ const {result} = await getOrder({tab}); setList([ ...result ]) })() },[tab]) return ( <OrderWrapper> ... {list.length>0 && <OrderList list={list}/>} {recommend.length>0 && <RecommendList recommend={recommend}/>} </OrderWrapper> ) }
2.3 實(shí)現(xiàn)搜索功能
搜索功能應(yīng)該在對(duì)應(yīng)的tab
下進(jìn)行,因此我們可以將輸入的內(nèi)容設(shè)置為一個(gè)狀態(tài)
,每次改變就根據(jù)tab
內(nèi)容和輸入內(nèi)容重新獲取數(shù)據(jù):
api接口對(duì)訂單數(shù)據(jù)的請(qǐng)求的封裝中增加一個(gè)query
限制:
export const getOrder = ({tab,query}) => axios .get('https://www.fastmock.site/mock/759aba4bef0b02794e330cccc1c88555/beers/order') .then ( res => { let result=res.data; if(tab){ switch(tab) { case "待支付": result=result.filter(item => item.state=="待支付"); break; case "待發(fā)貨": result=result.filter(item => item.state=="待發(fā)貨"); break; case "待收貨/使用": result=result.filter(item => item.state=="待收貨/使用"); break; case "評(píng)價(jià)": result=result.filter(item => item.state=="評(píng)價(jià)"); break; case "退款": result=result.filter(item => item.state=="退款"); break; default: break; } } if(query) { result = result.filter(item => item.title.includes(query)); } return Promise.resolve({ result }); } )
而在組件的實(shí)現(xiàn)上,由于頁(yè)面沒(méi)有添加點(diǎn)擊搜索的按鈕,如果將input
中的value
直接和query
狀態(tài)綁定的話,每次用戶輸入一個(gè)字就會(huì)進(jìn)行一次查詢,觸發(fā)太頻繁,性能不夠好,用戶體驗(yàn)也不好。
所以這里我的想法是每次輸入完按下enter
才進(jìn)行搜索
但是React中無(wú)法直接對(duì)input
的enter
事件進(jìn)行處理。于是我在網(wǎng)上查閱到兩種處理方式,第一種是通過(guò) e.nativeEvent
來(lái)獲取keyCode
判斷是否為 13 ,第二中方法是通過(guò)addEventListener
注冊(cè)事件來(lái)處理,要慎用。
這里采用第一種方法來(lái)實(shí)現(xiàn):
import React,{useState} from 'react' import { OrderWrapper } from './style' export default function Myorder() { const [query,setQuery] = useState(''); const handleEnterKey = (e) => { if(e.nativeEvent.keyCode === 13){ setQuery(e.target.value); } } return ( <OrderWrapper> ... <input placeholder="搜索訂單" onKeyPress={handleEnterKey} /> ... </div> </OrderWrapper> ) }
2.4 設(shè)置loading狀態(tài)
在數(shù)據(jù)請(qǐng)求過(guò)程之,頁(yè)面會(huì)空白,為了提升視覺(jué)上的效果,在這個(gè)時(shí)間段我們就設(shè)置一個(gè)loading
樣式,這個(gè)樣式組件我們直接使用reacct-weui
的Toast
組件。
我們?cè)黾右粋€(gè)loading
狀態(tài)來(lái)來(lái)控制Toast
的顯示。
import React,{useEffect, useState} from 'react' import { OrderWrapper } from './style' import WeUI from 'react-weui' const { Toast } = WeUI; export default function Myorder() { const [loading,setLoading]=useState(false); useEffect(()=>{ setLoading(true); (async()=>{ const {result} = await getOrder({tab}); setList([ ...result ]) setLoading(false); })() },[tab]) return ( <OrderWrapper> ... <Toast show={loading} icon="loading">加載中...</Toast> { list.length>0 && <OrderList list={list}} ... <OrderWrapper> ) }
實(shí)現(xiàn)效果如圖:
2.5 實(shí)現(xiàn)Empty(空狀態(tài))組件
空狀態(tài) 組件,顧名思義就是當(dāng)請(qǐng)求到的數(shù)據(jù)為空或者是數(shù)據(jù)長(zhǎng)度為 0 時(shí),就顯示該組件。這個(gè)組件實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單,因此這里我們直接寫在myorder
組件中,用styled-components
實(shí)現(xiàn)效果。
import React,{useEffect, useState} from 'react' import { OrderWrapper,EmptyItem } from './style' import OrderList from './OrderList' import empty from '../../assets/images/empty.png' export default function Myorder() { const [list,setList] = useState([]); ... return ( <OrderWrapper> ... {list.length>0&&<OrderList list={list} deleteOrder={deleteOrder}/>} {list.length==0&&loading==false&& <EmptyItem> <h3>美好生活 觸手可得</h3> <img src={empty} /> <h2>暫無(wú)訂單</h2> <p>你還沒(méi)有產(chǎn)生任何訂單</p> </EmptyItem> } ... </OrderWrapper> ) }
完成上面這些業(yè)務(wù),myorder
組件就完成的差不多啦~
3. 實(shí)現(xiàn) OederList 組件
這個(gè)組件只需要將父組件myorder
傳進(jìn)來(lái)的數(shù)組數(shù)據(jù)通過(guò) map
分配給 OederNote
,另外刪除功能在它的子組件OrderNote
上觸發(fā),需要通過(guò)它解構(gòu)出deleteOrder
函數(shù)傳給OrderNote
import React from 'react' import { OrderListWrapper } from './style' export default function OrderList({list,deleteOrder}) { return ( <OrderListWrapper> <h3>美好生活 觸手可得</h3> { list.map(item => ( <OrderNote key={item.id} data={item} deleteOrder={()=>deleteOrder(item.id)}/> )) } </OrderListWrapper> ) }
4. 實(shí)現(xiàn) OrderNote 組件
該組件主要負(fù)責(zé)實(shí)現(xiàn)訂單的展示效果,這里只展示部分代碼
import React from 'react' import { NoteWrapper } from './style' const OrderNote = (props) => { const { data } =props; const { deleteOrder } =props return ( <NoteWrapper> ... <div className="btngroup"> <button onClick={deleteOrder}>刪除訂單</button> <button>查看相似</button> </div> </div> </NoteWrapper> )
在這個(gè)組件可以觸發(fā)刪除訂單的業(yè)務(wù),具體如何刪除我們只需要在父組件myOrder
實(shí)現(xiàn),然后將函數(shù)傳遞到OrderNote
觸發(fā)
在myOrder
組件添加deleteOrder
函數(shù):
import React from 'react' import OrderList from './OrderList' export default function Myorder() { const deleteOrder = (id) => { setList(list.filter(order => order.id!==id)); } ... return ( <OrderWrapper> ... {list.length>0&&<OrderList list={list} deleteOrder={deleteOrder}/>} ... </OrderWrapper> ) }
5. 實(shí)現(xiàn) RecommendList 組件
該組件也是對(duì)從父組件Myorder
獲取來(lái)的數(shù)據(jù)進(jìn)行展示,主要是做樣式上的功夫。使用多列布局,將頁(yè)面分為兩列,并且不固定每個(gè)數(shù)據(jù)盒子的高度。
- 最外層列表盒子加上屬性: column-count:2; 將頁(yè)面分為兩列
- 列表中的每一個(gè)單獨(dú)的小盒子添加屬性:break-inside:avoid; 控制文本塊分解成單獨(dú)的列,以免項(xiàng)目列表的內(nèi)容跨列,破壞整體的布局**
- 圖片的寬度設(shè)置:width:100%
多列布局注意上面三點(diǎn)就差不多了
最后
以上就是筆者目前完成整個(gè)組件設(shè)計(jì)、封裝的過(guò)程啦,后面會(huì)去繼續(xù)學(xué)習(xí)下拉刷新、上拉加載等功能,慢慢完善這個(gè)組件。
源碼地址:cool-g/react-reportPage: 仿抖音我的訂單組件 (github.com)
gitpage地址(直接查看頁(yè)面效果):Vite App (cool-g.github.io)
更多關(guān)于React抖音訂單組件設(shè)計(jì)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- React中useRef hook的簡(jiǎn)單用法
- React?Hooks的useState、useRef使用小結(jié)
- React?Hooks中?useRef和useImperativeHandle的使用方式
- react如何利用useRef、forwardRef、useImperativeHandle獲取并處理dom
- React Hooks之useRef獲取元素示例詳解
- react中useRef的應(yīng)用使用詳解
- 重新理解?React?useRef原理
- 教你react中如何理解usestate、useEffect副作用、useRef標(biāo)識(shí)和useContext
- React中useRef的具體使用
- react如何使用useRef模仿抖音標(biāo)題里面添加標(biāo)簽內(nèi)容
相關(guān)文章
React高階組件優(yōu)化文件結(jié)構(gòu)流程詳解
高階組件就是接受一個(gè)組件作為參數(shù)并返回一個(gè)新組件(功能增強(qiáng)的組件)的函數(shù)。這里需要注意高階組件是一個(gè)函數(shù),并不是組件,這一點(diǎn)一定要注意,本文給大家分享React 高階組件HOC使用小結(jié),一起看看吧2023-01-01詳解基于webpack搭建react運(yùn)行環(huán)境
本篇文章主要介紹了詳解基于webpack搭建react運(yùn)行環(huán)境,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06詳解React 在服務(wù)端渲染的實(shí)現(xiàn)
這篇文章主要介紹了詳解React 在服務(wù)端渲染的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11在Ant Design Pro登錄功能中集成圖形驗(yàn)證碼組件的方法步驟
這篇文章主要介紹了在Ant Design Pro登錄功能中集成圖形驗(yàn)證碼組件的方法步驟,這里的登錄功能其實(shí)就是一個(gè)表單提交,實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,具體實(shí)例代碼跟隨小編一起看看吧2021-05-05react實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽方式
這篇文章主要介紹了react實(shí)現(xiàn)數(shù)據(jù)監(jiān)聽方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08自己動(dòng)手封裝一個(gè)React Native多級(jí)聯(lián)動(dòng)
這篇文章主要介紹了自己動(dòng)手封裝一個(gè)React Native多級(jí)聯(lián)動(dòng),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09react實(shí)現(xiàn)列表滾動(dòng)組件功能
在開發(fā)項(xiàng)目的時(shí)候,從服務(wù)端獲取到數(shù)據(jù)列表后,展示給用戶看,需要實(shí)現(xiàn)數(shù)據(jù)自動(dòng)滾動(dòng)效果,怎么實(shí)現(xiàn)呢,下面小編給大家分享react實(shí)現(xiàn)列表滾動(dòng)組件功能實(shí)現(xiàn)代碼,感興趣的朋友一起看看吧2023-09-09react render的原理及觸發(fā)時(shí)機(jī)說(shuō)明
這篇文章主要介紹了react render的原理及觸發(fā)時(shí)機(jī)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02