前言
学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会更加深刻。一千个读者就有一千个哈姆雷特,但是莎士比亚心中的哈姆雷特肯定只有一个。就好比element源码只有一个,但每个人看到的都是不一样的风景。element源码解读是一个系列,每一个组件细腻的背后都能看到前端工程师付出的心血,本篇带来的是Element源码分析系列4-Radio(单选框)
简介
单选框这个组件看似简单,实则知识点众多,较为复杂,如果写一个html的原生单选框,那确实很简单,但是封装一个完整的单选组件就不那么简单了。element团队在整个radio组件的设计和构思真可谓十分的细致,没有一行多余的代码。从基础的css到选择逻辑都无不彰显其巧夺天工的思想。
划重点
1、如何隐藏原始radio标签默认样式
我们都知道原生的radio标签很丑,样式在各个浏览器不统一,作为一个组件不可能就这样屈服,况且人这种物种总是希望统驾驭所有的物质之上,包括代码。所以必须自己实现所有radio按钮的样式,那么如何自己实现一个可控制的radio标签呢?看element是怎么实现的。
看图不难分析出radio组件的html样式,整个大盒子被一个laber标签包裹着,laber里分为两个span,第一个span是用来展示选择按钮的,第二个span是来描述选项的。第一个span里应该包括input(用来做radio)和span(隐藏真正的input)两个标签。其相关的html结构如下:<label className='el-radio'>
<span>
<span className="el-radio__inner"></span>
<input
type="radio"
className="el-radio__original"
/>
</span>
<span className="el-radio__label">
{children || value}
</span>
</label>
问题一:如何做到隐藏原始radio标签默认样式?作者巧用opacity:0,真正的input透明度为0,且是绝对定位脱离文档流,因此不占空间且我们看不到,注意不是display:none或者visibility:hidden,如果是none或者hidden的话则无法触发鼠标点击了,所以只有opacity:0才能达到目的.
.el-radio__original {
opacity: 0;
outline: 0;
position: absolute;
z-index: -1;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
}
2、如何点击选择按钮展示不同的状态
如何做到点击旋钮显示不同的状态。通过radio标签可分析出,点击即可以理解为一个事件(这里用onChange表示)。通过点击事件传递一个props值(checked)显示不同的状态。所以在input上一定会有一个事件和一个所要传递的状态。
<input
type="radio"
className="el-radio__original"
checked={checked} // 传递的状态
onChange={this.onChange.bind(this)} // 事件
/>
那么接下来就是定义事件了,其核心代码为:
constructor(props) {
super(props);
this.state = {
checked: this.getChecked(props) // 定义一个state,受props影响
};
}
componentWillReceiveProps(props) {
const checked = this.getChecked(props);
if (this.state.checked !== checked) { // 维持每次点击时只选中一个
this.setState({ checked });
}
}
getChecked(props) {
return Boolean(props.checked) // 根据传递的props,输出true/false
}
onChange(e) {
const checked = e.target.checked; // 定义被点击项的checked为true
if (checked) {
if (this.props.onChange) {
this.props.onChange(this.props.value); // 向外暴露一个onChange事件并携带value值
}
}
this.setState({ checked }); // 更新被点击项的state为true
}
如果你对上述描述的还是雨里雾里,那么下面这张图可以更好的帮组你理解element-radio标签事件的逻辑:
从图中我们可以看出,radio是如何做到点击改变状态,并且又如何维护每次点击完后radio的状态。- 传入checked作为props决定每个radio的状态
- 根据传入的props,定义一个内部的state,跟props的checked保持一致
- 点击事件改变当前的radio的状态,更新state中的checked,并且向外传递一个value
- 外面接受到传来的value,重新定义props
- 通过生命周期函数更新内部的state,让其保持每个radio一致
3、Radio.Group如何做到单选框组
单选框组故名思议是将所有的radio包裹一层,由最外层原始事件来决定每个radio的状态。故需要用到React的两个API:React.Children.map、React.cloneElement
技术扩展:
- React.Children 提供了用于处理 this.props.children 不透明数据结构的实用方法。
React.Children.map(children, function[(thisArg)])
在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。
- React.cloneElement: 以 element 元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。
React.cloneElement(element, [props], [...children])
先了解这两个API,在来看看element中Radio.Group的源码就很简单来。
<div ref='RadioGroup' className='el-radio-group'>
{
React.Children.map(this.props.children, element => {
if (!element) {
return null
}
return React.cloneElement(element, Object.assign({}, element.props, {
onChange: this.onChange.bind(this),
value: this.props.value,
}))
})
}
</div>
通过遍历所有的radio组件,克隆出相应的radio组件,并为其附上radio的一些属性,包括方法和value。
结语
纵观整个radio的设计和实现,每个设计的过程都十分的微妙,尤其是radio如何更新状态,在平时业务中,我们也经常会遇到这种需求,但是真正的实现起来却没有这么简洁。源码的学习或多或少可以让我们增强自己的代码能力和业务能力。那么跟随我的脚步,带你一步一步解析整个element源码的奇思妙想。山不再高,有仙则灵;水不在深,有心则成。后续将推出更多源码解析文章。源码请👇这里element-radio源码