React组件设计过程之仿抖音订单组件

目录
  • 前言
    • 前期准备
  • 实现后的组件效果
  • 1. 组件设计思路
  • 2. 实现 Myorder 组件
    • 2.1 实现tab切换效果
    • 2.2 获取数据
    • 2.3 实现搜索功能
    • 2.4 设置loading状态
    • 2.5 实现Empty(空状态)组件
  • 3. 实现 OederList 组件
  • 4. 实现 OrderNote 组件
  • 5. 实现 RecommendList 组件
  • 最后

前言

作为数据驱动的领导者react/vue等MVVM框架的出现,帮我们减少了工作中大量的冗余代码, 一切皆组件的思想深得人心。组件就是对一些具有相同业务场景和交互模式代码的抽象,这就需要我们对组件进行规范的封装,掌握高质量组件设计的思路和方法可以帮助我们提高日常的开发效率。笔者将会通过实战抖音订单组件详细的介绍组件的设计思路和方法,对新手特别友好,希望对前端新手们和有一定工作经验的朋友有一定帮助~

前期准备

在组件设计之前,希望你对css、js具有一定的基础。在我们的组件设计时需要用到的开源组件库有:
(有不了解的小伙伴可以自行查阅资料学习一下,在后面用到的时候我也会说明的)

axios 它是一个基于 promise 的网络请求库,用于获取后端数据,是前端常用的数据请求工具;

react-weuiweui weui 是微信官方制作的一个基础样式UI库,我们可以通过阅读官方文档直接使用里面的样式,而 react-weui 就是将这些样式封装成我们可以直接使用的组件;

styled-components 称之为css in js,现在正在成为在 React 中设计组件样式的新方法。

另外,我们还用到在线接口工具 faskmock 模拟ajax请求。它更加真实的模拟了前端开发中后端提供数据的方式。

实现后的组件效果

在这我们先来看看组件实现后的组件效果:

1. 组件设计思路

在这个组件中我们需要实现的业务有:
(目前我们就暂时实现以下效果,该页面的其他功能笔者将会在后期慢慢完善~)

  • tab切换:点击tab,该tab添加上红色下划线样式,并将该tab状态下的订单展示在下方。
  • 设置loading状态:在数据还在请求中时,显示loading图标
  • 搜索订单:在当前tab下搜索商品标题含有输入内容的订单。
  • 删除订单:删除指定订单,由于数据是在fastmock中请求得到,因此删除只相对于前端。
  • 实现Empty(空状态)组件当当前状态下订单数量为 0 时,显示该组件,否则显示列表组件。

根据我们的需求,可以划分出5个组件模块组成整个页面:

  • 页面级别组件<Myorder/>,它是其他组件的父组件;
  • 显示数据列表组件<OrderList/>,单个数据组件<OrderNote/>
  • 空状态组件<EmptyItem/>
  • 推荐商品列表组件<RecommendList/>
  • <Myoeder/>组件中请求数据,将对应的数组数据通过props传给<OrderList/>组件和<RecommendList/>组件;<OrderList/>组件再将单个数据传给<OrderNote/>组件。这样就规范的完成了父组件请求数据,子组件搭建样式的分工合作了。

分析完组件组成接下来完成组件目录的搭建:

2. 实现 Myorder 组件

首先我们先根据需求将组件框架写好,这样后面写业务逻辑会更清晰:

这个页面级别组件包括固定在顶部的搜索框+导航栏,以及OrderListRecommendList组件,因此可以写出如下组件框架:

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>
      // 搜索 + 导航栏 部分
      <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>待发货</li>
          <li>待收货/使用</li>
          <li>评价</li>
          <li>退款</li>
        </ul>
      </div>
      // 订单列表组件
      <OrderList/>
      // 推荐列表组件
      <RecommendList/>
    </OrderWrapper>
  )
}

有了这个框架,我们来一步步往里面实现内容吧。

2.1 实现tab切换效果

