前言
本文是作者写的关于组件设计的第六篇文章。 内容由易到难。 明天我们将使用react的中间APIReactPortals,这也是很多复杂组件必须使用的技能之一。 通过组件设计过程,你将接触到一个完整的、强大的组件设计思想和方法,并在实现组件的过程中逐渐对react/vue的中间知识和方法有更深入的理解和掌握,并能够在企业开展实际工作游刃有余。
我之所以写组件设计相关的文章,是因为作为一名优秀的后端工程师,面对各种繁琐、重复的工作css3 关闭按钮,我们不应该按部就班地“苦干”,而应该根据现有的后端——最终开发经验,总结出一套自己的高效开发方法。
作为数据驱动的引领者,react/vue等MVVM框架的出现帮助我们在工作中减少了大量冗余代码,一切皆组件的理念深入人心。 为了让工程师有更多的时间考虑业务和产品迭代,我们必须掌握高质量组件设计的思路和技巧。 因此,笔者会花时间总结各种业务场景下组件的设计思路和方法,并利用原生框架的句型来实现各种常用组件的开发。 希望等待后端菜鸟或者有一定工作经验的同事能够有所收获。
如果你不熟悉react/vue组件的设计原理,可以参考我之前的组件设计系列文章:
文本
在开始组件设计之前,希望你有一定的css3和js基础,了解基本的react/vue句型。 我们来看看组件实现后的效果:
1. 组件设计思路
根据笔者之前总结的组件设计原则,我们第一步就是确认需求。 一个抽屉(Drawer)组件会有以下要求:
收集完需求后,作为一名追求者,你将得到如下线框图:
对于react玩家来说,如果你不会使用typescript,我建议大家都使用PropTypes,它是react的外部类型检查工具,我们可以直接在项目中导出。 Vue有自己的属性测量方法,这里就不一一介绍了。
通过上面的需求分析,你是不是觉得一个抽屉组件要实现这样的功能是不是很复杂呢? 确实有点复杂,不要害怕。 有了前面精准的需求分析,我们只需要按照功能点一步步实现即可。 对于我们常用的表格组件、模态组件等需要考虑很多使用场景和功能点。 例如antd的table组件就暴露了几十个属性。 如果具体需求没有明确清楚,那么实现这样的组件是非常麻烦的。 。 我们来看看具体的实现。
2.基于react 2.1实现一个Drawer组件。 抽屉组件框架设计
首先我们先根据需求写组件框架,这样前面写的业务逻辑会更清晰:
import PropTypes from 'prop-types'
import styles from './index.less'
/**
* Drawer 抽屉组件
* @param {visible} bool 抽屉是否可见
* @param {closable} bool 是否显示右上角的关闭按钮
* @param {destroyOnClose} bool 关闭时销毁里面的子元素
* @param {getContainer} HTMLElement 指定 Drawer 挂载的 HTML 节点, false 为挂载在当前 dom
* @param {maskClosable} bool 点击蒙层是否允许关闭抽屉
* @param {mask} bool 是否展示遮罩
* @param {drawerStyle} object 用来设置抽屉弹出层样式
* @param {width} number|string 弹出层宽度
* @param {zIndex} number 弹出层层级
* @param {placement} string 抽屉方向
* @param {onClose} string 点击关闭时的回调
*/
function Drawer(props) {
const {
closable = true,
destroyOnClose,
getContainer = document.body,
maskClosable = true,
mask = true,
drawerStyle,
width = '300px',
zIndex = 10,
placement = 'right',
onClose,
children
} = props
const childDom = (
<div className={styles.xDrawerWrap}>
<div className={styles.xDrawerMask} ></div>
<div
className={styles.xDrawerContent}
{
children
}
{
!!closable && X</span>
}
</div>
</div>
)
return childDom
}
export default Drawer
有了这个框架,我们就一步步实现内容吧。
2.2 实现visible、closeable、onClose、mask、maskClosable、width、zIndex、drawerStyle
之所以先实现这些功能,是因为它们实现起来比较简单,不涉及其他复杂的逻辑。 您只需要向外界公开和使用属性即可。 具体实现如下:
function Drawer(props) {
const {
closable = true,
destroyOnClose,
getContainer = document.body,
maskClosable = true,
mask = true,
drawerStyle,
width = '300px',
zIndex = 10,
placement = 'right',
onClose,
children
} = props
let [visible, setVisible] = useState(props.visible)
const handleClose = () => {
setVisible(false)
onClose && onClose()
}
useEffect(() => {
setVisible(props.visible)
}, [props.visible])
const childDom = (
<div
className={styles.xDrawerWrap}
style={{
width: visible ? '100%' : '0',
zIndex
}}
>
{ !!mask && <div className={styles.xDrawerMask} onClick={maskClosable ? handleClose : null}></div> }
<div
className={styles.xDrawerContent}
style={{
width,
...drawerStyle
}}>
{ children }
{
!!closable && <span className={styles.xCloseBtn} onClick={handleClose}>X</span>
}
</div>
</div>
)
return childDom
}
上述实现过程中值得注意的是,我们的组件设计采用了reacthooks技术,这里使用了useState和useEffect。 如果不懂的话可以去官网学习。 这很简单。 如果有不懂的可以和作者交流或者在评论区发帖提问。 抽屉动画是通过控制抽屉内容的长度来实现的。 对于overflow:hidden,我会单独附上css代码供大家参考。
2.3 实现destroyOnClose
destroyOnClose主要用于消除组件缓存。 更常见的场景是输入文本。 例如,当我的抽屉的内容是表单创建页面时,我们关闭抽屉,希望用户在表单中输入的内容被清除,以保证上次进入的用户可以重新创建它,而实际情况是,如果我们不销毁抽屉里的子组件,子组件的内容就不会被清除,而用户上次打开时又开始了之前的输入,这显然是不合理的。 如右图所示:
为了消除缓存,内部组件首先必须重新渲染,所以我们可以通过一个状态来控制它。 如果用户明确指定该组件在关闭时应该被销毁,那么我们就会更新状态,所以这个子元素不会有缓存。 具体实现如下:
function Drawer(props) {
// ...
let [isDesChild, setIsDesChild] = useState(false)
const handleClose = () => {
// ...
if(destroyOnClose) {
setIsDesChild(true)
}
}
useEffect(() => {
// ...
setIsDesChild(false)
}, [props.visible])
const childDom = (
<div className={styles.xDrawerWrap}>
<div className={styles.xDrawerContent}
{
isDesChild ? null : children
}