前言
现在太多成熟、优秀的轮子功vuer使用,久而久之会有种“只会用轮子跑,不知道怎么造”的感觉,所以本篇就抛开乱七八糟的业务需求,来看看一个优秀的ui component是如何实现的。
现在手机端电商开发非常普遍,vant又能够满足大多数使用场景,所以以它为研究对象,涉及到的技术栈有vue + jsx + typescript + less。
目录简介
src/
utils/ //工具类
style/ //全局样式
row/ //组件代码
index.js
index.less
test/ //测试代码
demo/ //示例
col/
.....
test/ //测试类
types/ //typescript声明文件
准备工作:create创建类函数
vant每个组件都会先调用creaeNamespace
函数,createNamespace
函数接收参数name
,返回createComponent
、createBEM
、createI18N
三个函数,即comonent实例,css帮助类函数,以及多语言工具。
// utils/create/index.ts
export function createNamespace(name: string): CreateNamespaceReturn {
name = 'van-' + name;
return [createComponent(name), createBEM(name), createI18N(name)];
}
(1)createComponent
createComponent
函数根据name
生成一个vue组件对象,在这个过程中对组件进行一些预设。
export function createComponent(name: string) {
return function<Props = DefaultProps, Events = {}, Slots = {}> (
// 参数sfc:单文件组件(single file components),支持对象式组件和函数式组件
sfc: VantComponentOptions | FunctionComponent
): TsxComponent<Props, Events, Slots> {
// 函数式组件转化为对象式组件
if (typeof sfc === 'function') {
sfc = transformFunctionComponent(sfc);
}
if (!sfc.functional) {
sfc.mixins = sfc.mixins || [];
sfc.mixins.push(SlotsMixin); // push了一个兼容低版本scopedSlots的mixin
}
sfc.name = name;
sfc.install = install; // install中调用了Vue.component()方法注册组件
return sfc as TsxComponent<Props, Events, Slots>;
};
}
对象式组件VantComponentOptions
在vue组件基础上添加了两个属性:functional
和install
方法
export interface VantComponentOptions extends ComponentOptions<Vue> {
functional?: boolean; // 用于上文函数式组件判断
install?: (Vue: VueConstructor) => void; // 插件的install方法
}
(2)createBEM
createBEM
函数是一个根据name
生成css类名的方法,将类的元素(ELEMENT)和类名用“__”分隔,类的模组(MODS)用“–”分隔,使所有样式类名统一前缀,示例如下。
/**
* b() // 'button'
* b('text') // 'button__text'
* b({ disabled }) // 'button button--disabled'
* b('text', { disabled }) // 'button__text button__text--disabled'
* b(['disabled', 'primary']) // 'button button--disabled button--primary'
*/
b()接收两个参数:el
和mods
export function createBEM(name: string) {
return function(el?: Mods, mods?: Mods): Mods {
//只有字符串格式的el会被处理,其他格式会当做mods处理
if (el && typeof el !== 'string') {
mods = el;
el = '';
}
el = join(name, el, ELEMENT);
return mods ? [el, prefix(el, mods)] : el;
};
}
(3)createI18N
多语言处理,不多说了
export function createI18N(name: string) {
const prefix = camelize(name) + '.';
return function(path: string, ...args: any[]): string {
const message = get(locale.messages(), prefix + path) || get(locale.messages(), path);
return typeof message === 'function' ? message(...args) : message;
};
}
van组件——Row和Col
先调用creaeNamespace
函数
const [createComponent, bem, t] = createNamespace('row');
然后将组件的配置项传入createComponent
export default createComponent({
props: {
...
},
methods: {
...
},
render() {
...
);
}
具体props和events可以对照官方给出的api。
这里着重讲Row和Col的render
函数,分为组件实现、Html dom写法、css写法三个部分。
(1)组件实现
vant将布局分成了24列栅格,在Col上用span
属性控制占比,offset
控制偏移,在Row上gutter
控制间隔。
其中gutter
通过Col的左右padding 1/2 gutter
来实现,为了抵消padding产生的多余间距,在Row的左右各margin -1/2 gutter
,如图(搬运自juejin.cn/post/684490…,懒得画了)
span
和offset
就是总宽度/24*100%*具体数值
。
(2)Html dom写法
很多vuer倾向于使用vue-template写法进行开发,这样更符合传统Html/css/js习惯,不过vue也提供了render函数,并支持jsx,vant就是采用这种写法,能够对dom细节有更强的控制能力。
// Row.js
render() {
const { align, justify } = this;
const flex = this.type === 'flex';
const margin = `-${Number(this.gutter) / 2}px`;
// 计算样式:marginLeft和maginRight
const style = this.gutter ? { marginLeft: margin, marginRight: margin } : {};
return (
// 动态tag
<this.tag
style={style}
// 通过createBem将css类名统一在一个前缀下
class={bem({
flex,
[`align-${align}`]: flex && align,
[`justify-${justify}`]: flex && justify
})}
// 监听click事件
onClick={this.onClick}
>
{this.slots()} // 放置一个slot,这样Col就被包裹在Row里,可以在Col中通过this.$parent获取Row的data属性
</this.tag>
);
}
// Col.js
computed: {
gutter() {
return (this.$parent && Number(this.$parent.gutter)) || 0; // 获取父级gutter,仅支持一级父类
},
style() {
const padding = `${this.gutter / 2}px`;
return this.gutter ? { paddingLeft: padding, paddingRight: padding } : {};
}
},
render() {
const { span, offset } = this;
return (
<this.tag
style={this.style}
class={bem({ [span]: span, [`offset-${offset}`]: offset })}
onClick={this.onClick}
>
{this.slots()}
</this.tag>
);
}
(3)css写法
vant采用了less进行开发,配合上文提到的createBem
方法大大提高了开发效率。 以上文Rol的class为例:
class={bem({ [span]: span, [`offset-${offset}`]: offset })}
当 span=1,offset=2 , 即
class={bem({ 1: 1, [`offset-2`]: 2 })}
// 转换结果
class="van-col--1 van-col--offset-2"
利用less的循环来动态生成css样式
.generate-col(24);
.generate-col(@n, @i: 1) when (@i =< @n) {
.van-col--@{i} { width: @i * 100% / 24; } // 24列栅格
.van-col--offset-@{i} { margin-left: @i * 100% / 24; }
.generate-col(@n, (@i + 1));
}
小结
本文简要介绍了vant的utils类函数即Row和Col两个组件,之后会酌情挑选其他组件进行分析。仅作个人学习分享之用,如有错谬,欢迎指正^^!
今天的文章研究轮子,Vant源码分析1—Row/Col分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/17529.html