乐趣区

关于react.js:动手撸组件系列-1-使用React实现一个Collapse组件

写组件的能力是掂量前端工程师程度的重要指标,不论是根底组件还是业务组件。
笔者在闲暇工夫也喜爱写组件,为了帮忙初学者上手写 React 组件,同时为了分享我在写组件中的教训和想法,决定开设一个系列,即:入手撸组件系列,和大家分享一些公共组件和业务组件的实现形式和实现技巧。

作为这个系列的第一篇文章,分享下如何从零到一实现一个折叠面板(Collapse)组件

Collapse 根底 UI 绘制

折叠面板作为一个根底组件,由两局部形成:第一局部是题目区域,第二局部是可折叠区域,点击题目区域能够折叠和开展内容区。为了组件的好看性能够在题目右侧增加一个箭头图标,在开展和折叠的时候使其旋转。

为了升高环境搭建老本,实际采纳 create-react-app 环境,创立 create-react-app 开发环境异常简略,只须要在装置 node 的零碎中执行如下命令

npx create-react-app 项目名称

须要留神的是我的项目名必须为英文,create-react-app 会主动为咱们创立一个目录。

我的项目创立实现后,在 src 目录下创立名为 Collapse.jsx 的文件,输出如下代码:
(初学者能够抉择复制)

import React, {useState} from "react";
import "./style.css";

const CollapsablePanel = () => {const [isCollapsed, setIsCollapsed] = useState(true);

    const togglePanel = () => {setIsCollapsed((prevState) => !prevState);
    };
    return (
        <div className="wrapper">
            <div className="pannel" onClick={togglePanel}>
                <div className="heading">
                    <span>Flower Collapse</span>
                    <svg width="20px"
                        height="25px" viewBox="0 0 1024 1024"
                        style={{color: '#6495ed'}}><path d="M64 351c0-8 3-16 9-22.2 12.3-12.7 32.6-13.1
                         45.3-0.8l394.1 380.5L905.7 328c12.7-12.3 33-12 45.3 0.7s12 33-0.7 45.3L534.7 
                         776c-12.4 12-32.1 12-44.5 0L73.8 374c-6.5-6.3-9.8-14.6-9.8-23z"p-id="1705">
                        </path></svg>
                </div>
                <div
                    className="content"
                >
                    <div className="contentInner" >
                        Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
                        nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
                        erat, sed diam voluptua.
                    </div>
                </div>
            </div>
        </div>
    );
};

export default CollapsablePanel;

接着创立名称为 style.css 的款式文件

.wrapper {
    display: flex;
    justify-content: center;
    width: 100%;
    height: 100vh;
    background-color: rgb(228, 239, 239);
    padding-top: 40vh;
}

.pannel {
    width: 400px;
    text-align: left;
}

.heading {
    background-color: #bfa;
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
    color: #000;
    font-size: 20px;
    line-height: 20px;
    border: 1px solid rgb(212, 240, 205);
    padding: 10px 20px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    cursor: pointer;
}

.content {
    font-size: 20px;
    background: #fff;
    border: 1px solid #fff;
    border-top: none;
    padding: 0 20px;
    color: #000000;
    overflow: hidden;
}

.contentInner {padding: 20px 0;}

创立完以上两个文件后,在 index.js 中挂载创立好的 Collapse 组件:
(原有代码无关紧要,间接删除即可)

import React from 'react';
import ReactDOM from 'react-dom/client';
import Collapse from './components/Collapse';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Collapse />);

创立实现后,应用 yarn start 命令启动利用,就能看到绘制好的 Collapse 组件外观:

实现了 Collapse 根底 UI 绘制,回顾一下做了哪些操作:
首先定义了名为 isCollapsed 的 state,存储组件开展敞开状态,并申明了名为 togglePanel 办法,在用户点击题目的时候调用此办法即可实现面板的开展敞开。
接着别离定义了款式名为 pannel、heading、content 的 div 容器及与其相干的子容器,并在 style.css 中设置了容器的款式。组件中的 ICON 应用 svg 标签间接绘制,防止因引入 svg 包增大组件体积。

