react hook 造轮子

article/2025/10/16 18:47:09

GitHub地址:https://github.com/rayhomie/rayhomieUI

一、sass的使用

1、@import方式引入scss文件,后面必须带后缀名scss

@import "main.scss";

2、Partials方式引入base.scss文件,文件必须以(下划线)开头,可以不用带后缀名

@import "_base";

sass @import和css @import命令的区别:

CSS @import 指令在每次调用时,都会创建一个额外的 HTTP 请求。但,Sass @import 指令将文件包含在 CSS 中,不需要额外的 HTTP 请求。

Sass Partials:如果你不希望将一个 Sass 的代码文件编译到一个 CSS 文件,你可以在文件名的开头添加一个下划线。这将告诉 Sass 不要将其编译到 CSS 文件。(partials只能当做模块导入,不能当做css文件来编译使用。)

例如:以下实例创建一个 _colors.scss 的文件,但是不会编译成 _colors.css 文件:

_colors.scss 文件代码:

$myPink: #EE82EE;
$myBlue: #4169E1;
$myGreen: #8FBC8F;

如果要导入该文件,则不需要使用下划线

实例:

@import "colors";
body {font-family: Helvetica, sans-serif;font-size: 18px;color: $myBlue;
}

注意:请不要将带下划线与不带下划线的同名文件放置在同一个目录下,比如,_colors.scss 和 colors.scss 不能同时存在于同一个目录下,否则带下划线的文件将会被忽略。

二、Button组件

  • 使用classnames和@types/classnames包对类名进行拼接
  • 使用字符串枚举类型定义声明props,使用时也需要导入enum类型常量进行使用组件
  • js对象中属性键名是动态变化的,需要使用[]括起来设置键名,可以用这种方法进行字符串拼接键名
  • 使用sass的@mixin和@include混入使用样式
  • 使用交叉类型,使用react提供的原生标签属性类型
import React, { useState } from 'react'
import classNames from 'classnames'export enum ButtonSize {Large = 'lg',Small = 'small'
}export enum ButtonType {Primary = 'primary',Default = 'default',Danger = 'danger',Link = 'link'
}interface BaseButtonProps {className?: stringdisabled?: booleansize?: ButtonSizebtnType?: ButtonTypechildren: React.ReactNode,href?: string//link有href才是有效的
}//为了让我们自定义的组件拥有button和a标签的原生React属性
type NativeButtonProps = BaseButtonProps & React.ButtonHTMLAttributes<HTMLElement>
//原生的button属性(react提供的)和 基本自定义属性 的交叉类型
type AnchorButtonProps = BaseButtonProps & React.AnchorHTMLAttributes<HTMLElement>
//原生的a标签属性(react提供的)和 基本自定义属性 的交叉类型
export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>
//可选的 button和a标签 的交叉类型const Button: React.FC<ButtonProps> = (props) => {//使用rest运算符把多传入的props取出来const { disabled, className, size, btnType, children, href, ...restProps } = props//需要安装classnames和@types/classnames包,对className进行拼接const classes = classNames('btn', className, {[`btn-${btnType}`]: btnType,//后面的值返回true加上类名,false不加[`btn-${size}`]: size,'disabled': (btnType === ButtonType['Link']) && disabled//如果是传入的props.btnTpye是Link类型,则加上一个disabled类名})if (btnType === ButtonType['Link'] && href) {//如果是link类型return (<aclassName={classes}href={href}{...restProps}//把剩余的props全部传入>{children}</a>)} else {//button类型return (<buttonclassName={classes}disabled={disabled}{...restProps}//把剩余的props全部传入>{children}</button>)}
}
Button.defaultProps = {disabled: false,btnType: ButtonType.Default,
}export default Button

外部使用组件:

import React from 'react';
import './styles/index.scss';
import Button, { ButtonSize, ButtonType } from './components/Button/index'
//使用组件时也需要导入*字符串枚举*来设置相应props的值,正常使用组件
function App() {return (<Button btnType={ButtonType.Danger} size={ButtonSize.Small}>按钮</Button>);
}export default App;

sass混入@mixin的使用:

使用@mixin和@include来重用重复的css代码

//定义mixin
@mixin button-size($padding-y, $padding-x, $font-size, $border-raduis) {padding: $padding-y $padding-x;font-size: $font-size;border-radius: $border-raduis;
}

@include使用mixin

@include button-size( $btn-padding-y,  $btn-padding-x,  $btn-font-size,  $border-radius);

