IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React组件设计,仿米游社首页频道设置页面 -> 正文阅读

[JavaScript知识库]React组件设计,仿米游社首页频道设置页面

前言

作为一个刚接触react 组件设计不久的新人,独立完成一个组件的设计开发其中过程是十分卡手的,本篇详尽的描述了米游社首页频道选择页面组件开发的全过程,希望这个这个简单组件的设计开发能对和我一样接触react组件开发不久的人有点帮助

准备阶段

页面分析

在正式开始仿页面之前,先看下原页面效果:

  • 监听列表数据state 改变实现增加删除
  • 我的频道列表长按拖拽排序
  • 我的频道列表只剩一个游戏时,删除弹出提示
  • 数据发生改变,tab 中确定按钮高亮显示

根据需求我划分组件文件目录如下:

SelectChannel
├─ Body
│├─ content
││├─ index.jsx
││└─ style.js
│├─ index.jsx
│└─ style.js
├─ Footer
│├─ content
││├─ index.jsx
││└─ style.js
│├─ index.jsx
│└─ style.js
├─ Header
│├─ index.jsx
│└─ style.js
├─ index.jsx
└─ style.js 

使用工具

vite: 脚手架,初始化react项目dnd-kit: 拖拽排序功能就是靠他实现的,官方文档styled-components: css in js,官方文档classnames: 动态类名,官方文档fastmock: 接口假数据axios: 数据请求

开发阶段

1. 初始化项目

  • 终端npm init @vitejs/app 对项目进行初始化工作,根据提示输入项目名,选react,顺便打开生成的vite配置文件设置src目录别名为@
  • fastmock 准备好接口假数据,并在api 目录中请求数据,组件中不做数据请求:数据
  • iconfont 选择需要的icon 相似即可,解压放assets 目录下

2. 移动端适配

  • 移动端页面开发当然少不了适配* 在public 目录下创建js 文件adapter.js 内容如下:var init = function () {var clientWidth = document.documentElement.clientWidth || document.body.clientWidth;if (clientWidth >= 640) {clientWidth = 640;}var fontSize = (20 / 375) * clientWidth;document.documentElement.style.fontSize = fontSize + 'px';};init();window.addEventListener('resize', init); * 在src 下创建目录modules 创建rem.js如下:document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + 'px';// 横竖屏切换window.onresize = function() {document.documentElement.style.fontSize = document.documentElement.clientWidth / 3.75 + 'px';} * index.html中引用adapter.js ,main.jsx 中引用rem.js

3. 实现父组件 SelectChannel

  • 除了子组件独有的部分,数据状态改变和函数都在父组件里进行,传给子组件,完整文件如下:
export default function SelectChannel() {  const [list, setList] = useState([ { id: 7, title: '大别野', img: 'https://bbs.mihoyo.com/_nuxt/img/game-dby.7b16fa8.jpg', checked: true, }, ]); const [loading,setLoading] = useState(false) const [change,setChange] = useState(false) // 筛选出已选择和未选择项 const TrueCheck = list.filter(item => item.checked == true); const FalseCheck = list.filter(item => item.checked == false); // 提示模态框 const modal=()=>{ return( loading &&  <Modal> <span>至少选择一个游戏哦~</span> </Modal> ) } // 定时让模态框消失 const setState = () =>{ setTimeout(()=>{ setLoading(false) },2000) } // 选择 const choose = item => { // console.log('--------'); let idx = list.findIndex(data => item.id === data.id); // console.log(idx); list[idx].checked = !list[idx].checked; setList([...list]); setChange(true) }; // 删除已选择项 const deleteList = item => { let idx = list.findIndex(data => item.id === data.id); // 判断已选择项是否小于或等于两个,若是,那么不可删除,弹出提示模态框,若大于两个则执行删除 if(TrueCheck.length <= 2){ setLoading(true); setState(); }else{ list[idx].checked = !list[idx].checked; setList([...list]); setChange(true) } }; // 拿取数据 useEffect(() => { (async () => { let { data } = await select(); // console.log(data); setList([...list, ...data]); })(); }, []); // 拖拽后排序 const handleDragEnd = ({active, over}) => { if(active.id !== over.id){ setList((items) => { const oldIndex = items.findIndex(item => item.id === active.id) const newIndex = items.findIndex(item => item.id === over.id) return arrayMove(items, oldIndex, newIndex) }) } setChange(true) } return ( <> {modal()} <Header change={change} /> <Content data={list}  deleteList={deleteList}  handleDragEnd={handleDragEnd}  /> <Footer data={list}  choose={choose}  FalseCheck={FalseCheck}  /> </> ); 

3.1 小模态框

  • 给小模态框组件一个状态loading 默认为false 当触发删除函数时判断我的频道中数组数据长度,改变loading 状态
 const [loading,setLoading] = useState(false)const deleteList = item => {let idx = list.findIndex(data => item.id === data.id);// 判断已选择项是否小于或等于两个,若是,那么不可删除,弹出提示模态框,若大于两个则执行删除if(TrueCheck.length <= 2){setLoading(true);setState();}else{list[idx].checked = !list[idx].checked;setList([...list]);setChange(true)}}; 
  • 我的频道中数组数据长度只剩两个时再点击删除会弹出提示,由原页面可知整个页面就这一个提示数据,所以写死就可
 const [loading,setLoading] = useState(false)// 提示模态框const modal=()=>{return(loading && <Modal>// 没有其他弹出项,弹出数据写死<span>至少选择一个游戏哦~</span></Modal>)}// 定时让模态框消失const setState = () =>{setTimeout(()=>{setLoading(false)},2000)} 

3.2 删除和添加函数

  • 逻辑一样,findIndex 找出list 中的数据,将其和子组件触发事件传过来的 item 的id 进行对比,改变找出数据的checked ,setList 即可实现两个组件显示列表数据的改变
 // 选择const choose = item => {// console.log('--------');let idx = list.findIndex(data => item.id === data.id);// console.log(idx);list[idx].checked = !list[idx].checked;setList([...list]);setChange(true)};// 删除已选择项const deleteList = item => {let idx = list.findIndex(data => item.id === data.id);// 判断已选择项是否小于或等于两个,若是,那么不可删除,弹出提示模态框,若大于两个则执行删除if(TrueCheck.length <= 2){setLoading(true);setState();}else{list[idx].checked = !list[idx].checked;setList([...list]);setChange(true)}}; 

3.3 拖拽后排序

  • 逻辑和删除添加大致相同,调用了 dnd-kit 中的arrayMove 函数,对交换后的数据进行处理
 // 拖拽后排序const handleDragEnd = ({active, over}) => {if(active.id !== over.id){setList((items) => {const oldIndex = items.findIndex(item => item.id === active.id)const newIndex = items.findIndex(item => item.id === over.id)return arrayMove(items, oldIndex, newIndex)})}setChange(true)} 

4. 页面头部tab

  • 布局常见的三列式布局,左右两个地方可点击跳转首页,这里可以设置路由,使用Link 但这里就展示独立的一个页面组件开发,先用a 标签代替,后续若需要可替换
  • 使用classnames 可以十分简单的设置动态类名,利用父组件中传过来的 chang 值对“确认”按钮是否高亮做出改变

代码如下:

export default function Header({change}) {return (<Tab><div className="left"><a href="#"><i className="iconfont icon-fanhui"></i></a></div><div className="content">首页频道选择</div><div className="right"><a href="#" className={classnames("noChange",{changeItem: change})}>确定</a></div></Tab>);
} 

5. 我的频道和推荐频道组件实现

5.1 组件分析

我的频道和推荐频道都有两个部分,一个固定的头,显示我的频道和推荐频道标题,标题下方是map 动态生成的列表组件,我的频道还需要拖拽排序,遂这里都相应再增加了个子组件 ContentList

5.2 拖拽排序组件库选择

  • 这个组件是整个组件实现的难点,拖拽排序自己实现很难,我尝试自己用原生react 实现了下,效果不尽人意,最终决定用现成的方案,常见的拖拽库选择有下:* react-dnd github 中十分受欢迎的一个拖拽库,功能十分完备,但是用于本页面貌似有点太“重”了,遂放弃* react-beautiful-dnd 和react-dnd 类似,但是我下载包貌似不支持react18,install 不下来,遂寄* dnd-kit 芜湖,看了下官方官方文档使用十分简单,只需要用DndContext、 SortableContext 包装拖拽根组件,Sensors 监听不同的拖动设备,再加上组件库现成的碰撞算法即可,十分滴简单

5.3 我的频道组件实现

5.3.1 父组件实现

  • 使用@dnd-kit/core 中的hook useSensor捕获传感器
  • 使用@dnd-kit/core 中的 DndContext SortableContext 组件包装拖拽根组件
  • 使用@dnd-kit/modifiers 中的 verticalListSortingStrategy 动态修改传感器检测到的运动坐标,限制拖拽方向为纵向

父组件代码如下:

export default function Content(props) {const { data, deleteList, handleDragEnd } = props// 捕获触摸传感器const touchSensor = useSensor(TouchSensor,{activationConstraint:{delay: 300,tolerance: 10,}})// 捕获鼠标const mouseSensor = useSensor(MouseSensor,{activationConstraint:{delay: 300,tolerance: 0,}})const sensors = useSensors(touchSensor,mouseSensor)return (<BodyWrapper><TabWrapper><header><div className='left'><p>我的频道</p></div><div className='right'><p>长按拖动排序</p></div></header></TabWrapper>// DndContext SortableContext 包装拖拽根组件<DndContextsensors={sensors}collisionDetection={closestCenter}onDragEnd={handleDragEnd}modifiers={[restrictToVerticalAxis, restrictToWindowEdges]}><SortableContextitems={data.map(item => item.id)}strategy={verticalListSortingStrategy}>{data.map((item) =><ContentList key={item.id} deleteList={deleteList} item={item}{...item}/>)}</SortableContext></DndContext></BodyWrapper>); 

5.3.2 子组件实现

  • 使用@dnd-kit/sortable 中的hook useSortable 匹配父元素id 参数
  • 使用@dnd-kit/utilities 中的CSS 搭配一些css 属性实现选中拖拽时的样式

代码如下:

export default function ContentList(props) {const { checked, id, title, img, deleteList, item } = props;const {setNodeRef,attributes,listeners,transition,transform,isDragging} = useSortable({id: id})// 长按选中元素拖动时样式const style = {transition,transform: CSS.Transform.toString(transform),// 拖拽时透明度,原版为1opacity: isDragging ? 0.6 : 1,dragSelectorExclude: "i"}return (<>{checked == true &&<Tabref={setNodeRef}{...attributes}{...listeners}style={style}><TabItem><img src={img} alt="" /><span>{title}</span>{title !== '大别野' && <i className="iconfont icon-shanjian" onClick={() => deleteList(item)} ></i>}<i className="iconfont icon-shouye" ></i></TabItem></Tab>}</>) 

官方拖拽时没有样式改变我这给了个0.6的透明

5.4 推荐频道组件实现

  • 除了没有拖拽排序外几乎和我的频道一样
  • 判断FalseCheck 数组长度以控制组件是否显示,若组件列表中没有数据了,不显示组件

代码如下:

5.4.1 父组件

export default function Footer(props) {const { data, choose, FalseCheck } = propsreturn (<FooterWrapper>{FalseCheck.length > 0 &&<TabWrapper><header><div className='left'><p>推荐频道</p></div></header></TabWrapper>}<ContentList data={data} choose={choose} /></FooterWrapper>);
} 

5.4.2 子组件

export default function ContentList(props) {const { data , choose } = propsreturn (<Tab>{data.map((item) => item.checked == false &&<TabItem key={item.id}><img src={item.img} alt="" /><span>{item.title}</span><i className="iconfont icon-tianjia" onClick={() => choose(item)}></i></TabItem>)}</Tab>)
} 

最终效果:

最终目录结构:

select-channel
├─ index.html
├─ package-lock.json
├─ package.json
├─ public
│└─ js
│ └─ adapter.js
├─ src
│├─ api
││└─ request.js
│├─ App.css
│├─ App.jsx
│├─ assets
││├─ font
││└─ styles
││ └─ reset.css
│├─ components
││└─ SelectChannel
││ ├─ Body
││ │├─ content
││ ││├─ index.jsx
││ ││└─ style.js
││ │├─ index.jsx
││ │└─ style.js
││ ├─ Footer
││ │├─ content
││ ││├─ index.jsx
││ ││└─ style.js
││ │├─ index.jsx
││ │└─ style.js
││ ├─ Header
││ │├─ index.jsx
││ │└─ style.js
││ ├─ index.jsx
││ └─ style.js
│├─ index.css
│├─ main.jsx
│└─ modules
│ └─ rem.js
└─ vite.config.js 

最后

这就是这次组件实现的全过程,后续会继续完善,代码在仿米游社首页频道设置页面github page 直接查看效果:实时演示

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 18:55:10  更:2022-08-19 18:55:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:58:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码