React手寫一個手風(fēng)琴組件示例
知識點
- emotion語法
- react語法
- css語法
- typescript類型語法
結(jié)構(gòu)分析
根據(jù)上圖,我們來分析一下,一個手風(fēng)琴組件應(yīng)該包含一個手風(fēng)琴容器組件和多個手風(fēng)琴子元素組件。因此,假設(shè)我們實現(xiàn)好了所有的邏輯,并寫出使用demo,那么代碼應(yīng)該如下:
<Accordion defaultIndex="1" onItemClick={console.log}>
<AccordionItem label="A" index="1">
Lorem ipsum
</AccordionItem>
<AccordionItem label="B" index="2">
Dolor sit amet
</AccordionItem>
</Accordion>根據(jù)以上的結(jié)構(gòu),我們可以得知,首先容器組件Accordion會暴露一個defaultIndex屬性以及一個onItemClick事件。顧名思義,defaultIndex代表默認展開的子元素組件AccordionItem的索引,onItemClick代表點擊每一個子元素組件所觸發(fā)的事件。然后,我們可以看到子元素組件有l(wèi)abel屬性和index屬性,很顯然,label代表當(dāng)前子元素的標(biāo)題,index代表當(dāng)前子元素組件的索引值,而我們的Lorem ipsum就是子元素的內(nèi)容。根據(jù)這些分析,我們先來實現(xiàn)一下AccordionItem組件。
AccordionItem子組件
首先我們定義好子組件的結(jié)構(gòu),函數(shù)組件寫法如下:
const AccordionItem = (props) => {
//返回元素
};子元素組件分成三個部分,一個容器元素,一個標(biāo)題元素和一個內(nèi)容元素,因此我們可以將結(jié)構(gòu)寫成如下:
<div className="according-item-container"> <div className="according-item-header"></div> <div className="according-item-content"></div> </div>
知道了結(jié)構(gòu)之后,我們就知道props會有哪些屬性,首先是索引index屬性,它的類型為string 或者number,然后是判斷內(nèi)容是否展開的屬性isCollapsed,它的類型是布爾值,其次我們還有渲染標(biāo)題的屬性label,它應(yīng)該是一個react節(jié)點,類型為ReactNode,同理,還有一個內(nèi)容屬性即children,類型也應(yīng)該是ReactNode,最后就是我們要暴露的事件方法handleClick,它的類型應(yīng)該是一個方法,因此我們可以定義如下的接口:
interface AccordionItemType {
index: string | number;
label: string;
isCollapsed: boolean;
//SyntheticEvent代表react合成事件對象的類型
handleClick(e: SyntheticEvent): void;
children: ReactNode;
}接口定義好之后,接下來我們就在接口里面拿值(采用對象解構(gòu)的方式),這些值都算是可選的,即:
const { label, isCollapsed, handleClick, children } = props;此時我們的AccordionItem子組件應(yīng)該是如下:
const AccordionItem = (props: Partial<AccordionItemType>) => {
const { label, isCollapsed, handleClick, children } = props;
return (
<div className={AccordionItemContainer} onClick={handleClick}>
<div className={AccordionItemHeader}>{label}</div>
<div
aria-expanded={isCollapsed}
className={`${AccordionItemContent}${
isCollapsed ? ' collapsed' : ' expanded'
}`}
>
{children}
</div>
</div>
);
};這里我們可以使用emotion/css來寫css類名樣式,代碼如下:
const baseStyle = css`
line-height: 1.5715;
`;
const AccordionItemContainer = css`
border-bottom: 1px solid #d9d9d9;
`;
const AccordionItemHeader = cx(
baseStyle,
css`
position: relative;
display: flex;
flex-wrap: nowrap;
align-items: flex-start;
padding: 12px 16px;
color: rgba(0, 0, 0, 0.85);
cursor: pointer;
transition: all 0.3s, visibility 0s;
box-sizing: border-box;
`,
);
const AccordionItemContent = css`
color: #000000d9;
background-color: #fff;
border-top: 1px solid #d9d9d9;
transition: all 0.3s ease-in-out;
padding: 16px;
&.collapsed {
display: none;
}
&.expanded {
display: block;
}
`;以上的css后面跟模板字符串再跟css樣式就是emotion/css語法,cx也就是組合樣式寫法,樣式都是常規(guī)的寫法,也沒什么好說的。這里有一個難點,那就是display:none和display:block沒有過渡效果,因此可以采用visibility:hidden和opacity:0的方式來替換,但是這里為了簡單,沒考慮動畫效果,所以也就將問題放著,后面有時間再優(yōu)化。
到目前為止,這個子組件就算是完成了,這也就意味著我們的手風(fēng)琴組件已經(jīng)完成一半了,接下來我們來看容器組件Accordion的寫法。
Accordion容器組件
首先我們先把結(jié)構(gòu)寫好:
const Accordion = (props) => {
//后續(xù)代碼
};我們再來分析一下需要傳給Accordion組件的屬性有哪些,很顯然有defaultIndex,onItemClick和children,因此我們可以定義如下的接口:
interface AccordionType {
defaultIndex: number | string;
onItemClick(key: number | string): void;
children: JSX.Element[];
}注意這里的children不應(yīng)該是ReactNode,而是JSX.Element元素數(shù)組,這是為什么呢,我們后面再來解釋這個問題。現(xiàn)在我們知道了props的屬性之后,我們可以拿到這些屬性,代碼如下:
const Accordion = (props:Partial<AccordionType>) => {
const { defaultIndex, onItemClick, children } = props;
//后續(xù)代碼
};現(xiàn)在我們再維護一個狀態(tài),用來代表當(dāng)前顯示的子元素組件的索引,使用useState hook函數(shù),初始化默認值就應(yīng)該是defaultIndex。如下:
const Accordion = (props:Partial<AccordionType>) => {
const { defaultIndex, onItemClick, children } = props;
//新增的代碼
const [bindIndex, setBindIndex] = useState(defaultIndex);
//后續(xù)代碼
};接下來,我們編寫好容器元素,并寫好樣式,如下所示:
const Accordion = (props: Partial<AccordionType>) => {
const { defaultIndex, onItemClick, children } = props;
const [bindIndex, setBindIndex] = useState(defaultIndex);
return (
<div className={AccordionContainer}></div>
);
};容器元素的樣式如下:
const baseStyle = css`
line-height: 1.5715;
`;
const AccordionContainer = cx(
baseStyle,
css`
box-sizing: border-box;
margin: 0;
padding: 0;
color: #000000d9;
font-size: 14px;
background-color: #fafafa;
border: 1px solid #d9d9d9;
border-bottom: 0;
border-radius: 2px;
`,
);好的,接下來,我們實際上容器元素的子元素應(yīng)該是多個AccordionItem元素,也正因為如此,這里的children類型就是JSX.Element [],我們應(yīng)該如何獲取這些子元素呢?我們應(yīng)該知道,每一個子元素對應(yīng)的就是一個節(jié)點,在react中用的是鏈表來表示這些節(jié)點,每個節(jié)點對應(yīng)的就有個type屬性,我們只需要拿到容器元素的子組件元素中type屬性為AccordionItem的元素數(shù)組,如下:
//name不是AccordionItem,代表子元素不是AccordionItem,不是的我們需要過濾掉
const items = children?.filter(
(item) => item?.type?.name === 'AccordionItem,代表子元素不是AccordionItem,所以我們需要過濾掉',
);到了這里,我們就知道了,容器元素的子元素是一個數(shù)組,我們就需要遍歷,使用map方法,如下:
items?.map(({ props: { index, label, children } }) => (
<AccordionItem
key={index}
label={label}
children={children}
isCollapsed={bindIndex !== index}
handleClick={() => changeItem(index)}
/>
))請注意這一段代碼:
handleClick={() => changeItem(index)}這就是我們之前子組件綁定的事件,也是我們需要暴露出去的事件,在這個事件方法中,我們無非執(zhí)行的就是更改當(dāng)前被展開元素的索引。所以代碼就很好寫了:
const changeItem = (index: number | string) => {
//暴露點擊事件方法接口
if (typeof onItemClick === 'function') {
onItemClick(index);
}
//設(shè)置索引
if (index !== bindIndex) {
setBindIndex(index);
}
};到了這里,我們的一個手風(fēng)琴組件就完成了,完整代碼如下:
import { cx, css } from '@emotion/css';
import React, { useState } from 'react';
import type { ReactNode, SyntheticEvent } from 'react';
const baseStyle = css`
line-height: 1.5715;
`;
const AccordionContainer = cx(
baseStyle,
css`
box-sizing: border-box;
margin: 0;
padding: 0;
color: #000000d9;
font-size: 14px;
background-color: #fafafa;
border: 1px solid #d9d9d9;
border-bottom: 0;
border-radius: 2px;
`,
);
const AccordionItemContainer = css`
border-bottom: 1px solid #d9d9d9;
`;
const AccordionItemHeader = cx(
baseStyle,
css`
position: relative;
display: flex;
flex-wrap: nowrap;
align-items: flex-start;
padding: 12px 16px;
color: rgba(0, 0, 0, 0.85);
cursor: pointer;
transition: all 0.3s, visibility 0s;
box-sizing: border-box;
`,
);
const AccordionItemContent = css`
color: #000000d9;
background-color: #fff;
border-top: 1px solid #d9d9d9;
transition: all 0.3s ease-in-out;
padding: 16px;
&.collapsed {
display: none;
}
&.expanded {
display: block;
}
`;
interface AccordionItemType {
index: string | number;
label: string;
isCollapsed: boolean;
handleClick(e: SyntheticEvent): void;
children: ReactNode;
}
interface AccordionType {
defaultIndex: number | string;
onItemClick(key: number | string): void;
children: JSX.Element[];
}
const AccordionItem = (props: Partial<AccordionItemType>) => {
const { label, isCollapsed, handleClick, children } = props;
return (
<div className={AccordionItemContainer} onClick={handleClick}>
<div className={AccordionItemHeader}>{label}</div>
<div
aria-expanded={isCollapsed}
className={`${AccordionItemContent}${
isCollapsed ? ' collapsed' : ' expanded'
}`}
>
{children}
</div>
</div>
);
};
const Accordion = (props: Partial<AccordionType>) => {
const { defaultIndex, onItemClick, children } = props;
const [bindIndex, setBindIndex] = useState(defaultIndex);
const changeItem = (index: number | string) => {
if (typeof onItemClick === 'function') {
onItemClick(index);
}
if (index !== bindIndex) {
setBindIndex(index);
}
};
const items = children?.filter(
(item) => item?.type?.name === 'AccordionItem',
);
return (
<div className={AccordionContainer}>
{items?.map(({ props: { index, label, children } }) => (
<AccordionItem
key={index}
label={label}
children={children}
isCollapsed={bindIndex !== index}
handleClick={() => changeItem(index)}
/>
))}
</div>
);
};讓我們來看一下效果:

到此為止了,更多React組件的實現(xiàn),可以訪問react-code-segment。
源碼地址 https://github.com/eveningwater/code-segment-react
以上就是React手寫一個手風(fēng)琴組件示例的詳細內(nèi)容,更多關(guān)于React手風(fēng)琴組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React-Native中禁用Navigator手勢返回的示例代碼
本篇文章主要介紹了React-Native中禁用Navigator手勢返回的示例代碼,具有一定的參考價值,有興趣的可以了解一下2017-09-09
React Hook 父子組件相互調(diào)用函數(shù)方式
這篇文章主要介紹了React Hook 父子組件相互調(diào)用函數(shù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
React報錯之Object?is?possibly?null的問題及解決方法
這篇文章主要介紹了React報錯之Object?is?possibly?null的問題,造成 "Object is possibly null"的錯誤是因為useRef()鉤子可以傳遞一個初始值作為參數(shù),而我們傳遞null作為初始值,本文給大家分享詳細解決方法,需要的朋友可以參考下2022-07-07
采用React編寫小程序的Remax框架的編譯流程解析(推薦)
這篇文章主要介紹了采用React編寫小程序的Remax框架的編譯流程解析(推薦),本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
react如何實現(xiàn)側(cè)邊欄聯(lián)動頭部導(dǎo)航欄效果
這篇文章主要介紹了react如何實現(xiàn)側(cè)邊欄聯(lián)動頭部導(dǎo)航欄效果,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解
這篇文章主要介紹了使用 React 和 Threejs 創(chuàng)建一個VR全景項目的過程詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
react-router-dom v6版本實現(xiàn)Tabs路由緩存切換功能
今天有人問我怎么實現(xiàn)React-Router-dom類似標(biāo)簽頁緩存,很久以前用的是react-router v5那個比較容易實現(xiàn),v6變化挺大,但了解react的機制和react-router的機制就容易了,本文介紹react-router-dom v6版本實現(xiàn)Tabs路由緩存切換,感興趣的朋友一起看看吧2023-10-10