首先来完成第一个需求:当点击某个tab时,如'待支付',这个tab要有红色下划线效果。实现原理其实很简单,就是当我们触发该tab的点击事件时,就将我们事先写好的active样式加到该tab上。
这里有两种方案:

  • 第一种实现方法是定义一个状态tab来控制每个<li>className的内容:
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=='待发货'?'active':''} onClick={changeTab.bind(null,'待发货')}>待发货</li>
              <li className={tab=='待收货/使用'?'active':''} onClick={changeTab.bind(null,'待收货/使用')}>待收货/使用</li>
              <li className={tab=='评价'?'active':''} onClick={changeTab.bind(null,'评价')}>评价</li>
              <li className={tab=='退款'?'active':''} onClick={changeTab.bind(null,'退款')}>退款</li>
            </ul>
          ...
      </OrderWrapper>
  )
}

这种方法有一个明显的缺点,就是只能为其添加一个样式名,当有多个样式类名时,就会出问题了,因此可以采用第二种方法。

  • 第二种方法就是用 classnames 了,也是比较推荐的方法,写法也比较简单。
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==="待发货"})} onClick={changeTab.bind(null,'待发货')}>待发货</li>
              <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==="退款"})} onClick={changeTab.bind(null,'退款')}>退款</li>
            </ul>
          ...
      </OrderWrapper>
  )
}

当有多个类名时,这样添加:

<li className={classnames('test',{active:tab==="全部"})} onClick={changeTab.bind(null,'全部')}>全部</li>

实现效果如图:

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')

接口准备好了,接下来我们将数据分配给子组件,接下来数据如何在页面上显示的任务就交给子组件<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([]);
  // 从接口中获取推荐商品数据
  useEffect(()=> {
    (async()=> {
      const {data} = await getCommend();
      setRecommend([...data]);
    })()
  })
  // 从接口中获取订单数据,每次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 实现搜索功能

搜索功能应该在对应的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中无法直接对inputenter事件进行处理。于是我在网上查阅到两种处理方式,第一种是通过 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 (
       <OrderWrapper>
             ...
            <input
              placeholder="搜索订单"
              onKeyPress={handleEnterKey}
            />
           ...
        </div>
       </OrderWrapper>
   )
}

2.4 设置loading状态

在数据请求过程之,页面会空白,为了提升视觉上的效果,在这个时间段我们就设置一个loading样式,这个样式组件我们直接使用reacct-weuiToast组件。
我们增加一个loading状态来来控制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>
  )
}

实现效果如图:

2.5 实现Empty(空状态)组件

空状态 组件,顾名思义就是当请求到的数据为空或者是数据长度为 0 时,就显示该组件。这个组件实现起来比较简单,因此这里我们直接写在myorder组件中,用styled-components实现效果。

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>暂无订单</h2>
              <p>你还没有产生任何订单</p>
            </EmptyItem>
          }
        ...
     </OrderWrapper>
  )
}

完成上面这些业务,myorder组件就完成的差不多啦~

3. 实现 OederList 组件

这个组件只需要将父组件myorder传进来的数组数据通过 map 分配给 OederNote,另外删除功能在它的子组件OrderNote上触发,需要通过它解构出deleteOrder函数传给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. 实现 OrderNote 组件

该组件主要负责实现订单的展示效果,这里只展示部分代码

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>
    )

在这个组件可以触发删除订单的业务,具体如何删除我们只需要在父组件myOrder实现,然后将函数传递到OrderNote触发

myOrder组件添加deleteOrder函数:

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. 实现 RecommendList 组件

该组件也是对从父组件Myorder获取来的数据进行展示,主要是做样式上的功夫。使用多列布局,将页面分为两列,并且不固定每个数据盒子的高度。

  • 最外层列表盒子加上属性: column-count:2; 将页面分为两列
  • 列表中的每一个单独的小盒子添加属性:break-inside:avoid; 控制文本块分解成单独的列,以免项目列表的内容跨列,破坏整体的布局**
  • 图片的宽度设置:width:100%

多列布局注意上面三点就差不多了

最后

以上就是笔者目前完成整个组件设计、封装的过程啦,后面会去继续学习下拉刷新、上拉加载等功能,慢慢完善这个组件。