内容区开展动画

实现动画的形式有很多,能够应用 css 的 transition 属性实现,也可应用 React 生态品种繁多的动画库。在 React 生态中,有个十分风行的动画库叫react-spring, 不仅功能强大,而且反对 hook 形式调用,本文就用这个动画库来实现内容区域开展动画和按钮旋转动画。

装置 react-spring 动画库

yarn add react-spring

装置完 react-spring 动画库当前,就能够定义方法让 spring 帮咱们生成动画款式了

const panelContentAnimatedStyle = useSpring({height: isCollapsed ? 0 : 200,});

接着把内容区域的标签名从 <div> 改为 <animated.div> 即可(和 useSpring 雷同,animated 也是 react-spring 具备的一个对象),并在标签中加上刚刚创立的panelContentAnimatedStyle:

import React, {useState} from "react";
import {useSpring, animated} from "react-spring";
import "./style.css";

const CollapsablePanel = () => {const [isCollapsed, setIsCollapsed] = useState(true);

    const togglePanel = () => {setIsCollapsed((prevState) => !prevState);
    };
    const panelContentAnimatedStyle = useSpring({height: isCollapsed ? 0 : 180,});
    return (
        <div className="wrapper">
            <div className="pannel" onClick={togglePanel}>
                <div className="heading">
                    <span>Flower Collapse</span>
                    <svg width="20px"
                        height="25px" viewBox="0 0 1024 1024"
                        style={{color: '#6495ed'}}><path d="M64 351c0-8 3-16 9-22.2 12.3-12.7 32.6-13.1
                         45.3-0.8l394.1 380.5L905.7 328c12.7-12.3 33-12 45.3 0.7s12 33-0.7 45.3L534.7 
                         776c-12.4 12-32.1 12-44.5 0L73.8 374c-6.5-6.3-9.8-14.6-9.8-23z"p-id="1705">
                        </path></svg>
                </div>
                <animated.div
                    style={panelContentAnimatedStyle}
                    className="content"
                >
                    <div className="contentInner" >
                        Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
                        nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
                        erat, sed diam voluptua.
                    </div>
                </animated.div>
            </div>
        </div>
    );
};

export default CollapsablePanel;

点击标题栏能够看到如下成果:

等等~~ 高度是固定的吗?

显然不是!用户在应用 Collapse 组件的时候,传递的内容不单是文字还有可能是图片或者是任意类型的 ReactNode,所以在开展的时候是须要获取 content 对象的理论高度,获取 DOM 对象高度的操作有一个库能够帮忙咱们,react-use-measure, 这个库不仅能够测量 DOM 对象的长度和宽度,还能够测量 DOM 对象间隔浏览器上下左右的地位。
react-use-measure提供了名为 useMeasure 的 hook,应用形式如下:

const [ref, bounds] = useMeasure();

第一个参数是 ref 对象,将其绑定到须要测量的 DOM 对象的 ref 属性上即可,第二个 bounds 就是地位对象,蕴含下面提到的所有属性。

持续革新组件代码Collapse.jsx

import React, {useState} from "react";
import {useSpring, animated} from "react-spring";
import useMeasure from 'react-use-measure'
import "./style.css";

