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

react hook 造轮子相关推荐

  1. 精读《怎么用 React Hooks 造轮子》

    1 引言 上周的 精读<React Hooks> 已经实现了对 React Hooks 的基本认知,也许你也看了 React Hooks 基本实现剖析(就是数组),但理解实现原理就可以用好 ...

  2. 【笔记-node】《Egg.js框架入门与实战》、《用 React+React Hook+Egg 造轮子 全栈开发旅游电商应用》

    20210226-20210227:<Egg.js框架入门与实战> 课程地址:https://www.imooc.com/learn/1185 第一章 课程导学 01-01 课程介绍 一. ...

  3. React造轮子:拖拽排序组件「Dragact」

    先来一张图看看: 项目地址:Github地址 (无耻求星!) 在线观看(第一次加载需要等几秒):预览地址 说起来不容易,人在国外没有过年一说,但是毕竟也是中国年,虽然不放假,但是家里总会主内一顿丰盛的 ...

  4. 造轮子是什么意思_精炼:我造轮子的秘诀

    过去的几年里,我一直在打造各式各样的编程相关的工具.这些工具有的是用于指导软件开发工作,有的是用来进行编程学习,还有的纯粹是为了提升技术而写的.在我写了越来越多的工具,接触了越来越多的工具思路之后.我 ...

  5. 程序员造轮子的正确姿势

    作者 | 黄峰达,CSDN 博客专家 Phodal 责编 | 唐小引 出品 | CSDN(ID:CSDNnews) 过去的几年里,我一直在打造各式各样的编程相关的工具.这些工具有的是用于指导软件开发工 ...

  6. 造轮子前先看看现有的开源轮子

    下次造轮子前先看看现有的轮子吧 值得学习的C语言开源项目 - 1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定 ...

  7. 来一起用 Vue3.0 造轮子

    Vue.js 3.0 "One Piece" 已正式发布,33% 的渲染速度提升.41% 的体积缩小.54% 的内存用量下降.代码可维护和可复用性提升--两个字来形容:真香! 你开 ...

  8. 【Vue3 造轮子项目 ------ kaite-ui】基于vue3.0 + vite + TypeScript 实现一个UI框架 - kaiteUI

    基于vue3.0 + vite + TypeScript 实现一个UI框架 - kaiteUI 前言 前段时间笔者一直忙于学习Vue3方面新知识,比如如何从vue2.0版本过渡到vue3.0,如何理解 ...

  9. 在React Hook里使用history.push跳转

    在React Hook里使用history.push跳转 react hook里用不了this.props.history的解决方法 首先引入 import { useHistory } from ' ...

最新文章

  1. R语言ggplot2可视化:使用scale_y_continous函数自定义指定Y轴的坐标范围以及对应的数值、将坐标轴的数值设置为需要的数据类型(整型、浮点型)
  2. 罗敏敏组建立新型脑-脑接口实现“阿凡达”式的跨鼠遥控
  3. OpenCascade Primitives BRep-Cylinder
  4. 3.Lasso线性模型
  5. 框架:Spring的自动装配
  6. subline text 快捷键
  7. ViewState 与 静态变量的 区别
  8. python里split_python中使用split()实现嵌套列表理解
  9. 漂浮广告代码html,漂浮广告的JS代码(经典)
  10. 直播 | 2017阿里双11在线技术峰会
  11. 2015年校园招聘12家IT公司面试体验
  12. 欧洲对 Splashtop 远程计算机实验室的需求增长十倍
  13. java.lang.IllegalArgumentException的问题解决
  14. 数据库原理-SQL Server版(期末复习)
  15. 施耐德电气举办金融数据中心研讨会,“大咖”齐聚共话智慧未来
  16. Android 设置应用的底部导航栏(虚拟按键)背景颜色
  17. 鲲鹏昇腾开发者峰会开幕 星辰天合发布新一代天合翔宇一体机
  18. 2005年应届大专生-----个人求职简历
  19. Nginx 配置301跳转
  20. 【matplotlib】/【plt】中文设置为宋体,英文为Time New Roman

热门文章

  1. 请编写函数实现自然底数 e=2.718281828
  2. php的本地地址,php获取本机ip或远道地址
  3. 指纹识别研究(一) 指纹的三级特征
  4. ElasticSearch入门一(索引CRD和文档的CRUD)
  5. 赠与今年的大学毕业生 (二)
  6. 任务调度:全网最全 xxl-job任务触发流程
  7. 2022哪些蓝牙耳机适合学生党?适合学生党的平价蓝牙耳机推荐
  8. 洗头冲水时冲下好些头发,我是要秃了吗?
  9. 【微信小程序】图片被压扁怎么办?在image中添加 mode=widthFix
  10. latex表格中如何画虚线