React组件设计——今日校园_大学圈(多个小功能组件聚集地)

2,784 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

     作为一名新手,用react设计组件对于我来说,是一个比较有挑战性的事情。 第一次独立设计的是猫眼看
 电影的体育/赛事组件,用过猫眼的兄弟们肯定知道这个组件涉及的,主要是对获取来的数据进行过滤与展示。
 先前没有过独立设计过组件,很多时间都处于一种不知道该干啥的状态😳,真的是一团糟。但是也多亏那次经
 历,我对于这次组件的设计有了更清晰的步骤。
 

react组件设计_前期准备工作

    在进行react组件设计之前,环境和工具一定要有(vscode,node.js);然后要在node.js里用 npm init 
@vitejs/app 进行对项目的初始化工作,随后根据出现的提示进行相应的操作,完成初始化工作。

react组件设计_初期分析及创建文件夹工作

 第一步,仔细分析自己想要设计的组件,比如今日校园_大学圈这个组件截图:
 (原本的截图展示会涉及里面同学的隐私,所以这里放的是我自己做完后的组件截图,下方图标不一样,原因
 懂得都懂,请谅解😭)
    

image.png

 从这个图片中,我们可以分析出这个大组件具备以下展示功能:  
    1. “大学圈”,组件的标题,是固定在顶部的;
    2. 下面的三个选项,关注、本校、问答,选中会出现相应的高亮样式相应的数据展示也发生改变;固定在
       标题的下方;
    3. 三个选项中的问答选项,含有一个子选项——最新,最热,设计原理与第二个一样;
    4. 点击数据展示的三个点会出现模态框,模态框中显示相应的收藏复制等操作,点击相应操作后,会出现
       提示操作成功的小弹窗;
    5. 点击分享也会出现模态框;
    6. 最底部的tab导航,也是固定在底部,点击里面的选项,会有高亮显示;
   
第二步,在项目的src现下创建以下文件夹,放置不同功能的文件夹:
    1. 创建Components文件夹,用来放入自定义的组件,比如头部的<Header/>,列表项<List/>
    2. 创建assets文件夹,用来放入一些静态资源文件,比如styles文件夹的reset.css全局样式,或者需要
       引入的iconfont阿里图标字体库文件、图片文件夹等
    3. 创建api文件夹,专门用来获取数据。数据的接口可以通过fastmock来提供,数据格式为json,如果有
       不熟悉的兄弟们,可以直接打开百读搜索一下这个网站,直接操作一番,就会了解了。
       
    本组件是一个比较小的组件,所需要创建的文件夹也就比较少,但是复杂一点的组件涉及到页面级别的组件跳转时
还需要创建routes文件夹,进行路由配置。

第三步,在第二步的基础下开始创建相应的文件夹及文件:
     1. compnents文件夹下,根据组件的功能来划分不同的小组件,每个小组件都创建一个相应的文件夹,包含index.jsx(组件的功能设置)和style.js(使用styled-components自定义组件样式)
     2. 在assets文件夹下创建styles文件夹,再创建reset.css(重置样式),代码有一点点多,太占位置,有意的读者可以直接在评论区滴滴俺
     3. 在api文件夹下,创建request.js(文件名自定义),引入axios来获取数据,具体代码如下:
import axios from'axios'

export const getContextData =()=>
   axios.get('https://www.fastmock.site/mock/b5c0ca9f1fa672b811e5a28a700073e7/address/comments')
export const getContextQue =()=>
    axios.get('https://www.fastmock.site/mock/b5c0ca9f1fa672b811e5a28a700073e7/address/questions')

react组件设计_具体组件封装工作

- 点击Tab选项时出现高亮

    在点击相应选项时,该选项出现相应的高亮的样式,具体解决思路就是使用classnames(需要引入)根据点击
时所传递的tab值进行匹配,成功则显添加高亮样式:

QQ图片20220623221102.png

 import classnames from 'classnames'
   {/* active为高亮样式的类名<Tab>,<Tablist>为自定义的组件样式*/}
   <Tab>
      <TabList className={classnames({active:tab=='focus'})} onClick={()=>changeTab('focus')}>关注</TabList>
      <TabList className={classnames({active:tab=='school'})} onClick={()=>changeTab('school')}>本校</TabList>    
      <TabList  className={classnames({active:tab=='question'})} onClick={()=>changeTab('question')}>问答</TabList>    
   </Tab>