源码地址:cool-g/react-reportPage: 仿抖音我的订单组件 (github.com)

gitpage地址(直接查看页面效果):Vite App (cool-g.github.io)

更多关于React抖音订单组件设计的资料请关注我们其它相关文章!

(0)

相关推荐

  • React父子组件传值(组件通信)的实现方法

    目录 1.父组件传值子组件 2.子组件传值父组件 3.兄弟组件传值 1.父组件传值子组件 在引用子组件的时候传递,相当于一个属性,例如:在子组件内通过porps.param获取到这个param的值. 父组件向子组件传值,通过props,将父组件的state传递给了子组件. 父组件代码片段: constructor(props){ super(props) this.state={ message:"i am from parent" } } render(){ return( <

  • React组件设计模式之组合组件应用实例分析

    本文实例讲述了React组件设计模式之组合组件应用.分享给大家供大家参考,具体如下: 这种模式本质上解决的是组件之间传值的问题.但是它对于传值以及一些内部操控的逻辑封装得更严密. 场景:希望减少上下级组件之间props的传递,简单来说就是不用传做显式地传值,来达到组件之间相互通信的目的 举例来说,某些界面中应该有Tabs这样的组件,由Tab和TabItem组成,点击每个TabItem,该TabItem会高亮, 那么Tab和TabItem自然要进行沟通.很自然的写法是像下面这样 <TabItem

  • 从零搭建react+ts组件库(封装antd)的详细过程

    目录 整体需求 开发与打包工具选型 使用webpack作为打包工具 使用babel来处理typescript代码 使用less-loader.css-loader等处理样式代码 项目搭建思路 整体结构 方案思路 项目搭建实施 初始化 Babel引入 了解Babel webpack的基于babel-loader的处理流程 引入React相关库(externals方式) 编写组件代码 编译打包组件库 效果演示 处理样式(less编译与css导出) 依赖引入 配置webpack 编写样式代码 编译组件

  • React函数组件与类组件使用及优劣对比

    目录 一.类组件的问题 原因一.因为this带来的问题: 问题描述 问题解析 原因二.类组件代码量比函数组件多: 原因三.类组件过于臃肿不易拆分: 二.函数组件的问题 挂载阶段:getDerviedStateFromProps VS 无 挂载阶段:UNSAFE_componentWillMount VS 无 挂载阶段:componentDidMount VS useEffect render: 生命周期,更新阶段:UNSAFE_componentWillRerciveProps VS 无 生命周

  • React 组件权限控制的实现

    目录 前话 正文 1. 控制方式 1.1 直接计算 1.2 通用权限Hoc 1.3 权限包裹组件 2. 控制结果 2.1 显隐控制 2.2 自定义渲染 3. 权限数据 3.1 静态权限 3.2 动态权限 前话 具备用户体系的业务系统往往需要具备权限控制的能力.前后不分离的开发模式权限控制主要由后端结合模版引擎实现,而前后分离的开发模式由前后两端协商权限配置分别进行数据权限控制和组件权限控制. 正文 权限配置格式不限,为进行后续演示本篇设定权限配置如下: export type IAuthConf

  • React组件设计过程之仿抖音订单组件

    目录 前言 前期准备 实现后的组件效果 1. 组件设计思路 2. 实现 Myorder 组件 2.1 实现tab切换效果 2.2 获取数据 2.3 实现搜索功能 2.4 设置loading状态 2.5 实现Empty(空状态)组件 3. 实现 OederList 组件 4. 实现 OrderNote 组件 5. 实现 RecommendList 组件 最后 前言 作为数据驱动的领导者react/vue等MVVM框架的出现,帮我们减少了工作中大量的冗余代码, 一切皆组件的思想深得人心.组件就是对一

  • 微信小程序仿抖音短视频切换效果的实例代码

    一直以为抖音短视频切换假如用小程序做的话应该是比较简单的,直接用swiper实现就好,但在实际写的过程中才发现没那么简单,要控制的逻辑还是挺多的. 还是先看效果 体验路径 自定义组件系列>>仿抖音短视频切换 代码逻辑 直接调用自定义的swiper组件就好 调用代码 js const videoList = [] Page({ data: { videoList, activeId:2, isPlaying:true }, onLoad() { var that = this wx.getSys

  • 基于vue+uniapp直播项目实现uni-app仿抖音/陌陌直播室功能

    一.项目简介 uni-liveShow是一个基于vue+uni-app技术开发的集小视频/IM聊天/直播等功能于一体的微直播项目.界面仿制抖音|火山小视频/陌陌直播,支持编译到多端(H5.小程序.App端) 且兼容效果一致. 二.效果预览 在H5.小程序.App端测试效果如下:(后续大图均为APP端) 三.使用技术 编码器+技术:HBuilderX + vue/NVue/uniapp/vuex iconfont图标:阿里字体图标库 自定义导航栏 + 底部Tabbar 弹窗组件:uniPop(un

  • Android 仿抖音的评论列表的UI和效果的实现代码

    抖音是一款音乐创意短视频社交软件,是一个专注年轻人的15秒音乐短视频社区.用户可以通过这款软件选择歌曲,拍摄15秒的音乐短视频,形成自己的作品.此App已在Android各大应用商店和APP Store均有上线. 在design包里面 有一个 BottomSheetDialogFragment 这个Fragment,他已经帮我们处理好了手势,所以实现起来很简单.下面是代码: public class ItemListDialogFragment extends BottomSheetDialog

  • Android仿抖音右滑清屏左滑列表功能的实现代码

    概述 ​ 项目中要实现仿抖音直播间滑动清屏,侧滑列表的功能,在此记录下实现过程和踩坑记录希望避免大家走些弯路,也当作自己的一个总结 ​ 首先看下Demo中的效果 ​ 阅读文章需要提前熟悉些事件分发的内容,相信大家都已经了解过了,网上也有很多优秀的文章,这里推荐两篇自己读过印象较深的文章 https://www.jb51.net/article/124249.htm https://www.jb51.net/article/124861.htm 关于这方面的知识,在Android中是再重要不过的了

  • Android仿抖音列表效果

    本文实例为大家分享了Android仿抖音列表效果的具体代码,供大家参考,具体内容如下 当下抖音非常火热,是不是也很心动做一个类似的app吗? 那我们就用RecyclerView实现这个功能吧,关于内存的回收利用就交给RecyclerView就好了. 首先我们先说3个和视频播放暂停相关的接口 public interface OnViewPagerListener { /** * 初始化 */ void onInitComplete(View view); /** * 释放 */ void onP

  • Android自定义view实现仿抖音点赞效果

    前言 学习自定义view,想找点东西耍一下,刚好看到抖音的点赞效果不错,尝试一下. 抖音效果: 话不多说,先上代码: public class Love extends RelativeLayout { private Context mContext; float[] num = {-30, -20, 0, 20, 30};//随机心形图片角度 public Love(Context context) { super(context); initView(context); } public

  • Android 使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果

    SwipeRefreshLayout(这个控件),我先跟大家介绍一下这个控件: 一.SwipeRefreshLayout简单介绍 •先看以下官方文档,已有了很详细的描述了. 官方文档说明 •这里我再大概解释一下: •在竖直滑动时想要刷新页面可以用SwipeRefreshLayout来实现.它通过设置OnRefreshListener来监听界面的滑动从而实现刷新.也可以通过一些方法来设置SwipeRefreshLayout是否可以刷新.如:setRefreshing(true),展开刷新动画. s

  • Android 之BottomsheetDialogFragment仿抖音评论底部弹出对话框效果(实例代码)

    实现的效果图: 自定义Fragment继承BottomSheetDialogFragment 重写它的三个方法: onCreateDialog() onCreateView() onStart() 他们的执行顺序是从上到下 import android.app.Dialog; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable;

  • 微信小程序仿抖音视频之整屏上下切换功能的实现代码

    效果演示: WXML: <view class="video_box"> <view bindtouchend="touchEnd" id="myVideo{{index}}" animation="{{animation}}" bindtouchstart="touchStart" bindtouchmove="touchMove" class="video

随机推荐