React實現(xiàn)antdM的級聯(lián)菜單實例
效果圖

需求分析
級聯(lián)菜單分為兩部分:head與body。
body
包含兩部分:已選項列表,候選菜單
已選項列表
- body展示當(dāng)前菜單的所有option,可上下滾動。
- body中選一個option后,會在head的已選列表中進行展示,并且body將顯示下一級的菜單。
- 選中的option,背景色和字體需要改變,以示區(qū)分。
候選菜單
- 依次顯示每個選中的option,當(dāng)所有option的長度超過屏幕寬度時,可左右滾動
- 每個option固定寬度,超出寬度顯示省略號。
- 當(dāng)點擊其中一個option時候,該項高亮,并且body顯示為該級的菜單。
head
- 包含取消和確定兩個按鈕。
- 點擊取消,將不做任何處理
- 確定按鈕需要在級聯(lián)菜單選到?jīng)]有下一級的時候才可點擊
- 點擊確定,將觸發(fā)回調(diào),攜帶已選的參數(shù)
項目結(jié)構(gòu)
├─ src │ ├─ App.js │ ├─ components │ │ └─ Cascader │ │ ├─ CascaderContent // content部分 │ │ │ ├─ CascaderContent.js │ │ │ └─ style.js │ │ ├─ CascaderHead // head部分 │ │ │ ├─ CascaderHead.js │ │ │ └─ style.js │ │ ├─ index.js // 入口 │ │ ├─ style.js │ │ ├─ Cascader.js │ │ └─ testData // 測試數(shù)據(jù) │ │ └─ data.js │ ├─ index.css │ └─ index.js
實現(xiàn)body部分
levels
根據(jù)數(shù)據(jù)源dataSource與value生成一個數(shù)組,數(shù)據(jù)結(jié)構(gòu)如下。
數(shù)組的長度為已選項數(shù)加一,最大為數(shù)據(jù)源的最大深度
onChange
點擊菜單項,如果為不可選狀態(tài),則return。如果有onSelect回調(diào),則將已選的value傳遞給回調(diào)函數(shù)
value
初始化的時候,value為默認(rèn)值,后面在此基礎(chǔ)上進行修改
loading
有時候數(shù)據(jù)可能是異步請求獲取的,增加一個定時器,可以在數(shù)據(jù)未加載完的時候,顯示loading效果。
tabActiveIndex
當(dāng)前候選的菜單的索引,,選中一項后,值加一,如果已經(jīng)選到了最大深度,那么索引為最后一頁。
classPrefix
是一個變量,方便設(shè)置公共變量

