一个JavaScript文件,放在books页面要输出"books",放在authors页面要输出"authors",你会怎么写?
最直接的办法是堆if语句。判断当前路径,走不同分支。代码能跑,但每加一个页面就多一个if,逻辑越缠越紧,改一处怕碰坏另一处。
有没有一种写法,让调用方根本不知道具体逻辑从哪来,只管执行?Django的模板系统加上JavaScript的import maps,正好能拼出这个效果。
先搞清楚要解决什么
策略模式是一种行为型设计模式,核心是在运行时选择算法。代码不直接实现某个算法,而是接收运行时的指令,决定用一组算法里的哪一个。
放到前端场景里,就是同一段调用逻辑,在不同页面加载不同的实现。
传统写法长这样:
- 判断路径是不是books,是就写入books的文案
- 判断路径是不是authors,是就写入authors的文案
- 每多一个页面,就多一个判断分支
这种代码的膨胀速度,写过的人都懂。真正想做的,是把"用哪个实现"这件事从调用方剥离出去。
理想状态下,调用方只写一行:从strategy模块导入renderText,然后执行。至于strategy指向哪个文件,调用方不关心。
import maps补上了关键一环
这里说的是JavaScript的ES模块,语法是import ... from "module-name"。
ES模块有个特点:它是静态的,运行时不能改。而且直到不久前,浏览器根本不支持裸导入——也就是import { renderText } from "strategy"这种写法,strategy不是路径,浏览器不知道去哪找。
这种裸导入以前只有webpack这类模块打包器支持。
变化来自JavaScript import maps。它的作用是控制import语句最终去抓取哪个URL。有了它,浏览器里也能用裸导入,而且导入的URL可以在运行时被替换。
再叠加上Django模板系统的灵活性,一个简化版的策略模式就能落地了。
靠模板继承来换实现
具体做法利用了Django模板的一个能力:子模板可以继承父模板,并覆盖指定的模板块。
流程拆开就两步:
- 子模板继承父模板
- 子模板覆盖js块,在里面声明自己的import map
示例项目由三个应用组成。core放基础模板和基础JavaScript文件,authors和books各自继承core的基础模板。
core应用里,base.html声明了content和js两个可被覆盖的模板块,同时加载一个JavaScript模块。这个基础文件Base.js的内容很干净:从strategy导入renderText,然后执行。它完全不知道strategy这个文件在哪。
authors应用的list.html继承core的base.html,覆盖js块,声明自己的import map,把strategy指向authors/js/Authors.js,然后用block.super保留父模板原有的内容。
在Authors.js里,导出renderText函数,写入对应的文案。books应用照同样的方式做一遍,把strategy指向自己的文件。
结果就是:Base.js一个字没改,在books页面和authors页面跑出了不同的行为。
几个值得注意的细节
模块导入名不一定是strategy,可以叫任何名字,只要子模板里引用的是同一个名字就行。
另外,Django社区已经在推进相关支持。Matthias Kestenholz基于Thibaud Colas此前的工作,提出了一个给Django添加ImportMap对象的提案,值得关注后续进展。
这个方案的价值不在于多巧妙,而在于它把"选择哪个实现"这件事,从JavaScript代码里挪到了模板层。调用逻辑保持稳定,变化被隔离在各自的子模板中。对多应用、多页面共享同一段前端逻辑的Django项目来说,这是一种值得一试的组织方式。
热门跟贴