热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ReactHook的使用示例

这篇文章主要介绍了ReactHook的使用示例,帮助大家更好的理解和学习使用React,感兴趣的朋友可以了解下

这篇文章分享两个使用React Hook以及函数式组件开发的简单示例。

一个简单的组件案例

Button组件应该算是最简单的常用基础组件了吧。我们开发组件的时候期望它的基础样式能有一定程度的变化,这样就可以适用于不同场景了。第二点是我在之前做项目的时候写一个函数组件,但这个函数组件会写的很死板,也就是上面没有办法再绑定基本方法。即我只能写入我已有的方法,或者特性。希望编写Button组件,即使没有写onClick方法,我也希望能够使用那些自带的默认基本方法。

对于第一点,我们针对不同的className,来写不同的css,是比较好实现的。

第二点实现起略微困难。我们不能把Button的默认属性全部写一遍,如果能够把默认属性全部导入就好了。

事实上,React已经帮我们实现了这一点。React.ButtonHTMLAttributes里面就包含了默认的Button属性。可是我们又不能直接使用这个接口,因为我们的Button组件可能还有一些自定义的东西。对此,我们可以使用Typescript的交叉类型

type NativeButtOnProps= MyButtonProps & React.ButtonHTMLAttributes

此外,我们还需要使用resProps来导入其他非自定义的函数或属性。

下面是Button组件具体实现方案:

import React from 'react'
import classNames from 'classnames'

type ButtOnSize= 'large' | 'small'
type ButtOnType= 'primary' | 'default' | 'danger'

interface BaseButtonProps {
 className?: string;
 disabled?: boolean;
 size?: ButtonSize;
 btnType?: ButtonType;
 children?: React.ReactNode;
}

type NativeButtOnProps= BaseButtonProps & React.ButtonHTMLAttributes
const Button: React.FC= (props) => {
 const {
 btnType,
 className,
 disabled,
 size,
 children,
 // resProps用于取出所有剩余属性
 ...resProps
 } = props
 // btn, btn-lg, btn-primary
 const classes = classNames('btn', className, {
 [`btn-${btnType}`]: btnType,
 [`btn-${size}`]: size,
 'disabled': disabled
 })
 return (
 
 )
}

Button.defaultProps = {
 disabled: false,
 btnType: 'default'
}

export default Button

通过上面的方式,我们就可以在我们自定义的Button组件中使用比如onClick方法了。使用Button组件案例如下:




展示效果如下:

在这个代码中我们引入了一个新的npm package称之为classnames,具体使用方式可以参考GitHub Classnames,使用它就可以很方便实现className的扩展,它的一个简单使用示例如下:

classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'

// lots of arguments of various types
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'

// other falsy values are just ignored
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'

通过使用classNames,就可以很方便的在Button中添加个性化的属性。可以看到对于组件的HTML输出结果中有hahaclassName:


与此同时,我们上述代码方式也解决了自定义组件没有办法使用默认属性和方法问题。

更复杂的父子组件案例

接下来我们展示一下如何用函数组件完成一个菜单功能。这个菜单添加水平模式和垂直模式两种功能模式。点开某个菜单详情,将这个详情作为子组件。

当然,菜单这个功能根本就不需要父组件传数据到子组件(子组件指的是菜单详情),我们为了学习和演示如何将父组件数据传给子组件,强行给他添加这个功能。有点画蛇添足,大家理解一下就好。

首先介绍父子组件的功能描述。Menu是整体父组件,MenuItem是每一个具体的小菜单,SubMenu里面是可以点开的下拉菜单。

下图是展开后的样子:

整体代码结构如下:

 {alert(index)}} mode="vertical" defaultOpenSubMenus={['2']}>
 
 cool link
 
 
 cool link 2
 
 
 
  dropdown 1
 
 
  dropdown 2
 
 
 
 cool link 3
 

在这个组件中,我们用到了useState,另外因为涉及父组件传数据到子组件,所以还用到了useContext(父组件数据传递到子组件是指的父组件的index数据传递到子组件)。另外,我们还会演示使用自定义的onSelect来实现onClick功能(万一你引入React泛型不成功,或者不知道该引入哪个React泛型,还可以用自定义的补救一下)。

如何写onSelect

