写这篇文章的想法,其实来自于自己的一次React组件开发实践(传送门)。当时是做一个markdown编辑器,原理是用户编辑过后的string,经过其他开源的markdown语法解析器解析后输出html格式的文档,这样就可以实时预览markdown了。开发第一版时,我将解析器markdown-it内置在组件里,虽然能够满足了需求,但是也带来了一些问题:解析器会打包到自己的组件里导致代码体积增大,用户不能自定义解析器(ps:就想用那谁谁家的,咋地)。后来在项目贡献者sylingd的改造下,将组件的renderHtml接口改造成了一个高阶函数,完美的解决了这个问题(在此表示衷心的感谢!)
高阶函数
- 高阶函数是一个函数,它接收函数作为参数或将函数作为输出返回。常运用于AOP,函数柯里化,节流或防抖等。
- 函数式编程的思想在很多的鼎鼎大名的开源库里运用很广泛,比如redux、koa等。特别是redux,里面有很多中间件本质其实就是高阶函数。
组件开发
- 组件开发的目的其实就是暴露出对外使用的接口。使用者只需要定义好订阅的事件,至于组件内部如何去实现的过程,对使用者来说,这并不是他所关心的。使用者只需要关心接口的入参和出参,是否异步等这些关键信息即可。
- 如何写出一个扩展性和稳定性都很好的接口,确实会考验开发者的设计水平。往往当用户有新的需求,然而目前的项目却不能支持,不得不面临着升级(万一开发者不能及时或者不升级呢?)。所以,优秀的开发者在定义接口时,会考虑好各种情况,并且将高度的自定义权利移交给使用者,虽然这样会给开发带来复杂度,但是带给用户的好处却是成倍的。
举个vue的🌰
<div id="app" class='container'>
<div style='margin: 50px'>{{title}}</div>
<div style='margin: 20px; font-size: 16px'>{{message}}</div>
<my-button :tap='tapString' type='我是字符串'></my-button>
<my-button :tap='tapFunction' type='我是函数'></my-button>
<my-button :tap='tapPromise' type='我是promise'></my-button>
</div>
<script>
// 子组件
Vue.component('my-button', {
props: ['type'],
methods: {
handleClickMe: function () {
// 普通返回
var value = '我是一个普通数据类型,比如字符串,数字'
var middleware = this.$attrs.tap(value)
if (!middleware) {
console.log('返回undefined,直接终止')
return
}
if (typeof middleware === 'function') {
middleware('我是一个函数')
} else if (typeof middleware === 'object' && typeof middleware.then === 'function') {
middleware.then(({ payload, callback }) => {
typeof callback === 'function' && callback(payload + ', world!')
}).catch(({ payload, callback }) => {
typeof callback === 'function' && callback(payload + ', 没关系!')
})
}
// ...其他类型
}
},
template: '<button v-on:click="handleClickMe">点我, {{type}} </button>'
})
// 父组件
var app = new Vue({
el: '#app',
data: {
title: '父组件',
message: ''
},
methods: {
tapString: function (res) {
this.message = res
// alert(res)
},
tapFunction: function (res) {
var _this = this
return function (data) {
_this.message = data
// alert(data)
}
},
tapPromise: function (res) {
var _this = this
return new Promise((resolve, reject) => {
_this.message = ''
var number = Math.random()
setTimeout(() => {
if (number > 0.5) {
resolve({
payload: 'hello',
callback: function (res) {
_this.message = res
// alert(res)
}
})
} else {
reject({
payload: '发生错误了',
callback: function (res) {
_this.message = res
// alert(res)
}
})
}
}, 1000)
})
}
}
})
</script>
- 子组件有个tap属性的接口,当子组件触发click事件,会触发(发布)父组件订阅的tap方法。通常我们写的组件的属性方法,类似tapString这样,通过函数参数的形式,获取自组件传递过来的结果即可。
- 如果换成高阶组件,可以返回一个函数,或者Promise甚至其他类型,这样自组件第一步执行tap属性函数,会获取一个函数执行的返回结果
var middleware = this.$attrs.tap(value),然后通过判断这个结果的类型,如果是函数或者是promise,再进行相应的处理即可。
if (!middleware) {
return
}
if (typeof middleware === 'function') {
middleware('我是一个函数')
} else if (typeof middleware === 'object' && typeof middleware.then === 'function') {
middleware.then(({ payload, callback }) => {
typeof callback === 'function' && callback(payload + ', world!')
}).catch(({ payload, callback }) => {
typeof callback === 'function' && callback(payload + ', 没关系!')
})
}
- 通过这种高阶函数的一个接口,就能满足用户在不同的使用场景下,都能获取满意的结果。比如异步获取的信息传递给组件,最后又从组件那里获取传递过来的结果信息。
- 这个🌰同样适用于react组件,只不过其中的语法不同而已。