Controller模块实现
- Controller 核心功能点
- 事件捕获 API 设计
- DOM 操作 API 封装
- render API 的设计
思考:如果我们想要使用这个 Controller 模块的时候,我们希望用什么 Api 来调用 ?,思考在业务里面,Controller 会包含哪些内容 ?
例如在 todo 这个增删改查的 App,会有添加按钮、删除按钮、编辑按钮及其相关的业务操作,这些操作怎么在 Controller 中封装。
- 传入和这个 Controller 相关的 model 和 view
- 传入这个 Controller 会挂载到哪个 DOM 元素上
- 传入某种按钮(元素)点击时候,要触发哪些事件
class Controller {
// ...
}
const todoController = new Controller({
model,
view,
el: '#app',
onClick: {
'.btn-add' (e) {
// ...
},
'.btn-update' (e) {
// ...
},
'.btn-delete' (e) {
// ...
}
}
})
实现后的 Controller
class Controller {
constructor (conf){
// 获取参数赋值到私有属性中
this.el = document.querySelector(conf.el)
this.model = conf.model
this.view = conf.view
this.render = this.render.bind(this)
// 为 conf.onClick 的元素绑定事件
this.el.addEventListener('click', (e) => {
e.stopPropagation()
// 取出 conf.onClick 对象中所有的函数名(元素选择器)
const rules = Object.keys(conf.onClick || {})
rules.forEach((rule) => {
// 如果点击的事件的元素匹配上我们的选择器
if( e.path[0].matches(rule)){
// 执行 conf.onClick 中,其选择器中的方法
// 并使用保证 this 的上下文时在当前指向
conf.onClick[rule].call(this,e)
}
})
})
}
// 框架的好处在于你可以把各类的 Api 封装一次,按照自己方便的来提供数据
getTargetArr (e, attr) {
return e.path[0].getAttribute(attr)
}
getChild (selector) {
return this.el.querySelector(selector)
}
// 用于绑定数据的变化和视图的渲染
render () {
this.el.innerHTML = this.view(this.model)
}
}
继承 Controller 的使用事例
class TodoController extends Controller {
constructor (model, view) {
super({
model,
view,
el: '#app',
onClick: {
'.btn-add' () {
// 新增 Todo 时对数据全量赋值
this.model.todos = this.model.todos.concat([{
id: new Date().getTime().toString(),
// 使用 getter 获取绑定至 DOM 元素的数据
text: this.addInputText
}])
},
'.btn-delete' (e) {
const id = this.getTargetAttr(e, 'data-id')
// 根据 id 过滤掉待删除元素
this.model.todos = this.model.todos.filter(
todo => todo.id !== id
)
},
'.btn-update' (e) {
const id = this.getTargetAttr(e, 'data-id')
const text = this.getUpdateText(id)
// 根据 id 更新元素
this.model.todos = this.model.todos.map(
todo => ({
id: todo.id,
text: todo.id === id ? text : todo.text
})
)
}
}
})
// 订阅 Model 更新事件
this.model.subscribers.push(this.render)
}
getUpdateText (id) {
return super.getChild(`input[data-id="${id}"]`).value
}
get addInputText () {
return super.getChild('.input-add').value
}
}