为了防止后面在代码的汪洋大海中难以找到onSelect,这里先简单的抽出来做一个onSelect书写示例。比如我们在Menu组件中使用onSelect,它的使用方式和onClick看起来是一样的:

 {alert(index)}}>

在具体这个Menu组件中具体使用onSelect可以这样写:

type SelectCallback = (selectedIndex: string) => void

interface MenuProps {
 onSelect?: SelectCallback;
}

实现handleClick的方法可以写成这样:

 const handleClick = (index: string) => {
 // onSelect是一个联合类型,可能存在,也可能不存在,对此需要做判断
 if (onSelect) {
  onSelect(index)
 }
 }

到时候要想把这个onSelect传递给子组件时,使用onSelect: handleClick绑定一下就好。(可能你没看太懂,我也不知道该咋写,后面会有整体代码分析,可能联合起来看会比较容易理解)

React.Children

在讲解具体代码之前,还要再说说几个小知识点,其中一个是React.Children。

React.Children 提供了用于处理 this.props.children 不透明数据结构的实用方法。

为什么我们会需要使用React.Children呢?是因为如果涉及到父组件数据传递到子组件时,可能需要对子组件进行二次遍历或者进一步处理。但是我们不能保证子组件是到底有没有,是一个还是两个或者多个。

this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined ;如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。所以,处理 this.props.children 的时候要小心[1]。

React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object[1]。

所以,如果有父子组件的话,如果需要进一步处理子组件的时候,我们可以使用React.Children来遍历,这样不会因为this.props.children类型变化而出错。

React.cloneElement

React.Children出现时往往可能伴随着React.cloneElement一起出现。因此,我们也需要介绍一下React.cloneElement。

在开发复杂组件中,经常会根据需要给子组件添加不同的功能或者显示效果,react 元素本身是不可变的 (immutable) 对象, props.children 事实上并不是 children 本身,它只是 children 的描述符 (descriptor) ,我们不能修改任何它的任何属性,只能读到其中的内容,因此 React.cloneElement 允许我们拷贝它的元素,并且修改或者添加新的 props 从而达到我们的目的[2]。

例如,有的时候我们需要对子元素做进一步处理,但因为React元素本身是不可变的,所以,我们需要对其克隆一份再做进一步处理。在这个Menu组件中,我们希望它的子组件只能是MenuItem或者是SubMenu两种类型,如果是其他类型就会报警告信息。具体来说,可以大致将代码写成这样:

if (displayName === 'MenuItem' || displayName === 'SubMenu') {
 // 以element元素为样本克隆并返回新的React元素,第一个参数是克隆样本
 return React.cloneElement(childElement, {
 index: index.toString()
 })
} else {
 console.error("Warning: Menu has a child which is not a MenuItem component")
}

父组件数据如何传递给子组件

通过使用Context来实现父组件数据传递给子组件。如果对Context不太熟悉的话,可以参考官方文档,Context,在父组件中我们通过createContext来创建Context,在子组件中通过useContext来获取Context。

index数据传递

Menu组件中实现父子组件中数据传递变量主要是index。

最后附上完整代码,首先是Menu父组件:

import React, { useState, createContext } from 'react'
import classNames from 'classnames'
import { MenuItemProps } from './menuItem'

type MenuMode = 'horizontal' | 'vertical'
type SelectCallback = (selectedIndex: string) => void

export interface MenuProps {
 defaultIndex?: string; // 用于哪个menu子组件是高亮显示
 className?: string;
 mode?: MenuMode;
 style?: React.CSSProperties;
 onSelect?: SelectCallback; // 点击子菜单时可以触发回调 
 defaultOpenSubMenus?: string[]; 
}

// 确定父组件传给子组件的数据类型
interface IMenuContext {
 index: string;
 onSelect?: SelectCallback;
 mode?: MenuMode;
 defaultOpenSubMenus?: string[]; // 需要将数据传给context
}

// 创建传递给子组件的context
// 泛型约束,因为index是要输入的值,所以这里写一个默认初始值
export const MenuCOntext= createContext({index: '0'})

const Menu: React.FC = (props) => {
 const { className, mode, style, children, defaultIndex, onSelect, defaultOpenSubMenus} = props
 // MenuItem处于active的状态应该是有且只有一个的,使用useState来控制其状态
 const [ currentActive, setActive ] = useState(defaultIndex)
 const classes = classNames('menu-demo', className, {
 'menu-vertical': mode === 'vertical',
 'menu-horizontal': mode === 'horizontal'
 })

 // 定义handleClick具体实现点击menuItem之后active变化
 const handleClick = (index: string) => {
 setActive(index)
 // onSelect是一个联合类型,可能存在,也可能不存在,对此需要做判断
 if (onSelect) {
  onSelect(index)
 }
 }

 // 点击子组件的时候,触发onSelect函数,更改高亮显示
 const passedContext: IMenuCOntext= {
 // currentActive是string | undefined类型,index是number类型,所以要做如下判断进一步明确类型
 index: currentActive ? currentActive : '0',
 onSelect: handleClick, // 回调函数,点击子组件时是否触发
 mode: mode,
 defaultOpenSubMenus,
 }

 const renderChildren = () => {
 return React.Children.map(children, (child, index) => {
  // child里面包含一大堆的类型,要想获得我们想要的类型来提供智能提示,需要使用类型断言  
  const childElement = child as React.FunctionComponentElement
  const { displayName } = childElement.type
  if (displayName === 'MenuItem' || displayName === 'SubMenu') {
  // 以element元素为样本克隆并返回新的React元素,第一个参数是克隆样本
  return React.cloneElement(childElement, {
   index: index.toString()
  })
  } else {
  console.error("Warning: Menu has a child which is not a MenuItem component")
  }
 })
 }
 return (
 
    {renderChildren()}
) } Menu.defaultProps = { defaultIndex: '0', mode: 'horizontal', defaultOpenSubMenus: [] } export default Menu

然后是MenuItem子组件:

import React from 'react'
import { useContext } from 'react'
import classNames from 'classnames'
import { MenuContext } from './menu'

export interface MenuItemProps {
 index: string;
 disabled?: boolean;
 className?: string;
 style?: React.CSSProperties;
}