import React, { useMemo, useState } from "react";
import { useCallback, useEffect } from "react";
import { Wrapper } from "./style";
const classPrefix = `antdm-cascader-view`
export const CascaderContent = function ({ visible = false, ...props }) {
// 當(dāng)前頁
const [tabActiveIndex, setTabActiveIndex] = useState(0);
// 初始值
const [value, setValue] = useState(props.value || props.defaultValue || []);
// loading效果
const [loading, setLoading] = useState(true);
const levels = useMemo(() => {
const ret = []
let currentOptions = props.options
let reachedEnd = false
for (const v of value) {
const target = currentOptions.find(option => option.value === v)
ret.push({
selected: target,
options: currentOptions,
})
// 沒有下一項的時候中止遍歷
if (!target || !Array.isArray(target.children) || target.children.length === 0) {
reachedEnd = true
break
}
currentOptions = target.children
}
if (!reachedEnd) {
ret.push({
selected: undefined,
options: currentOptions,
})
}
return ret;
}, [props.options, value])
// 點擊選項的時候
const onChange = useCallback((item, index) => {
if (item?.disabled) {
return
}
const newValue = [...value.slice(0, index), item.value];
setValue(newValue);
props.onSelect?.(newValue)
}, [value, props.onSelect])
// 選中數(shù)據(jù)后,切換下一級菜單
useEffect(() => {
const max = levels.length - 1
if (tabActiveIndex > max) {
setTabActiveIndex(max)
}
}, [tabActiveIndex, levels])
useEffect(() => {
setTabActiveIndex(levels.length - 1)
}, [value])
useEffect(() => {
if (visible) {
setValue(props.value || props.defaultValue || []);
}
}, [visible])
useEffect(() => {
setValue(props.value || props.defaultValue || [])
}, [props.value, props.defaultValue])
// 設(shè)置定時器,使用loading效果
useEffect(() => {
const timer = setTimeout(() => {
if (props.options?.length === 0) {
setLoading(false)
}
return () => {
clearTimeout(timer)
}
}, 3000);
}, [])
// 數(shù)據(jù)加載完畢后取消loading效果
useEffect(() => {
if (props.options.length !== 0) {
setLoading(false)
}
}, [props.options])
return <Wrapper>
<div className={classPrefix}>
<div className={`${classPrefix}-tabs`}>
{levels.map((item, index) => {
return <div
key={index}
onClick={() => { setTabActiveIndex(index) }}
className={`${classPrefix}-tab ${tabActiveIndex === index && classPrefix + "-tab-active"}`}>
{item?.selected?.label ? item?.selected?.label : item?.selected?.label === "" ? "" : "請選擇"}
</div>
})}
</div>
<div className={`${classPrefix}-content`}>
{!loading ? levels.map((item, index) => {
return <div
key={index.toString()}
style={{ display: index === tabActiveIndex ? "block" : "none" }}
className={`${classPrefix}-list`} >
{item.options.map((o, i) => {
return <div key={i.toString()} className={`${classPrefix}-item ${o.value === item?.selected?.value && classPrefix + "-item-active"}`}>
<div
onClick={() => onChange(o, index)}
className={`${classPrefix}-item-main ${o?.disabled && classPrefix + "-item-disabled"}`}>
{o.label}
</div>
{o.value === item?.selected?.value && <div className={`${classPrefix}-item-extra`}>?</div>}
</div>
})}
</div>
}) : "loading..."}
</div>
</div>
</Wrapper>
}
實現(xiàn)head部分
當(dāng)已經(jīng)沒有下一級菜單的時候,確定按鈕變?yōu)榭牲c擊狀態(tài)
import React from "react";
import { Wrapper } from "./style";
const classPrefix = `antdm-cascader`
export const CascaderHead = function (props) {
return <Wrapper>
<div className={classPrefix}>
<div className={`${classPrefix}-header`}>
<a
className={`${classPrefix}-header-button`}
onClick={() => {
props.onCancel?.()
}}
>
{props.cancelText || "取消"}
</a>
<div className={`${classPrefix}-header-title`}>{props.title}</div>
<a
className={`${classPrefix}-header-button ${props.canCommit ? '' : classPrefix + '-header-confirm-disable'}`}
onClick={() => {
props.canCommit && props.onConfirm?.();
}}
>
{props.confirmText || "確定"}
</a>
</div>
</div>
</Wrapper>
}
整合head與body
import React, { useState, useCallback, useEffect } from "react";
import { Popup } from "antd-mobile";
import { CascaderHead } from "./CascaderHead/CascaderHead";
import { CascaderContent } from "./CascaderContent/CascaderContent";
import { Wrapper } from "./style";
export const CascaderModal = function (props) {
const [value, setValue] = useState(props.value || props.defaultValue || []);
const [canCommit, setCanCommit] = useState(false);
const onChange = useCallback((v) => {
setValue(v);
props.onSelect?.(v)
}, [props.onSelect])
// 將選擇的數(shù)據(jù)提交出去
const onConfirm = useCallback(() => {
props.onConfirm?.(value)
}, [props.onConfirm, value])
// 取消
const onCancel = useCallback(() => {
props.onCancel?.()
}, [props.onCancel])
useEffect(() => {
if (value.length === 0) {
return;
}
let children = props.options;
let i = 0;
for (i; i < value.length; i++) {
const obj = children.find(item => item.value === value[i]);
if (!obj) {
children = undefined;
break;
} else {
children = obj.children
}
}
setCanCommit(!Array.isArray(children) || children.length === 0)
}, [value, props.options])
useEffect(() => {
setValue(props.value || props.defaultValue || [])
}, [props.value, props.defaultValue])
useEffect(() => {
if (props.visible) {
setCanCommit(false);
}
}, [props.visible])
return <Wrapper>
<Popup
className="antdm-cascader-modal"
visible={props.visible}
onClose={onCancel}
animationType="slide-up"
popup={true}
>
<CascaderHead {...props} canCommit={canCommit} onCancel={onCancel} onConfirm={onConfirm} />
<CascaderContent {...props} visible={props.visible} onSelect={onChange} />
</Popup>
</Wrapper>
}以上就是React實現(xiàn)antdM的級聯(lián)菜單實例的詳細(xì)內(nèi)容,更多關(guān)于React antdM級聯(lián)菜單的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React應(yīng)用框架Dva數(shù)據(jù)流向原理總結(jié)分析
這篇文章主要為大家介紹了React 應(yīng)用框架Dva數(shù)據(jù)流向原理總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
在react中對less實現(xiàn)scoped配置方式
這篇文章主要介紹了在react中對less實現(xiàn)scoped配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
React.memo 和 useMemo 的使用問題小結(jié)
隨著代碼的增加,每次的狀態(tài)改變,頁面進行一次 reRender ,這將產(chǎn)生很多不必要的 reRender 不僅浪費性能,從而導(dǎo)致頁面卡頓,這篇文章主要介紹了React.memo 和 useMemo 的使用問題小結(jié),需要的朋友可以參考下2022-11-11
使用react+redux實現(xiàn)計數(shù)器功能及遇到問題
使用redux管理數(shù)據(jù),由于Store獨立于組件,使得數(shù)據(jù)管理獨立于組件,解決了組件之間傳遞數(shù)據(jù)困難的問題,非常好用,今天重點給大家介紹使用react+redux實現(xiàn)計數(shù)器功能及遇到問題,感興趣的朋友參考下吧2021-06-06
React中路由參數(shù)如何改變頁面不刷新數(shù)據(jù)的情況
這篇文章主要介紹了React中路由參數(shù)如何改變頁面不刷新數(shù)據(jù)的情況,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
React項目中使用zustand狀態(tài)管理的實現(xiàn)
zustand是一個用于狀態(tài)管理的小巧而強大的庫,本文主要介紹了React項目中使用zustand狀態(tài)管理的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2023-10-10
React替換傳統(tǒng)拷貝方法的Immutable使用
Immutable.js出自Facebook,是最流行的不可變數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)之一。它實現(xiàn)了完全的持久化數(shù)據(jù)結(jié)構(gòu),使用結(jié)構(gòu)共享。所有的更新操作都會返回新的值,但是在內(nèi)部結(jié)構(gòu)是共享的,來減少內(nèi)存占用2023-02-02