- 筛选数据操作

    在父组件中在useEeffect()通过await引入api中的获取数据函数,进一步根据点击选项时所传入的tab值来
 筛选数据,具体代码如下:
  //关注、本校、问答三个选项
  const [tab,setTab] =useState('focus')
  //问答选项下的最热、最新选项
  const [Ltab,setLtab] =useState('hot')
  const[que,setQue]=useState([])
  const [data,setData]=useState([])
  const changeTab =(t)=>{
    setTab(t)
  }
  const changeLtab=(t)=>{
    setLtab(t)
}
 useEffect(()=>{
   (async ()=>{
   let {data}=await getContextData()
   let {data:que}=await getContextQue();
   if(tab=="focus"){
   let filterData=data.filter((item)=>item.Focus=="yes")
     setData(filterData)
   }
   else if(tab=="school"){
      setData(data)
   }
   else{
     if(Ltab=='hot')
     {
       let filterQue= que.filter((item)=>item.agree>0)
       setQue(filterQue)
     }
     else{
      let filterQue= que.filter((item)=>item.agree==0)
      setQue(filterQue)
     }
   }
 })()
},[tab,Ltab])

- 空状态组件

    空状态组件设置是为了当没有数据传进来时,用文字提示和图片来代替页面出现空白的情况,该组件的设计
比较简单,只要在组件展示前判断数据的数量是否为0就可以控制组件的显示,代码如下:

image.png

    //父组件中:
   {
       !data.length&&<Nodata/>
       //<Nodata>为自定义的组件,
   } 
// <Nodata/> 空状态组件设计:
import React from 'react'
import blankt from '../../assets/images/blankt.png'

export default function Nodata() {
  return (
    <div style={{textAlign:"center",background:"white",paddingTop:"40px"}}>
      <img src={blankt} alt="" />
      <p style={{color:"#a5a5a5",fontSize:"10px"}}>这里一片空白,啥也么有</p>
    </div>
  )
}

- 遍历数据操作

    筛选完数据,再进行遍历是一个很常规的操作,但是这里要讲的遍历是将其封装在一个函数中,增强了代码
的可读性,避免了代码的繁琐,然后将封装好的遍历函数放置在需要渲染的位置就可以了。具体代码如下:
//遍历数据函数封装
  const renderQue=()=>{
        return que.map(({topic,answer,agree},index)=>{
            return(
            
             <QueWrapper key={index}>
                 <p className='title'>{topic}</p>
                 <p className='ansAccount'>已有人<font style={{color:"#fe888f"}}>{answer.length}</font>回答</p>
                 {
                     answer.map((item,index)=>
                     <div key={index}>
                        <img src={item[0]} alt="" />
                        <p className='heaName'>{item[1]}</p>
                        <p className='heaContext'>{item[2]}</p>
                        <p className='heaDate'>{item[3]}</p>
                     </div>    
                     )
                 }
                 <LittleBox >
                     <i className='fa fa-caret-up'></i>
                     <span >赞同{agree}</span> | <i className='fa fa-caret-down'></i>
                     <span >反对</span>
              </LittleBox>
             </QueWrapper>
            )
        })
    }
    .........
    .........
    return(
        {renderQue()}
    )

- 数据展示操作

    之所以将这个拿出来展示是因为,在一些组件的列表选项中会经常出现多个选项下的数据展示都是用不同
样式渲染的,比如这个大学圈的本校选项与问答选项就是不一样的,所以在展示它们的数据时,我用函数进行了
封装,通过tab值来限制数据的输出。代码如下:
     const ShowData =()=>{
        if(tab!='question'){
            return(
                <div>
                    // 小模态框
                    {Lmodal()}
                    // 本校选项下的数据渲染
                    {renderItem()}
                    <Plus>
                    <li className='fa fa-plus'></li>
                    </Plus>
                </div>
            )
        }
        else{
            return(
                <div>
                    //最新 ,最热选项
                    {queList()}
                    // 问答渲染数据渲染
                    {renderQue()}
                    <Plus>
                      <p>提问</p> 
                    </Plus>
                </div>
            )
        }
    }
    
    return(
         <div style={{paddingTop:"56px",paddingBottom:"58px"}}>
            {ShowData()}
        </div>
    )

- 点赞操作设计

   每一条数据展示部分的下方都有点赞操作,点击后会有高亮样式,且点赞数加一,再点一次,再由高亮样式
恢复原来样式,点赞数量变回原来的数量。点赞数量是从fastmock中获取的,所以每一次点击的时候,要用从
fastmock提供接口得来的数据,根据点击时传入函数的id(或数据中其他唯一的值都行)来定位其点赞数量,
继而进行对数量进行操作,此时一定要记得将操作完后的数据重新在setData一下,代码如下:
   

点赞.gif

const [Dtab,setDtab]= useState(false);
const [tabID,setTabID]= useState("");
const dianZan =(id) =>{
    setTabID(id);
    if(Dtab==true){
        let newData=data.map(item=>
            {
                if(item.id==id){
                    item.Dianzan=item.Dianzan-1
                    setTabID('')
                }
                return item
            }
        )
        setData([...newData])
    }
    else{
        let newData=data.map(item=>
            {
                if(item.id==id){
                    item.Dianzan=item.Dianzan+1
                    
                }
                return item
            }
        )
        setData([...newData])
    }
    setDtab(!Dtab)
}

