<Menu defaultIndex={'0'} onSelect={(index) => {alert(index)}} mode="vertical" defaultOpenSubMenus={['2']}> <MenuItem index={'0'}> cool link </MenuItem> <MenuItem index={'1'}> cool link 2 </MenuItem> <SubMenu title="dropdown"> <MenuItem index={'3'}> dropdown 1 </MenuItem> <MenuItem index={'4'}> dropdown 2 </MenuItem> </SubMenu> <MenuItem index={'2'}> cool link 3 </MenuItem> </Menu>
在这个组件中,我们用到了useState,别的因为涉及父组件传数据到子组件,所以还用到了useContext(父组件数据通报到子组件是指的父组件的index数据通报到子组件)。别的,我们还会演示利用自界说的onSelect来实现onClick成果(万一你引入React泛型不乐成,可能不知道该引入哪个React泛型,还可以用自界说的调停一下)。
如何写onSelect为了防备后头在代码的汪洋大海中难以找到onSelect,这里先简朴的抽出来做一个onSelect书写示例。好比我们在Menu组件中利用onSelect,它的利用方法和onClick看起来是一样的:
<Menu onSelect={(index) => {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.cloneElementReact.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<IMenuContext>({index: '0'}) const Menu: React.FC<MenuProps> = (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<MenuItemProps> 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 ( <ul className={classes} style={style}> <MenuContext.Provider value={passedContext}> {renderChildren()} </MenuContext.Provider> </ul> ) } Menu.defaultProps = { defaultIndex: '0', mode: 'horizontal', defaultOpenSubMenus: [] } export default Menu
然后是MenuItem子组件: