写在前面
在大公司里, 你总是会被明确的分工到某个螺丝坑中,重复的拧着同一种型号的螺丝。 当然这里带来了利与弊, 弊端很明显, 就是无法再接触所谓的工作中学习,毕竟你一直做着同样的事。 但是另一方面,善于总结的人,总是能因此点满了某一项专精,比如ui控件大佬,比如css大佬, echart大佬。 这些大佬会沉淀出一种叫轮子的东西,供他人使用。
所以在大公司里,你往往可以看到一些相对不是很成熟,但是又有一些精髓的轮子再反复使用。比如UI组件,SDK,认证服务等。和github等开源社区上的资源相比,这些轮子离我们更近,开发人员能力也和我们在一定程度上更接近,而且由于公司的推动,你将更多的更有机会的去接触他们,学习他们。我们不但需要快速上手这些工具,还要快速了解其原理,巧妙的通过一些demo快速掌握这些套路,总结。让其成为自己的另一种学习途径,在大公司里的特殊途径,尤为重要。
大致思路
Leech: n. 吸血鬼; 榨取他人利益的人 vt. 依附并榨取
这边我用leech这个词,是很贴近本文的意图的。 因为我们不断的接触这些工具轮子,所以可以有很多的时间,将他人沉淀的轮子,融为自己的知识,让自己变成更健壮。
轮子: 可以解释为套路,模式,工具,一切经过总结出来的东西
新轮子
=》 思考熟悉的那一面,脑海里产生第一印象,它可能是什么
===》复制黏贴,看看和你想的是否一样,异同点在哪儿
======》 找到它的关键节点,改一改关键元素,猜猜他的结果会有什么改变, 甚至可以试着做一个demo
=========》 参考答案瞄一眼(源码),如果有机会有时间,可以观摩一下大佬的代码嘛。不过如果demo跑通了,其实看不看问题不大
============》 思路,方法,套路,原理 Get, 细节 实现过程 IGNORE
简称:copy - try - modify
小小的例子
bootstrap()
.ns('AE-DOC')
.theme(MyTheme) // 注册并使用主题
.req(require.context('./modules', true, /(?:index|404).jsx?$/))
.mount()
这是一个初始化ae的bootstrap,给我的第一印象就是$(li').eq(0).sibling().addClass('xxx').value
像jQ一样的链式调用,很舒服很实用
bootstrap()
.ns('AE-DOC')
.mount() // 先mount
.theme(MyTheme) // 注册并使用主题
.req(require.context('./modules', true, /(?:index|404).jsx?$/))
试着改了一下顺序,这个链式调用果然失败了,因为mount就是他的终点。然而改变其他方法的顺序,并没有问题。
引出两个问题:
- 为何可以如此的链式调用,有的时候失败有的时候成功
- 这种写法的意义是在哪里
所以我拿开始想到的jQ做了对比,jQ中间的链式调用,是为了展示一个过程,找到最终想要的 jQ元素,而之所以能链式调用,是因为每次返回的都是一个jQ过的对象。
相同原理的东西其实还有, 比如:moment().subtract(1, 'days').add(1,'days').format('YYYY-MM-DD')
试着跑一个类似原理的Demo:
// 一个api拦截器 处理一些body和params的
class NomorlizeConfig {
constructor (config) {
this.config = config
}
paramsTrans = () => {
// ...转化configParams
this.config.params = transParams
return this
}
dataTrans = () => {
// ...转化body
this.config.data = data
return this
}
// 添加某些config
addConfig = (config) => {
this.config = Object.assign(this.config, config)
return this
}
get value() {
return this.config
}
}
// 继承了bootstrap链式调用的特性
newConfig = new NomorlizeConfig(config)
.paramsTrans()
.dataTrans()
.addConfig({needSuccessMessage: '新增人员成功'})
.value
以上,我已经get到bootstrap的链式调用精髓所在,而且我leech出了自己的demo,很舒服很实用
经典表单表格模式
什么是模式?
减小相似交互逻辑页面的重复性开发工作。 DRY原则
设计模式中的 Pattern:模式是解决某一类问题的方法论,把解决某类问题的方法总结归纳到理论高度,那就是模式
AE 中的模式:既包括数据层面的,对数据(state)和基于这些数据操作(actions/reducers)的封装;也包括 UI 上的,对数据展示和操作在 UI 上的封装,是针对某一类问题的解决方案。
总结一下,这里本没有路,人走多了就有了路
这个模式其实就是我前面说的,一群大佬点满了某项专精以后,总结出了一种特定的模式
有时候,模式会依赖一些DSL语言。
Domain Specific Language)是一种用来解决特定领域问题的计算机编程语言我们经常使用的CSS、SQL都属于DSL。
简单来说,DSL语言是在高级语言之上,更容易理解的语言。
AE就是用了某种设计语言,理论上你了解了其中的语法,使用起来就很舒服了。
表单模式
export default ({ctx, scope}) => ({
permission: -1,
name: '经典表单',
model: 'Form',
actions: { 'show': (entity) => showSubmitData(entity) },
reducers: { show: (state, payload) => state },
source: { uri: '/basic', agent: () => Promise.resolve({field_1: '123123'}) },
schema: {
field_1: { label: '单行文本字段', type: 'string', required: true, },
field_2: { label: '多行文本字段', type: 'string', component: 'FieldTextArea',
componentConfig: { autosize: { minsize: 10, maxsize: 40 }}
},
field_3: { label: '自定义字段', type: 'string', component: SimpleField }
},
decorators: {
config: {
onLoad: 'get', // 打开表单时,触发请求,从服务端获取数据
onResponse: noop,
buttons: [
{label: '重置',action: 'reset'},
{label: '提交数据', style: 'primary',action: 'show'}
]
}
}
})
以上是AE的最基本的表单模式DSL,忽略掉他封装的reduxSource部分,我们抓住他的核心,schema(一般是DSL里篇幅最大的部分),我们称之为root。
如果你完全没有见过类似的配置型语句,那也没关系。其实你一定见过了,只是你想不起来。
我们根据root简单的来造个demo。
核心是一个表单, 那么他应该长这样的。
render() {
const { getFieldDecorator } = this.props.form
const schemas = this.props.fields || []
return (
<Form layout={defaultFormLayout.form}>
{
schemas.map(schema => {
// 分解构造所需的config. 未知的restConfig会自动向下传递
const { key, rule, label, field, onChange ...restConfig } = schema
const Component = Fields[field || 'Input']
return (
<FormItem
{...defaultFormLayout.formItem}
label={label}
>
{
getFieldDecorator(key)(
// 为每个函数绑定this
<Component
onChange={onChange.bind(this)} fieldConfig={{...restConfig}} />
)
}
</FormItem>
)
})
}
</Form>
)
}
通过schema形成一个基本表单样式,然后把需要的东西一点点的拆解开来,放在合适的位置。
比如:
- label,component可能对应表单的label和渲染的组件名称
- onchange用来绑定表单上的可能事件,注意,这里开发者可能会友好的为你绑上this,以让你更好的使用组件内部的东西
- restConfig可能就插在组件里向下传递,提供给使用者埋点
在基础的表单架构上,AE的DSL是利用装饰器给表单组件做进一步的丰富的。
其实decorators就是高阶函数和HOC,decorators就可以理解为为基础组件添砖加瓦,多穿几件衣服。
所以理解decorators后,其实你可以很轻松的为自己的刚才的demo搞一个modal的decorators
// 自制修饰器modal
@modal
class demoForm {}
也可能提供一种内置的修饰器,只要传入相关的option
// 这边传入button 就能直接修饰表单的footer
footerFactory = () => {
const defaultButtons = [
{ label: '确定', type: 'primary', key: 'ok', action: 'onOk' },
{ label: '取消', key: 'cancel', type: '', action: 'close' }
]
const buttons = this.props.buttons || defaultButtons
return buttons.map(button => {
// 同样的button事件绑定,通过判断actionType动态绑定
const onClickEvent = typeof button.action === 'function' ?
button.action.bind(this)
: this.props[button.action] ?
this.props[button.action].bind(this)
: this[button.action]
return <Button type={button.type} key={button.key} onClick={onClickEvent}>{button.label}</Button>
})
}
表格模式
export default ({ctx, scope}) => ({
permission: -1,
model: 'Grid', // 经典表格
componentConfig: {
checkable: true,
selectType: 'radio',
bordered: false
},
schema: {
name: {type: 'string', label: '姓名'},
age: {type: 'number', label: '年龄'},
},
decorators: {
detail: false,
search: false,
edit: false,
del: false,
list: false,
create: false,
paginate: { showQuickJumper: true, $limit: 3 },
}
})
横向比较表单和表格,其实你已经领悟到该DSL的大致语法了。
同样从schema入手,创建一个基本表格:
render () {
const { items = {} } = this.props.dataSource || {}
// 转换schema对象为数组columns
const { columns, primaryKey } = this.normalizeColumns(this.props.columns)
// 转换items对象为数组dataSource
const dataSource = this.normalizeTableItems(items)
return (
<div className='baseTable'>
<Table
rowKey={record => record[primaryKey]}
columns={columns}
dataSource={dataSource}
onChange={this.onTableChange}
pagination={this.paginationFormat()}
/>
</div>
)
}
这边把schema对象在最后渲染时才做转换为数组,为了在算法性能上尽量的去避免对数组的for循环,也是一个可以学习的点。
这是自制的demo:
export default (props) => {
const columns = props.columns
const option = {
columns: columns,
source: 'dataSource',
action: 'submitParams',
decorators: ['filter']
}
return (
<div>
<BaseTable {...props} option={option} />
</div>
)
}
// 关于修饰器
const { decorators } = this.props.option
this.final = Base
decorators.forEach(decorator => {
if (decorator && typeof decorator === 'function') {
this.final = decorator(this.final)
}
if (decorator && typeof decorator === 'string') {
if (decoratorsCore[decorator]) {
this.final = decoratorsCore[decorator](this.final)
}
}
})
当你把demo造好以后,其他的配置项其实和表单没有什么不一样,同样是在你觉得可以插入的地方插入你想要的东西。当然和设计者是略有出入的,可能是它的组件不够强大,也可能是其他方面的考虑。
总结
- 没有魔法, 一切靠猜, 越猜功力越深厚
- 不要试图钻研底层源码,虽然那很有用,但是通常没有注释的代码会让你抓狂且费时。尝试自制Demo
- 如果你用AE模式,尽可猜根溯源;如果你用自定义组件,用你的方式仿造aE
学习延伸
以下, 是我根据ae的表格表单套路,造出的一个简单的echart模式。想法灵感来源于AE模式
// 线图
lineOption = {
type: 'line', // 定义图表基础type
option: lineBaseOption, // 传入配置好的图表option
plugins: ['tooltip'] // 图表插件定义, 如tooltip, legend等, 需要在图表配置中加入相关option
}
// 饼图
barOption = {
type: 'bar', // 定义图表基础type
option: barBaseOption, // 传入配置好的图表option
dataHandler: barDataHandler // 组件有默认的数据处理函数,如要替换,请再此引入
}
其实和我自造表单,表格并没有什么区别,一样是很简单的原理, 把helloworld跑通了。 留下了大量的可配置空间
当你了解了这种工作模式, 我觉得上手vue应该也不是什么难事
var view = new Vue({
el: '#app',
data: {
name: 'vue',
},
template: `<div id='app'>hello {{data.name}}</div>`
})
网友评论