- 模态框操作设计

    点击数据展示出现的...可以出现一个覆盖在遮罩层上的模态框,点击遮罩层以及其他动态操作会使模态框
消失,并且点击收藏时,弹出收藏成功字样的小弹窗,在这个组件中,没有使用框架中的弹窗,纯纯手写的哟!
(其实主要是引入框架,自己自定义的一些样式会发生变化😭,会加油改进的!)
    手写模态框的核心主要是利用useState定义一个visible状态变量,通过setVisible改变这个状态的boolean
值,进而改变模态框显示与隐藏状态;并且要将父组件的状态量传入模态框组件,模态框组件里也要设置自身开关
的状态量,并且进行开关操作时,要告诉父组件。具体代码如下:

模态框演示.gif

    // 父组件中设计模态框的设计
    const [visiable,setVisiable]=useState(false)
    const [loading,setLoading]=useState(false)
    const [text,setText]=useState('');
    const onModalClose=()=>{
        setVisiable(false)
    }
    const onCancel=()=>{
        setShow(false)
    }
    //用函数封装的小模态框
 const Lmodal=()=>{
    return(
     loading&&<LittleModal>
        <i className={classnames('fa','fa-check-circle-o')}></i>
          <span>{text}</span>
      </LittleModal>
    )
  }
  //定时让小模态框消失
  const setState = () =>{
    setTimeout(()=>{
      setLoading(false)
    },3000)
  }
   return (
      <>
      //小模态框
       {Lmodal()}
       //模态框组件
        <Operation 
            visiable={visiable}
            text={text}
            onModalClose={onModalClose}
            setState={setState}
            setText={setText}
            setLoading={setLoading}
            />
       </>
import React, { useEffect, useState } from 'react'
import './operation.css'

export default function Operation(props) {
  //解构出父组件中定义的状态量,并且起别名为show
  const {visiable:show,text}=props;
  //解构出父组件中定义的函数
  const {onModalClose,setText,setState,setLoading}=props
  const [visiable,setVisiable]=useState(false)
  const [operation,setOperation]=useState('收藏')
  //点击遮罩层,使模态框隐藏
  const maskClick = () =>{
   setVisiable(false)
   onModalClose&&onModalClose()
  }
      //出现收藏成功的小弹窗操作
  const operate = (str) =>{
        setVisiable(false)
        onModalClose&&onModalClose()
        if(operation=='取消收藏'){
          setText('取消收藏成功')
          setOperation('收藏')
        }
        else
        {
          setText(str);
          setOperation('取消收藏')
        }
        setLoading(true)
        setState()
    }
   const operate_ = (str) =>{
        setVisiable(false)
        onModalClose&&onModalClose()
        setText(str);
        setLoading(true)
        setState()
  }
  
 // 关键:将父组件传入的状态量,变为自己的状态量
  useEffect(()=>{
    setVisiable(show)
  },[show])
  
  return (
    visiable&&<div className='modalWrapper'>
        <div className='modal' >
          <div className='modal_'>
            <p className='modalTitle'>动态操作</p>
            <p className='operation' onClick={operate.bind(null,'收藏成功')}>{operation}</p>
            <p className='operation' onClick={operate_.bind(null,'复制成功')}>复制动态内容</p>
            <p className='operation' style={{border:"0"}}>举报</p>
          </div>
          <p className='cancel_'  onClick={maskClick}>取消</p>
        </div>
        <div className="mask" onClick={maskClick}></div>
    </div>
  )
}

react组件设计_后期跟进工作

    这个小组件还有一些小互动没有写完,写完这篇文章后,我就要去干掉它们啦!!!总体来说,这次的组件
设计体验比上一次感觉的要好很多,思路也比较清晰。在这里我提出一下我自己个人开发的一些建议,希望对你
们有些帮助。
    第一,对于写样式的时候不要太求精,可以做出大概效果后,去写其他的功能交互,完成之后,最后在整体
调试一些样式,可以得到即省时间又高效率的成果;
    第二,对于要写在return()里的很多的jsx代码,可以在外面封装成函数,再去实现,后期修改也会更
方便一些。
    第三,在刚开始初始完项目时,可以参考我在前面写的react前期工作,虽然啰嗦了一点,但是养成良好的开
发习惯对后面的一系列设计是非常有帮助的!
    好啦,这篇文章就写到这里了,如果你们有一些好的建议或者对于里面展示的一些代码有疑惑的话可以在评
论区@我哦,让我们一起加油吧!!!

组件资源地址

组件代码资源 组件展示