const CollapsablePanel = () => {const [isCollapsed, setIsCollapsed] = useState(true);
    const [ref, bounds] = useMeasure();

    const togglePanel = () => {setIsCollapsed((prevState) => !prevState);
    };
    const panelContentAnimatedStyle = useSpring({height: isCollapsed ? 0 : bounds.height,});
    return (
        <div className="wrapper">
            <div className="pannel" onClick={togglePanel}>
                <div className="heading">
                    <span>Flower Collapse</span>
                    <svg width="20px"
                        height="25px" viewBox="0 0 1024 1024"
                        style={{color: '#6495ed'}}><path d="M64 351c0-8 3-16 9-22.2 12.3-12.7 32.6-13.1
                         45.3-0.8l394.1 380.5L905.7 328c12.7-12.3 33-12 45.3 0.7s12 33-0.7 45.3L534.7 
                         776c-12.4 12-32.1 12-44.5 0L73.8 374c-6.5-6.3-9.8-14.6-9.8-23z"p-id="1705">
                        </path></svg>
                </div>
                <animated.div

                    style={panelContentAnimatedStyle}
                    className="content"
                >
                    <div ref={ref} className="contentInner" >
                        Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
                        nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
                        erat, sed diam voluptua.
                        Lorem ipsum, dolor sit amet consectetur adipisicing elit.
                         Quasi aperiam dignissimos eaque deserunt expedita sit 
                         accusamus sunt laudantium repellendus nisi! Sit, 
                         consequuntur. Tempora, officiis molestiae 
                         fuga sit quae aliquid maxime.
                    </div>
                </animated.div>
            </div>
        </div>
    );
};

export default CollapsablePanel;

当初的成果:

应用 react-use-measure 能够十分不便的获取 DOM 对象的实在高度和在浏览器中的地位,在我的项目中灵活运用能够进步开发效率。

实现箭头图标旋转动画

箭头图标的旋转和内容区域的实现相似,只须要将其标签改成 animated.div,并将 useSpring 生成的款式对象绑定即可。
生成箭头 ICON 旋转动画 style 对象:

const toggleWrapperAnimatedStyle = useSpring({transform: isCollapsed ? "rotate(0deg)" : "rotate(180deg)",
  });

svg 对象外套一个 div,并绑定动画款式:

import React, {useState} from "react";
import {useSpring, animated} from "react-spring";
import useMeasure from 'react-use-measure'
import "./style.css";

const CollapsablePanel = () => {const [isCollapsed, setIsCollapsed] = useState(true);
    const [ref, bounds] = useMeasure();

    const togglePanel = () => {setIsCollapsed((prevState) => !prevState);
    };
    const panelContentAnimatedStyle = useSpring({height: isCollapsed ? 0 : bounds.height,});
    const toggleWrapperAnimatedStyle = useSpring({transform: isCollapsed ? "rotate(0deg)" : "rotate(180deg)",
    });
    return (
        <div className="wrapper">
            <div className="pannel" onClick={togglePanel}>
                <div className="heading">
                    <span>Flower Collapse</span>
                    <animated.div style={toggleWrapperAnimatedStyle}>
                        <svg width="20px"
                            height="25px" viewBox="0 0 1024 1024"
                            style={{color: '#6495ed'}}><path d="M64 351c0-8 3-16 9-22.2 12.3-12.7 32.6-13.1
                         45.3-0.8l394.1 380.5L905.7 328c12.7-12.3 33-12 45.3 0.7s12 33-0.7 45.3L534.7 
                         776c-12.4 12-32.1 12-44.5 0L73.8 374c-6.5-6.3-9.8-14.6-9.8-23z"p-id="1705">
                            </path></svg>
                    </animated.div>
                </div>
                <animated.div

                    style={panelContentAnimatedStyle}
                    className="content"
                >
                    <div ref={ref} className="contentInner" >
                        Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
                        nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam
                        erat, sed diam voluptua.
                    </div>
                </animated.div>
            </div>
        </div>
    );
};

export default CollapsablePanel;

能够看到如下成果:

总结

入手撸组件系列 第一篇文章抉择讲 Collapse 组件的实现,是因为这个组件的实现简略而且富裕趣味,读着能够领会到写组件的乐趣。一个简略的组件在实现的时候也有可能遇到问题,像如何获取 content 区域中的高度,就很有代表性。以后 React 及 Vue 的生态都异样凋敝,开发者在实现具体需要的时候要可能灵活运用这些开源库,以开发出简洁且功能强大的组件。

退出移动版