测试用例:

 <ButtonbtnType={ButtonType.Default}size={ButtonSize.Small}>Default</Button><ButtonbtnType={ButtonType.Primary}size={ButtonSize.Small}>Primary</Button><ButtonbtnType={ButtonType.Primary}size={ButtonSize.Large}>Large Primary</Button><ButtonbtnType={ButtonType.Danger}size={ButtonSize.Small}>Danger</Button><ButtonbtnType={ButtonType.Default}size={ButtonSize.Small}disabled>disabled</Button><ButtonbtnType={ButtonType.Link}size={ButtonSize.Small}href='http://www.baidu.com/'>baidu Link</Button><ButtonbtnType={ButtonType.Link}size={ButtonSize.Small}href='http://www.baidu.com/'disabled>disabled Link</Button>

在这里插入图片描述

三、Alert组件

  • 使用react-transition-group编写动画过渡效果
  • 使用ts类型断言,对传入的可选props函数进行执行
import React, { useState } from 'react'
import classNames from 'classnames'
import { CSSTransition } from 'react-transition-group';export enum AlertType {Default = 'default',Success = 'success',Danger = 'danger',Warning = 'warning',
}
interface BaseAlertProps {className?: stringalertType?: AlertTypedescription?: string//描述title: string//标题closable?: boolean//是否显示关闭图标onClose?: () => void//关闭alert时触发的事件visible: boolean//显示状态
}const Alert: React.FC<BaseAlertProps> = (props) => {const { className, alertType, title, description, closable, onClose, visible } = propsconst classes = classNames('alt', className, {[`alt-${alertType}`]: alertType,})const closeIconClasses = classNames({'alt-close': closable//true就显示类名,false类名为null,执行alt-close-none})const onclose = onClose as () => void //类型断言return (<><CSSTransitionin={visible}//为true进入显示组件(主要通过in属性来控制组件状态)classNames="card"//设置类名的前缀timeout={400}//设置过渡动画事件unmountOnExit={true}//消失动画结束后 + display:none><divclassName={classes}><span className='alt-title'>{title}</span><p className='alt-description'>{description}</p><span className={closeIconClasses || 'alt-close-none'}onClick={() => {onclose()}}>关闭</span></div></CSSTransition></>)
}
Alert.defaultProps = {closable: true,alertType: AlertType.Default,onClose: () => { }
}
export default Alert

css的编写:

.card-enter,
.card-appear {opacity: 0;transform: scale(.8);
}.card-enter-active,
.card-appear-active {opacity: 1;transform: scale(1);transition: opacity 300ms, transform 300ms;
}.card-exit {opacity: 1;
}.card-exit-active {opacity: 0;transform: scale(.8);transition: opacity 300ms, transform 300ms;
}.alt {position: relative;padding: 0.75rem 1.25rem;margin-bottom: 1rem;border: 1px solid transparent;border-radius: 0.25rem;
}.alt-default {color: #fff;background: #0d6efd;border-color: #0262ef;
}.alt-success {color: #fff;background: #52c41a;border-color: #49ad17;
}.alt-danger {color: #fff;background: #dc3545;border-color: #d32535;
}.alt-warning {color: #fff;background: #fadb14;border-color: #efd005;
}.alt-title {}.alt-description {font-size: 0.875rem;margin: 0.3rem 0 0;
}.alt-close {position: absolute;top: 0;right: 0;padding: 0.75rem 1.25rem;color: inherit;cursor: pointer;
}.alt-close-none {display: none;
}

测试用例:

const [state, setState] = useState(false);<button onClick={() => { setState(!state) }}>显示</button><Alert alertType={AlertType.Default} title='Default' description='hhh' onClose={() => { setState(!state) }} visible={state}></Alert>
<Alert alertType={AlertType.Success} title='Success' visible></Alert>
<Alert alertType={AlertType.Danger} title='Danger' visible></Alert>
<Alert alertType={AlertType.Warning} title='Warning' closable={false} visible></Alert>

在这里插入图片描述

四、组件测试

Jest通用测试框架:断言库,Common Matchers

React专用测试工具:

①React Testing Library☆

  • 对组件编写测试用例,就像终端用户在使用它一样方便。

②Airbnb推出的Enzyme

  • 对react组件的输出进行断言、操控、遍历等。(类似于jquery的链式操作)

使用@testing-library/react进行组件测试:

//button.test.tsx
import React from 'react';
import { render } from '@testing-library/react';//使用测试框架render
import Button from './index';//导入测试组件test('our first react test case', () => {const wrapper = render(<Button>Nice</Button>)const element = wrapper.queryByText('Nice')expect(element).toBeTruthy()
})//在终端中输入npm run test进行测试

使用@testing-library/jest-dom进行dom断言测试:

1、在src下约定setupTests.ts文件中进行引入工具包

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

2、创建对应的单元测试xxx.test.tsx文件,就可以使用一些dom的断言进行测试

//button.test.tsx
import React from 'react';
import { render } from '@testing-library/react';//使用测试框架render
import Button from './index';//导入测试组件test('our first react test case', () => {const wrapper = render(<Button>Nice</Button>)const element = wrapper.queryByText('Nice')expect(element).toBeTruthy()
})//在终端中输入npm run test进行测试
import React from 'react';
import { render } from '@testing-library/react';//使用测试框架render
import Button from './index';//导入测试组件describe('test Button component', () => {it('should render the correct default button', () => {const wrapper = render(<Button>Nice</Button>)const element = wrapper.getByText('Nice')expect(element).toBeInTheDocument()expect(element.tagName).toEqual('BUTTON')expect(element).toHaveClass('btn btn-default')})it('should render the correct component based on different props', () => {const wrapper = render(<Button>hhh</Button>)const element = wrapper.getByText('hhh')expect(element).toBeInTheDocument()expect(element.tagName).toEqual('BUTTON')expect(element).toHaveProperty('disabled')})
})

五、Menu组件

两种方案:

//1.不完美的解决方案
const items = [{disabled:false,element :(<a>title</a>)},{disabled:true,element :'disabled link'}
];
<Menu defaultIndex={0} items={items} onSelect={} mode='vertical'>
</Menu>//2.更加语义化的解决方案,贴近于html
<Menu defaultIndex={0} onSelect={} mode='vertical'><Menu.Item><a>title</a></Menu.Item><Menu.Item disabled>disabled link</Menu.Item>
</Menu>
  • 使用context进行组件间传值
  • React.Children API 遍历传入的子节点进行优化
  • 使用组件displayName进行调试优化

组件Menu:

import React, { useState, createContext } from 'react'
import classNames from 'classnames';type MenuMode = 'horizontal' | 'vertical'
type selectCallback = (selectedIndex: number) => voidinterface MenuProps {//定义组件props类型defaultIndex?: number//默认被选中的索引值(默认0)mode?: MenuMode//横向|纵向(默认横向)onSelect?: selectCallback//点击选择之后的触发的函数className?: string//用户自定义的传入的classstyle?: React.CSSProperties//用户自定义组件的style传递给ul
}interface MenuContext {//定义context传递类型,子父组件间传值index: numberonSelect?: selectCallback
}
//导出创建的context供子组件使用且提供默认值
export const MenuContext = createContext<MenuContext>({ index: 0 })const Menu: React.FC<MenuProps> = (props) => {const { defaultIndex, mode, children, className, style, onSelect } = propsconst [Active, setActive] = useState(defaultIndex)//由父组件进行所有状态的维护const classes = classNames('menu', className, {'menu-vertical': mode === 'vertical'})const handleClick = (index: number) => {setActive(index)//维护状态改变if (onSelect) onSelect(index)//执行用户自定义传入的方法}//初始化需要共享的状态和修改的方法const passedContext: MenuContext = {index: Active || 0,//将状态共享onSelect: handleClick//将函数共享}//使用context所有的状态都由父组件进行控制return (<ul className={classes} style={style}><MenuContext.Provider value={passedContext}>{/*提供者*/}{children}</MenuContext.Provider></ul>)
}
Menu.defaultProps = {defaultIndex: 0,mode: 'horizontal'
}
export default Menu

子组件MenuItem:

import React, { useContext } from 'react'
import classNames from 'classnames';
import { MenuContext } from './index'interface MenuItemProps {index: number//每个item不用的索引值disabled?: boolean//是否可用className?: stringstyle?: React.CSSProperties
}const MenuItem: React.FC<MenuItemProps> = (props) => {const { index, disabled, className, style, children } = props;const context = useContext(MenuContext)//使用共享的contextconst classes = classNames('menu-item', className, {'is-disabled': disabled,'is-active': context.index === index})const handleClick = () => {//点击li触发onSelect方法并传递相应index给父组件if (context.onSelect && !disabled) context.onSelect(index)}return (<li className={classes} style={style} onClick={handleClick}>{children}</li>)
}
MenuItem.displayName = 'MenuItem'
export default MenuItem
测试用例:
 <Menu defaultIndex={0} onSelect={(i) => alert(i)} mode='vertical'><MenuItem index={0}>cool link1</MenuItem><MenuItem index={1}>cool link2</MenuItem><MenuItem index={2}>cool link3</MenuItem><MenuItem index={3} disabled>cool link4</MenuItem>
</Menu>
<Menu defaultIndex={0} onSelect={(i) => alert(i)}><MenuItem index={0}>cool link1</MenuItem><MenuItem index={1}>cool link2</MenuItem><MenuItem index={2}>cool link3</MenuItem><MenuItem index={3} disabled>cool link4</MenuItem>
</Menu>

在这里插入图片描述

