作为一个刚接触react 组件设计不久的新人,独立完成一个组件的设计开发其中过程是十分卡手的,本篇详尽的描述了米游社首页频道选择页面组件开发的全过程,希望这个这个简单组件的设计开发能对和我一样接触react组件开发不久的人有点帮助
在正式开始仿页面之前,先看下原页面效果:
根据需求我划分组件文件目录如下:
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: 数据请求
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.jsexport 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 && 至少选择一个游戏哦~ ) } // 定时让模态框消失 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()} > );
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 && // 没有其他弹出项,弹出数据写死至少选择一个游戏哦~ )}// 定时让模态框消失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)}};
// 拖拽后排序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)}
代码如下:
export default function Header({change}) {return (首页频道选择 );
}
我的频道和推荐频道都有两个部分,一个固定的头,显示我的频道和推荐频道标题,标题下方是map 动态生成的列表组件,我的频道还需要拖拽排序,遂这里都相应再增加了个子组件 ContentList
父组件代码如下:
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 (我的频道
长按拖动排序
// DndContext SortableContext 包装拖拽根组件 item.id)}strategy={verticalListSortingStrategy}>{data.map((item) =>)} );
代码如下:
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 &&{title}{title !== '大别野' && deleteList(item)} >} }>)
官方拖拽时没有样式改变我这给了个0.6的透明
代码如下:
export default function Footer(props) {const { data, choose, FalseCheck } = propsreturn ({FalseCheck.length > 0 &&推荐频道
} );
}
export default function ContentList(props) {const { data , choose } = propsreturn ({data.map((item) => item.checked == false &&{item.title} choose(item)}> )} )
}
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 直接查看效果:实时演示