const MenuItem: React.FC = (props) => {
 const { index, disabled, className, style, children } = props
 const cOntext= useContext(MenuContext)
 const classes = classNames('menu-item', className, {
 'is-disabled': disabled,
 // 实现高亮的具体逻辑
 'is-active': context.index === index
 })
 const handleClick = () => {
 // disabled之后就不能使用onSelect,index因为是可选的,所以可能不存在,需要用typeof来做一个判断
 if (context.onSelect && !disabled && (typeof index === 'string')) {
  context.onSelect(index)
 }
 }
 return (
 
  • {children}
  • ) } MenuItem.displayName = 'MenuItem' export default MenuItem

    最后是SubMenu子组件:

    import React, { useContext, FunctionComponentElement, useState } from 'react'
    import classNames from 'classnames'
    import { MenuContext } from './menu'
    import { MenuItemProps } from './menuItem'
    
    export interface SubMenuProps {
     index?: string;
     title: string;
     className?: string
    }
    
    const SubMenu: React.FC = ({ index, title, children, className }) => {
     const cOntext= useContext(MenuContext)
     // 接下来会使用string数组的一些方法,所以先进行类型断言,将其断言为string数组类型
     const openedSubMenus = context.defaultOpenSubMenus as Array
     // 使用include判断有没有index
     const isOpened = (index && context.mode === 'vertical') ? openedSubMenus.includes(index) : false
     const [ menuOpen, setOpen ] = useState(isOpened) // isOpened返回的会是true或者false,这样就是一个动态值
     const classes = classNames('menu-item submenu-item', className, {
     'is-active': context.index === index
     })
     // 用于实现显示或隐藏下拉菜单
     const handleClick = (e: React.MouseEvent) => {
     e.preventDefault()
     setOpen(!menuOpen)
     }
     let timer: any
     // toggle用于判断是打开还是关闭
     const handleMouse = (e: React.MouseEvent, toggle: boolean) => {
     clearTimeout(timer)
     e.preventDefault()
     timer = setTimeout(()=> {
      setOpen(toggle)
     }, 300)
     }
     // 三元表达式,纵向
     const clickEvents = context.mode === 'vertical' ? {
     onClick: handleClick
     } : {}
     const hoverEvents = context.mode === 'horizontal' ? {
     onMouseEnter: (e: React.MouseEvent) => { handleMouse(e, true) },
     onMouseLeave: (e: React.MouseEvent) => { handleMouse(e, false) },
     } : {}
    
     // 用于渲染下拉菜单中的内容
     // 返回两个值,第一个是child,第二个是index,用i表示
     const renderChildren = () => {
     const subMenuClasses = classNames('menu-submenu', {
      'menu-opened': menuOpen
     })
     // 下面功能用于实现在subMenu里只能有MenuItem
     const childrenCompOnent= React.Children.map(children, (child, i) => {
      const childElement = child as FunctionComponentElement
      if (childElement.type.displayName === 'MenuItem') {
      return React.cloneElement(childElement, {
       index: `${index}-${i}`
      })
      } else {
      console.error("Warning: SubMenu has a child which is not a MenuItem component")
      }
     })
     return (
      
      {childrenComponent}
    ) } return ( // 展开运算符,向里面添加功能,hover放在外面
  • {title}
    {renderChildren()}
  • ) } SubMenu.displayName = 'SubMenu' export default SubMenu

    参考资料

    • React.Children的用法
    • React.cloneElement 的使用

    以上就是React Hook的使用示例的详细内容,更多关于React Hook的使用的资料请关注其它相关文章!


    推荐阅读
    • React 小白初入门
      推荐学习:React官方文档:https:react.docschina.orgReact菜鸟教程:https:www.runoob.c ... [详细]
    • 必须先赞下国人npm库作品:node-images(https:github.comzhangyuanweinode-images),封装了跨平台的C++逻辑,形成nodejsAP ... [详细]
    • 业务:Payments&Risk大数据/AI/数据可视化时间要求:至少实习6个月,每周5天,入职时间4-5月 ... [详细]
    • Java验证码——kaptcha的使用配置及样式
      本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
    • 本文介绍了JavaScript进化到TypeScript的历史和背景,解释了TypeScript相对于JavaScript的优势和特点。作者分享了自己对TypeScript的观察和认识,并提到了在项目开发中使用TypeScript的好处。最后,作者表示对TypeScript进行尝试和探索的态度。 ... [详细]
    • 程序员如何选择机械键盘轴体?红轴和茶轴对比
      本文介绍了程序员如何选择机械键盘轴体,特别是红轴和茶轴的对比。同时还介绍了U盘安装Linux镜像的步骤,以及在Linux系统中安装软件的命令行操作。此外,还介绍了nodejs和npm的安装方法,以及在VSCode中安装和配置常用插件的方法。最后,还介绍了如何在GitHub上配置SSH密钥和git的基本配置。 ... [详细]
    • loader资源模块加载器webpack资源模块加载webpack内部(内部loader)默认只会处理javascript文件,也就是说它会把打包过程中所有遇到的 ... [详细]
    • npmrunbuild后dist文件夹下面直接浏览器打开index.html,css和js的路径都不正确。放到跟目录下就正常了,iis上同样只能在根目录下。我项目的目录如下: ... [详细]
    • RN即ReactNative基于React框架针对移动端的跨平台框架,在学习RN前建议最好熟悉下html,css,js,当然如果比较急,那就直接上手吧,毕竟用学习前面基础的时间,R ... [详细]
    • 前言:原本纠结于Web 模板,选了Handlebars。后来发现页面都是弱逻辑的,不支持复杂逻辑表达式。几乎要放弃之际,想起了Javascript中eval函数。虽然eval函 ... [详细]
    • .babelrc是用来设置转码规则和插件的,这种文件在window上无法直接创建,也无法在HBuilder中创建,甚至无法查看,但可以在sublimetext中创建、查看并编辑。当 ... [详细]
    • 《从零构建前后星散的web项目》:前端相识过关了吗?
      #前端基本架构和硬核引见手艺栈的挑选起首我们构建前端架构须要对前端生态圈有统统相识,而且最好带有肯定的手艺前瞻性,好的手艺架构能够日后会轻易的扩大,削减重构的次数,纵然重构也不须要 ... [详细]
    • 【node.js】关于node.js,如何解决npm should be run outside of the Node.js REPL, in your normal shell报错?
      问题描述前言,安装node方式采用的是安装包解压的报如下错误:npmshouldberunoutsideoftheNode.jsREPL,inyournormals ... [详细]
    • Webpack5内置处理图片资源的配置方法
      本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
    • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
      本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
    author-avatar
    yuan00911
    这个家伙很懒,什么也没留下!
    PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
    Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有