前言
作为数据驱动的领导者react/vue等MVVM框架的出现,帮我们减少了工作中大量的冗余代码, 一切皆组件的思想深得人心。组件就是对一些具有相同业务场景和交互模式代码的抽象,这就需要我们对组件进行规范的封装,掌握高质量组件设计的思路和方法可以帮助我们提高日常的开发效率。笔者将会通过实战抖音订单组件详细的介绍组件的设计思路和方法,对新手特别友好,希望对前端新手们和有一定工作经验的朋友有一定帮助~
前期准备
在组件设计之前,希望你对css、js具有一定的基础。在我们的组件设计时需要用到的开源组件库有:
(有不了解的小伙伴可以自行查阅资料学习一下,在后面用到的时候我也会说明的)
axios
它是一个基于 promise 的网络请求库,用于获取后端数据,是前端常用的数据请求工具;
react-weui
、weui
weui 是微信官方制作的一个基础样式UI库,我们可以通过阅读官方文档直接使用里面的样式,而 react-weui 就是将这些样式封装成我们可以直接使用的组件;
styled-components
称之为css in js,现在正在成为在 React 中设计组件样式的新方法。
另外,我们还用到在线接口工具 faskmock
模拟ajax请求。它更加真实的模拟了前端开发中后端提供数据的方式。
实现后的组件效果
在这我们先来看看组件实现后的组件效果:
1. 组件设计思路
在这个组件中我们需要实现的业务有:
(目前我们就暂时实现以下效果,该页面的其他功能笔者将会在后期慢慢完善~)
- tab切换:点击
tab
,该tab
添加上红色下划线样式,并将该tab
状态下的订单展示在下方。 - 设置loading状态:在数据还在请求中时,显示
loading
图标 - 搜索订单:在当前
tab
下搜索商品标题含有输入内容的订单。 - 删除订单:删除指定订单,由于数据是在
fastmock
中请求得到,因此删除只相对于前端。 - 实现Empty(空状态)组件当当前状态下订单数量为 0 时,显示该组件,否则显示列表组件。
根据我们的需求,可以划分出5个组件模块组成整个页面:
- 页面级别组件
,它是其他组件的父组件; - 显示数据列表组件
,单个数据组件
; - 空状态组件
; - 推荐商品列表组件
。 - 在
组件中请求数据,将对应的数组数据通过props
传给
组件和
组件;
组件再将单个数据传给
组件。这样就规范的完成了父组件请求数据,子组件搭建样式的分工合作了。
分析完组件组成接下来完成组件目录的搭建:
2. 实现 Myorder 组件
首先我们先根据需求将组件框架写好,这样后面写业务逻辑会更清晰:
这个页面级别组件包括固定在顶部的搜索框+导航栏,以及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 (// 搜索 + 导航栏 部分 ) }// 订单列表组件
- 全部
- 待支付
- 待发货
- 待收货/使用
- 评价
- 退款
// 推荐列表组件
有了这个框架,我们来一步步往里面实现内容吧。
2.1 实现tab切换效果
首先来完成第一个需求:当点击某个tab
时,如'待支付',这个tab
要有红色下划线效果。实现原理其实很简单,就是当我们触发该tab
的点击事件时,就将我们事先写好的active
样式加到该tab
上。
这里有两种方案:
- 第一种实现方法是定义一个状态
tab
来控制每个的
className
的内容:
import React,{ useState} from 'react' import { OrderWrapper } from './style' export default function Myorder() { const [tab,setTab] = useState('全部'); const changeTab= (target) => { setTab(target); } return (... ) }...
- 全部
- 待支付
- 待发货
- 待收货/使用
- 评价
- 退款
这种方法有一个明显的缺点,就是只能为其添加一个样式名,当有多个样式类名时,就会出问题了,因此可以采用第二种方法。
- 第二种方法就是用
classnames
了,也是比较推荐的方法,写法也比较简单。
import classnames from 'classnames' import { OrderWrapper } from './style' export default function Myorder() { const [tab,setTab] = useState('全部'); const changeTab= (target) => { setTab(target); } return (... ) }...
- 全部
- 待支付
- 待发货
- 待收货/使用
- 评价
- 退款
当有多个类名时,这样添加:
实现效果如图:
2.2 获取数据
这里准备了两个接口,用于获取订单数据和推荐商品数据。
为了便于管理,我们将数据请求封装在api文件中:
- 第一个接口获取订单数据。需要根据
tab
状态筛选获取的数据,这一步我们也写在接口文件中:
import axios from 'axios' // 请求订单数据 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 "待发货": result=result.filter(item => item.state=="待发货"); break; case "待收货/使用": result=result.filter(item => item.state=="待收货/使用"); break; case "评价": result=result.filter(item => item.state=="评价"); break; case "退款": result=result.filter(item => item.state=="退款"); break; default: break; } } return Promise.resolve({ result }); } )
- 第二个接口获取推荐商品数据:
import axios from 'axios' // 请求推荐商品数据 export const getCommend = () => axios.get('https://www.fastmock.site/mock/759aba4bef0b02794e330cccc1c88555/beers/goods')
接口准备好了,接下来我们将数据分配给子组件,接下来数据如何在页面上显示的任务就交给子组件
和
完成
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([]); // 从接口中获取推荐商品数据 useEffect(()=> { (async()=> { const {data} = await getCommend(); setRecommend([...data]); })() }) // 从接口中获取订单数据,每次tab切换都重新拉取 useEffect(()=>{ (async()=>{ const {result} = await getOrder({tab}); setList([ ...result ]) })() },[tab]) return (... {list.length>0 && ) }} {recommend.length>0 && }
2.3 实现搜索功能
搜索功能应该在对应的tab
下进行,因此我们可以将输入的内容设置为一个状态
,每次改变就根据tab
内容和输入内容重新获取数据:
api接口对订单数据的请求的封装中增加一个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 "待发货": result=result.filter(item => item.state=="待发货"); break; case "待收货/使用": result=result.filter(item => item.state=="待收货/使用"); break; case "评价": result=result.filter(item => item.state=="评价"); 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 }); } )
而在组件的实现上,由于页面没有添加点击搜索的按钮,如果将input
中的value
直接和query
状态绑定的话,每次用户输入一个字就会进行一次查询,触发太频繁,性能不够好,用户体验也不好。
所以这里我的想法是每次输入完按下enter
才进行搜索
但是React中无法直接对input
的enter
事件进行处理。于是我在网上查阅到两种处理方式,第一种是通过 e.nativeEvent
来获取keyCode
判断是否为 13 ,第二中方法是通过addEventListener
注册事件来处理,要慎用。
这里采用第一种方法来实现:
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 (... ...