点击在线阅读,体验更好 | 链接 |
---|---|
现代JavaScript高级小册 | 链接 |
深入浅出Dart | 链接 |
现代TypeScript高级小册 | 链接 |
我们先从最基本的实现开始,然后逐步添加更多的功能,如手风琴模式、自定义箭头、禁用状态、隐藏时是否渲染DOM结构
组件接口定义
Collapse
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
accordion | 是否开启手风琴模式 | boolean | false |
activeKey | 当前展开面板的 key | 手风琴模式:string | null 非手风琴模式:string[] | – |
arrow | 自定义箭头,如果是 ReactNode,那么 antd-mobile 会自动为你增加旋转动画效果 | ReactNode | ((active: boolean) => React.ReactNode) | – |
defaultActiveKey | 默认展开面板的 key | 手风琴模式:string | null 非手风琴模式:string[] | – |
onChange | 切换面板时触发 | 手风琴模式:(activeKey: string | null) => void 非手风琴模式:(activeKey: string[]) => void | – |
Collapse.Panel
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
arrow | 自定义箭头 | ReactNode | ((active: boolean) => React.ReactNode) | – |
destroyOnClose | 不可见时卸载内容 | boolean | false |
disabled | 是否为禁用状态 | boolean | false |
forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false |
key | 唯一标识符 | string | – |
onClick | 标题栏的点击事件 | (event: React.MouseEvent<Element, MouseEvent>) => void | – |
title | 标题栏左侧内容 | ReactNode | – |
创建基础Collapse组件
我们创建一个基础的Collapse组件。这个组件需要有一个状态来追踪它是否被展开
import React, { useState } from 'react';
const Collapse = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? 'Collapse' : 'Expand'}
</button>
{isOpen && <div>{children}</div>}
</div>
);
};
export default Collapse;
拓展Collapse组件其它属性
-
accordion
:如果设置为true,我们将启用手风琴模式。在这种模式下,只有一个面板可以被展开。当一个新的面板被展开时,之前展开的面板将被关闭。 -
activeKey
:这是当前展开面板的key。如果我们处于手风琴模式,这将是一个字符串或null。如果我们不在手风琴模式,这将是一个字符串数组。 -
arrow
:这是一个自定义的箭头。如果这是一个React节点,antd-mobile将自动为你添加旋转动画效果。如果这是一个函数,它将接收一个参数,表示面板是否被展开,并返回一个React节点。 -
defaultActiveKey
:这是默认展开面板的key。它的类型与activeKey相同。 -
onChange
:这是一个函数,它在面板切换时被触发。它接收一个参数,表示当前展开面板的key。它的类型与activeKey相同。
import React, { useState, useEffect } from 'react';
const Collapse = ({ children, forceRender, accordion, activeKey, arrow, defaultActiveKey, onChange }) => {
const [isOpen, setIsOpen] = useState(false);
const [currentActiveKey, setCurrentActiveKey] = useState(defaultActiveKey);
useEffect(() => {
setCurrentActiveKey(activeKey);
}, [activeKey]);
const handleClick = () => {
setIsOpen(!isOpen);
if (accordion) {
setCurrentActiveKey(isOpen ? null : activeKey);
}
onChange && onChange(isOpen ? null : activeKey);
};
const renderArrow = () => {
if (typeof arrow === 'function') {
return arrow(isOpen);
}
return arrow;
};
return (
<div>
<button onClick={handleClick}>
{isOpen ? 'Collapse' : 'Expand'}
{renderArrow()}
</button>
<div style={{ display: isOpen || forceRender ? 'block' : 'none' }}>
{children}
</div>
</div>
);
};
export default Collapse;
实现Panel
我们创建一个名为Collapse.Panel
的子组件来支持这些新的属性。这个子组件将作为Collapse
组件的一部分,用于表示一个可折叠的面板。
-
arrow
:这是一个自定义的箭头。如果这是一个React节点,antd-mobile将自动为你添加旋转动画效果。如果这是一个函数,它将接收一个参数,表示面板是否被展开,并返回一个React节点。 -
destroyOnClose
:如果设置为true,我们将在面板关闭时销毁它的内容。 -
disabled
:如果设置为true,我们将禁用面板,使其不能被打开或关闭。 -
forceRender
:如果设置为true,我们将在面板关闭时仍然渲染它的DOM结构。 -
key
:这是面板的唯一标识符。 -
onClick
:这是一个函数,它在面板的标题栏被点击时被触发。它接收一个参数,表示点击事件。 -
title
:这是面板标题栏的内容。
import React, { useState, useEffect } from 'react';
const Panel = ({ children, arrow, destroyOnClose, disabled, forceRender, key, onClick, title }) => {
const [isOpen, setIsOpen] = useState(false);
const handleClick = (event) => {
if (disabled) return;
setIsOpen(!isOpen);
onClick && onClick(event);
};
const renderArrow = () => {
if (typeof arrow === 'function') {
return arrow(isOpen);
}
return arrow;
};
useEffect(() => {
if (destroyOnClose && !isOpen) {
children = null;
}
}, [isOpen]);
return (
<div key={key}>
<button onClick={handleClick}>
{title}
{renderArrow()}
</button>
<div style={{ display: isOpen || forceRender ? 'block' : 'none' }}>
{children}
</div>
</div>
);
};
const Collapse = ({ children, accordion, activeKey, defaultActiveKey, onChange }) => {
};
Collapse.Panel = Panel;
export default Collapse;
forceRender属性
我们要添加一个名为forceRender的属性。如果这个属性被设置为true,我们会在组件隐藏时仍然渲染DOM结构,如果面板渲染的数据量比较大,这个属性特别有用,不会造成打开的时候会卡顿一下
import React, { useState } from 'react';
const Collapse = ({ children, forceRender }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? 'Collapse' : 'Expand'}
</button>
<div style={{ display: isOpen || forceRender ? 'block' : 'none' }}>
{children}
</div>
</div>
);
};
export default Collapse;
实现折叠面板动画
height方式实现
.collapse-panel {
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 10px;
overflow: hidden;
}
.collapse-panel-button {
background-color: #f5f5f5;
color: #333;
cursor: pointer;
padding: 10px 15px;
width: 100%;
text-align: left;
border: none;
outline: none;
}
.collapse-panel-content {
padding: 10px 15px;
background-color: white;
overflow: hidden;
max-height: 0;
transition: max-height 0.2s ease-out;
}
.collapse-panel-content.open {
max-height: 100vh;
}
import React, { useState, useEffect, useRef } from 'react';
const Panel = ({ children, arrow, destroyOnClose, disabled, forceRender, key, onClick, title }) => {
const [isOpen, setIsOpen] = useState(false);
const contentRef = useRef(null);
const handleClick = (event) => {
if (disabled) return;
setIsOpen(!isOpen);
onClick && onClick(event);
};
const renderArrow = () => {
if (typeof arrow === 'function') {
return arrow(isOpen);
}
return arrow;
};
useEffect(() => {
if (destroyOnClose && !isOpen) {
children = null;
}
}, [isOpen]);
useEffect(() => {
contentRef.current.style.maxHeight = isOpen ? `${contentRef.current.scrollHeight}px` : '0';
}, [isOpen]);
return (
<div key={key} className="collapse-panel">
<button onClick={handleClick} className="collapse-panel-button">
{title}
{renderArrow()}
</button>
<div ref={contentRef} className={`collapse-panel-content ${isOpen ? 'open' : ''}`}>
{children}
</div>
</div>
);
};
// ...
完整效果:
其它方式
上面手风琴效果是利用height的实现,这种实现会触发重排,所以感兴趣的同学可以考虑其它方式优化一下
- 基于scaleY
- 使用FLIP技术添加动画优化