组件优化:

  • <Menu>子节点只能使用<MenuItem>
  • 子节点<MenuItem>的index属性可选且不选时默认值为顺序索引(0,1,2…n)

注意:在传入的props的children直接使用数组map方法是非常危险的事情(因为props属性的数据结构不透明,未知)

react提供了两个方法去循环children:①React.Children.mapReact.Children.forEach

const Menu: React.FC<MenuProps> = (props) => {const renderChildren = () => {return React.Children.map(children, (child, index) => {const childElement = child as React.FunctionComponentElement<MenuItemProps>//类型断言const { displayName } = childElement.type//取出child的displayNameif (displayName === 'MenuItem') {//取出每个child节点的displayName和MenuItem作对比return React.cloneElement(childElement, { index })//如果是MenuItem就拷贝该子节点再添加index属性并输出} else {//如果标签不是MenuItem就报错,不输出该child值console.error('Warning: Menu has a child which is not a MenuItem component')}})}return (<ul className={classes} style={style}><MenuContext.Provider value={passedContext}>{renderChildren()}</MenuContext.Provider></ul>)
}

我们还需要在遍历children时给子组件child自动顺序添加index属性,所以使用React.cloneElement API来克隆元素的同时添加属性

测试用例:
 <Menu defaultIndex={0} onSelect={(i) => alert(i)} mode='vertical'><MenuItem>cool link1</MenuItem><MenuItem>cool link2</MenuItem><MenuItem>cool link3</MenuItem><MenuItem disabled>cool link4</MenuItem><li>1111</li>
</Menu>

在这里插入图片描述

子组件SubMenu:

(用于下拉菜单)

  • 防抖来控制动画流畅
  • 类名不同来控制display:none|block
  • jsx标签可以{…xxx}来设置属性值
import React, { useContext, useState, FunctionComponentElement } from 'react'
import classNames from 'classnames'
import { MenuContext } from './index'
import { MenuItemProps } from './MenuItem'export interface SubMenuProps {index?: numbertitle: stringclassName?: string
}
const SubMenu: React.FC<SubMenuProps> = (props) => {const [open, setOpen] = useState(false)//控制开关const { index, title, className, children } = propsconst context = useContext(MenuContext)const classes = classNames('menu-item submenu-item', className, {'is-active': context.index === index})const handleClick = (e: React.MouseEvent) => {//纵向时点击控制e.preventDefault()setOpen(!open)}let timer: any//开闭更圆滑,防抖const handleMouse = (e: React.MouseEvent, toggle: boolean) => {//横向时hover控制clearTimeout(timer)e.preventDefault()timer = setTimeout(() => {setOpen(toggle)}, 300)}const clickEvents = context.mode === 'vertical' ? {//纵向时点击控制onClick: handleClick} : {}const hoverEvents = context.mode !== 'vertical' ? {//横向时hover控制onMouseEnter: (e: React.MouseEvent) => { handleMouse(e, true) },onMouseLeave: (e: React.MouseEvent) => { handleMouse(e, false) }} : {}const renderChildren = () => {const subMenuClasses = classNames('viking-submenu', {'menu-opened': open//通过display:none|block来控制})const childrenComponent = React.Children.map(children, (child, index) => {const childElement = child as React.FunctionComponentElement<MenuItemProps>//类型断言if (childElement.type.displayName === 'MenuItem') {//SubMenu的子节点只能是MenuItemreturn childElement} else {console.error('Warning: Menu has a child which is not a MenuItem component')}})return (<ul className={subMenuClasses}>{childrenComponent}</ul>)}return (<li key={index} className={classes} {...hoverEvents}><div className="submenu-title" {...clickEvents}>{title}</div>{renderChildren()}</li>)
}
SubMenu.displayName = 'SubMenu'
export default SubMenu
测试用例:
//纵向的menu
<Menu defaultIndex={0} onSelect={(i) => alert(i)} mode='vertical'><MenuItem>cool link1</MenuItem><MenuItem>cool link2</MenuItem><SubMenu title='cool link3'><MenuItem>cool link3.1</MenuItem></SubMenu><MenuItem disabled>cool link4</MenuItem><li>1111</li>
</Menu>
//横向的menu
<Menu defaultIndex={0} onSelect={(i) => alert(i)}><MenuItem>cool link1</MenuItem><MenuItem>cool link2</MenuItem><SubMenu title='cool link3'><MenuItem>cool link3.1</MenuItem></SubMenu><MenuItem disabled>cool link4</MenuItem>
</Menu>

在这里插入图片描述

SubMenu的index问题:

因为Menu组件分成了上层组件构成外层Menu、中层SubMenu、内存MenuItem。

我们还需要把index传递给所以的内层组件,把每个内层组件给index排序。

  • index不使用number,而使用字符串:以"n-n"的形式表示

在这里插入图片描述

六、Tabs组件

和Menu组件差不多的实现,注意修改一下css,效果如下:

在这里插入图片描述

七、Icon组件

fontawesome

react-fontawesome

npm i --save @fortawesome/fontawesome-svg-core \@fortawesome/free-solid-svg-icons \@fortawesome/react-fontawesome

用例:

//方式一:导入对象变量的形式引入
import { faCoffee } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';  
<FontAwesomeIcon icon={faCoffee} size='6x' rotation={180} spin border />
<FontAwesomeIcon icon={faCoffee} size='6x' rotation={180} pulse border />
//spin旋转动画、pulse像脉搏一样跳动、rotation旋转度数、border加上边框...等等

在这里插入图片描述
在这里插入图片描述

//方式二:字符串的形式引入
import { fas } from '@fortawesome/free-solid-svg-icons';//fas是导入全部图标
import { library } from '@fortawesome/fontawesome-svg-core';
library.add(fas);//library进行管理,fas是全部图标
<FontAwesomeIcon icon='coffee' size='6x' rotation={180} pulse border />
<FontAwesomeIcon icon='arrow-down' size='lg' rotation={180} border />

在这里插入图片描述

二次封装react-rontawesome组件

//对react-fontawesome库进行二层封装
import React from 'react'
import classNames from 'classnames'
import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome'//定义自定义主题颜色
export type ThemeProps = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger'
interface IconProps extends FontAwesomeIconProps {//继承react-fontawesome库暴露出来组件propstheme?: ThemePropsclassName?: string
}
const Icon: React.FC<IconProps> = (props) => {const { className, theme, ...restProps } = propsconst classes = classNames('icon', className, {[`icon-${theme}`]: theme,})return (<FontAwesomeIcon className={classes} {...restProps} />)
}
export default Icon

使用sass的@each方法进行循环变量,写样式:

//示例:
$sizes: 40px, 50px, 80px;
//循环遍历$sizes变量
@each $size in $sizes {.icon-#{$size} {font-size: $size;height: $size;width: $size;}
}

设置自定义类样式:

//Icon_style.scss
$theme-colors: ("primary": $primary,"secondary": $secondary,"success": $success,"info": $info,"warning": $warning,"danger": $danger,"light": $light,"dark": $dark);@each $key, $val in $theme-colors {.icon-#{$key} {color: $val;}
}

测试用例:

<Icon icon='arrow-down' size='6x' rotation={180} border theme='success' />
<Icon icon='arrow-down' size='6x' rotation={180} border theme='danger' />

在这里插入图片描述

使用react-transition-group写动效

CSSTransition组件设置*号对应的类名,然后按照以下方式进行书写动效样式:

自定义Transition组件编码

用于复用动画效果

//由之前的CSSTransition组件
<CSSTransition in={state}timeout={300}classNames='card'appear //appear生效unmoutOnExit //动画结束时display:none>{node}
</CSSTransition>
// 自定义复用之后
<Transitionin={state}timeout={300}animation='zoom-in-top'//字符串字面量,自定义预设的动画>{node}
</Transition>
写一个Transition自定义组件包裹CSSTransition组件
import React from 'react'
import { CSSTransition } from 'react-transition-group';
import { CSSTransitionProps } from 'react-transition-group/CSSTransition';type AnimationName = 'zoom-in-top' | 'zoom-in-left' | 'zoom-in-bottom'type TransitionProps = CSSTransitionProps & {//继承CSSTransition的属性animation?: AnimationName//新增加一个字面量属性值
}const Transition: React.FC<TransitionProps> = (props) => {const { children, classNames, animation, ...restProps } = propsreturn (<CSSTransition //如果传入了classNames属性,就使用classNames属性,不使用自定义的animationclassNames={classNames ? classNames : animation}{...restProps}//把剩余的props全部传入>{ children}</CSSTransition >)
}
Transition.defaultProps = {//默认propsunmountOnExit: true,appear: true
}
export default Transition

测试用例:我们用之前写的Alert组件测试

//这是之前的用法
<CSSTransitionin={visible}//为true进入显示组件(主要通过in属性来控制组件状态)classNames="card"//设置类名的前缀timeout={400}//设置过渡动画事件unmountOnExit={true}//消失动画结束后 + display:none><divclassName={classes}><span className='alt-title'>{title}</span><p className='alt-description'>{description}</p><span className={closeIconClasses || 'alt-close-none'}onClick={() => {onclose()}}>关闭</span></div>
</CSSTransition>

在这里插入图片描述

这是使用自定义Transition组件进行优化

<Transitionin={visible}//为true进入显示组件(主要通过in属性来控制组件状态)animation='zoom-in-left'//使用我们自定义的animationtimeout={400}//设置过渡动画事件><divclassName={classes}><span className='alt-title'>{title}</span><p className='alt-description'>{description}</p><span className={closeIconClasses || 'alt-close-none'}onClick={() => {onclose()}}>关闭</span></div>
</Transition>

在这里插入图片描述

八、Input组件

设计Input组件需要设置的属性

<Inputdisabledsize='lg|sm'icon='fontawesome 支持的图标'prepand='前缀 string|ReactElement'append='后缀 string|ReactElement'{...restProps}//支持其他所有的 HTMLInput 属性/>

九、Pagination组件

import React, { useState, useEffect, useRef, useMemo } from 'react'
import classnames from 'classnames'
import Transition from '../Transition/Transition'interface PaginationProps {className?: stringstyle?: React.CSSPropertiespageSize: number//每页大小current?: number//指针total: number//总条数disabled?: boolean//是否禁用showQuickJumper?: boolean//是否用快速跳转输入框onChange?: (next: number) => void//页码变化时回调
}const Pagination: React.FC<PaginationProps> = (props) => {const {className,style,pageSize,current,total,disabled,onChange,showQuickJumper} = propsconst [cur, setCur] = useState(current)//当前的页码状态const [jumperTopic, setJumperTopic] = useState(false)//focus控制显示(输入后回车框)useEffect(() => {//当cur变化后实时获取到cur的值if (onChange && cur) {onChange(cur)//执行传入的回调}}, [cur])//获取页数const getPageNum = (total: number, pageSize: number): number => {return Math.ceil(total / pageSize)}//useMemo缓存优化获取页数const pageNum = useMemo(() => getPageNum(total, pageSize), [total, pageSize])const generateList = (pageNum: number) => {if (cur)//cur可能为undefined,默认值为1return new Array(pageNum).fill('').map((item, index) => {//长度和填充页数相等的数组return (<><divclassName={classnames('item', {'active': cur === index + 1,//点击态'hidden': pageNum > 5 && cur <= 3 && index + 1 > 5//点击1、2、3时大于5的页码都隐藏显示|| pageNum > 5 && cur >= pageNum - 2 && index + 1 < pageNum - 4//点击倒数1,2,3时,小于倒数第四的页码隐藏|| pageNum > 5 && cur < pageNum - 2 && cur > 3 && (index + 1 > cur + 2 || index + 1 < cur - 2),//点击4~n-3时,显示cur附近的(一共五个)'show': pageNum > 5 && (index + 1 === pageNum || index + 1 === 1),//一头一尾总是显示disabled,'active-disabled': cur === index + 1 && disabled})}key={index}onClick={() => {setCur(index + 1)}}>{index + 1}</div><divclassName={classnames('item', {//控制...的显示和不显示'hidden': pageNum > 0,'show': pageNum > 5 && cur > 4 && index + 1 === 1|| pageNum > 5 && cur < pageNum - 3 && index + 1 === pageNum - 1,disabled})}onClick={() => {if (pageNum > 5 && cur > 4 && index + 1 === 1) {if (cur === 5) {//解决bug最前面的...(当cur为5时点击...变成1才对)setCur(cur - 4)} else { setCur(cur - 5) }}if (pageNum > 5 && cur < pageNum - 3 && index + 1 === pageNum - 1) {if (cur === pageNum - 4) {//当cur为n-4时点击...变成n才对setCur(cur + 4)} else { setCur(cur + 5) }}}}>...</div></>)})}const handlePrev = () => {if (cur && cur > 1) {setCur(cur - 1)}}const handleNext = () => {if (cur && cur < pageNum) {setCur(cur + 1)}}const inputRef = useRef<HTMLInputElement>(document.createElement("input"))return (<divclassName={classnames('generateList', className, {disabled})}style={style}><divclassName={classnames('item', {disabled})}onClick={handlePrev}>{'<'}</div>{generateList(pageNum)}<divclassName={classnames('item', {disabled})}onClick={handleNext}>{'>'}</div>{showQuickJumper ? <div style={{ marginLeft: '20px' }} id='jump'><div className='main-jumperTopic'>跳至<inputclassName={classnames('quickJumper', {disabled})}type="text"ref={inputRef}//ref保存当前Input节点onChange={(e) => {inputRef.current.value = e.target.value}}onKeyDown={(e) => {if (e.keyCode === 13) {//确认的时候跳转const value = Number(inputRef.current.value)if (value > 0 && value <= pageNum) {setCur(value)}inputRef.current.value = ''};}}onFocus={() => {setJumperTopic(true)}}onBlur={() => {setJumperTopic(false)}}/><Transitionin={jumperTopic}//控制动画animation='zoom-in-bottom'timeout={300}className='Topic'><div>输入后回车</div></Transition>页</div></div> : <></>}</div>)
}Pagination.defaultProps = {current: 1
}
export default Pagination
测试用例:
import React, { useState, useEffect } from 'react'
import Pagination from './components/Pagination/Pagination';
interface Props {}
const MOCK_DATA = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11, 12, 13, 14, 15, 16, 17, 18, 19, 20,21, 22, 23, 24, 25, 26, 27, 28, 29, 30,31, 32, 33, 34, 35, 36, 37, 38, 39, 40,41, 42, 43, 44, 45, 46, 47, 48, 49, 50,51, 52, 53, 54, 55, 56, 57, 58, 59, 60,61, 62, 63, 64, 65, 66, 67, 68, 69, 70,71, 72, 73, 74, 75, 76, 77, 78, 79, 80,81, 82, 83, 84, 85, 86, 87, 88, 89, 90,91, 92]const PAGE_SIZE = 10const searchPage = (current: number, pageSize: number, sourceData: any[]) => {return sourceData.slice(pageSize * (current - 1), pageSize * current)}const PaginationTest: React.FC<Props> = (props) => {useEffect(() => {const init = searchPage(1, PAGE_SIZE, MOCK_DATA)setData(init)}, [])const [data, setData] = useState<any[]>([])return (<><Paginationtotal={MOCK_DATA.length}pageSize={PAGE_SIZE}className='hhh'showQuickJumperonChange={(p) => {setData(searchPage(p, PAGE_SIZE, MOCK_DATA));}}/><div>{data.map((i) => <div>{i}</div>)}</div></>)
}
export default PaginationTest

在这里插入图片描述


http://chatgpt.dhexx.cn/article/0IGdKqGi.shtml

相关文章

「轮子工厂」谭庆波,很高兴认识大家!

我是谁&#xff1a; 我叫谭庆波&#xff0c;网名厂长。 首先我是一名学生&#xff0c;哈工大计算系的博士生&#xff0c;也是一个喜欢在互联网上折腾的95后。 2015年开始写博客&#xff0c;累计输出博客500多篇&#xff0c;访问量超百万&#xff1b; 2018年开始做公众号和知乎&…

前端轮子厂

1. Element Element-Ul是饿了么前端团队推出的一款基于Vue.js 2.0 的桌面端UI框架&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库&#xff0c;手机端有对应框架是Mint UI 。 中文文档&#xff1a;http://element-cn.eleme.io/#/zh-CN github地址…

前端_Vue_1.初识Vue

文章目录 一、前言二、开始1 简介1.1 什么是Vue&#xff1f;1.2 渐进式框架1.3 单文件组件1.4. API风格1.4.1. 选项式API&#xff08;Options API&#xff09;1.4.2. 组合式API&#xff08;Composition API&#xff09;1.4.3. 该选哪个&#xff1f; 2. 快速上手&#xff08;学前…

Nginx-学习一

什么是Nginx Nginx是一个http服务器。 是一个使用c语言开发的高性能的http服务器及反向代理服务器。 Nginx是一款高性能的http 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器。由俄罗斯的程序设计师Igor Sysoev所开发&#xff0c;官方测试ngin…

Nginx学习二

文章目录 一、proxy_set_header1.1、<span class"katex--inline">http\_host与</span>host区别1、在使用Nginx做反向代理的时候&#xff0c;proxy_set_header功能可以设置反向代理后的http header中的host&#xff0c;1.1.1、 不设置 proxy_set_header H…

nginx学习一

nginx 安装&#xff1a; 1、 ./configure 如果报错 error: C compiler cc is not found 如果没有安装就安装:yum install –y gcc 如果还报错看看是不是安装: yum install –y zlib zlib-devel 2、 安装 make 3、 make install 4、 安装完后可以查看到目录 5、 启动 nginx …

nginx学习记录

一、docker安装nginx和配置文件 &#xff08;1&#xff09;docker拉取镜像 docker pull nginx &#xff08;2&#xff09;创建容器 # 在/root⽬录下创建nginx⽬录⽤于存储nginx数据信息 mkdir ~/nginx cd ~/nginx mkdir conf cd conf # 在~/nginx/conf/下创建nginx.conf⽂件…

尚硅谷nginx学习笔记

尚硅谷nginx学习笔记 1.nginx相关概念1.1什么是nginx&#xff1f;1.2正向和反向代理1.3负载均衡1.4动静分离 2.nginx在linux中的安装与启动3.nginx的常用命令4.nginx的配置文件5.nginx配置实例5.1反向代理准备工作5.2反向代理实例一5.3反向代理实例二5.4负载均衡实例5.5动静分离…

Nginx学习(一)

系统版本CentOS Linux release 7.6.1810 (Core) Nginx作用 反向代理正向代理负载均衡HTTP服务器(包含动静分离) 环境确认 1.关闭iptables规则 如果启动的iptables防火墙不想关闭的话&#xff0c;可以通过iptables -F 来清除防火墙关闭。然后通过iptables -L查看 2.停用seli…

NGINX学习记录-基础入门篇

学习《NGINX 经典教程》林静&#xff0c;刘旭峰&#xff0c;章澍&#xff0c;廖健雄&#xff0c;宗兆伟 ... 著 目录 一、Nginx优点 二、功能(应用场景) 反向代理。 负载均衡 静态缓存 Web服务器 安全和访问控制 三、nginx安装 四、启动、停止和重载nginx 五、配置文…

Nginx学习总结(1):Nginx简介

(最近&#xff0c;部门组织了好几个技术兴趣小组&#xff0c;对当前的热门技术进行研究。我加入了Nginx学习小组&#xff0c;与几个同事一道围绕Nginx来进行研究和学习。从今天起&#xff0c;我会陆陆续续发一系列有关Nginx的学习总结。本文是系列之一&#xff1a;Nginx简介。)…

学习nginx

Nginx笔记 安装nginxwget -c https://nginx.org/download/nginx-1.20.1.tar.gz1、创建软链路ls /usr/local/nginx/sbin/nginx /usr/bin/ 2、前端部署配置 Conf 在这里插入图片描述 3、启动服务 由于软链路 可以直接 nginx 启动&#xff08;任意目录&#xff09; 停止 nginx -s…

nginx学习日记

nginx介绍 1.什么是nginx? nginx就是c语言开发的一个高性能HTTP和反向代理Web服务器以及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器 nginx是轻量级的。 2.什么是反向代理&#xff1f; 代理服务可以简单的分为正向代理和反向代理。 正向代理&#xff1a;就是代理…

Nginx学习笔记总结:初次认识 Nginx

2022 年 4月 16 日 百思不得小赵 &#x1f50d;点此进入博客主页 —— 新时代的农民工 &#x1f64a; —— 换一种思维逻辑去看待这个世界 &#x1f440; 今天是加入CSDN的第1141天。觉得有帮助麻烦&#x1f44f;点赞、&#x1f340;评论、❤️收藏啦。 概述 Nginx是一个高性能…

Nginx服务器学习

学习思维导图 Nginx服务架构说明 Nginx采用master-worker的架构&#xff0c;和tomacat的按路径匹配一个节点线程进行处理方式不同&#xff0c;Nginx是直接让worker来先抢夺资源也就是请求&#xff0c;然后在去处理&#xff0c;每个worker保持为一个独立的进程。 Nginx服务的实际…

nginx学习,看这一篇就够了

nginx学习&#xff0c;看这一篇就够了&#xff1a;下载、安装。使用&#xff1a;正向代理、反向代理、负载均衡。常用命令和配置文件,很全_冯安晨-CSDN博客_nginx 代理 文件下载文章目录前言一、nginx简介1. 什么是 nginx 和可以做什么事情2.Nginx 作为 web 服务器3. 正向代理4…

Nginx学习+安装

目录 一、Nginx介绍 二、Nginx下载和安装 1.安装过程 三、了解目录结构 小知识点&#xff1a;树形结构目录展示 四、常用命令 五、配置文件结构 六、具体应用 1.部署静态资源 2.反向代理 了解正向代理 反向代理 3.负载均衡 重中之重&#xff1a;更详细的内容可以访…

Nginx学习与安装

Nginx学习与安装 一、Nginx介绍二、Nginx 安装2.1 安装 pcre-8.44.tar.gz2.2 安装openssl与zlib2.3 安装nginx2.4 Nginx 命令 Nginx 配置文件 一、Nginx介绍 是一个高性能的HTTP和反向代理服务器&#xff0c;同时也是一个IMAP/POP3/SMTP 代理服务器。Nginx以事件驱动的方式编写…

手把手学习nginx基本配置

相信很多人都听过nginx&#xff0c;这个小巧的东西慢慢地在吞食apache和IIS的份额。那究竟它有什么作用呢&#xff1f;可能很多人未必了解。 说到反向代理&#xff0c;可能很多人都听说&#xff0c;但具体什么是反向代理&#xff0c;很多人估计就不清楚了。摘一段百度百科上的描…

Nginx学习整理|入门记录

目录 1. Nginx概述 1.1 Nginx介绍 1.2 Nginx下载和安装 1.3 Nginx目录结构 2. Nginx命令 3. Nginx配置文件结构 4. Nginx具体应用 4.1 部署静态资源 4.2 反向代理 4.3 负载均衡 1. Nginx概述 1.1 Nginx介绍 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件…