关于「领域驱动设计」在前端的应用,断断续续都能看到一些博客,但大部分看完后不明觉厉,转眼就忘记了,最重要的原因是无法应用在自己项目中。
其实有一个大部分前端同学每天都接触的领域模型 - 「列表领域」。
领域模型是公共认知的体现
我们说到列表时,都知道它在 PC 端一般是表格形式,移动端是列表形式。
- 表格形式下它有页码,可以点击切换页码,表格显示当前页数据
- 列表形式它是无限滚动加载或点击加载更多,列表显示当前页及之前所有页数据
- PC 端移动端都有搜索功能,搜索时需要将页码重置到第一页

不仅仅是上面的描述,还有更多对于「列表」这个概念的「公共认知」就不赘述了。这个公共认知不仅对于研发,对于产品也是一样的。 产品经理在 PRD 中对于列表功能从不会详细描述,不会写出这样的 PRD
… 该页面展示客户表格,默认请求第一页数据,下方展示可以点击的页码 1 - n,点击页码时,将当前表格数据更新为点击页码对应的数据;根据关键字对客户进行搜索时,不使用当前已点击的页码而是第一页;点击重置按钮时,清空所有搜索条件,页码和表格数据回到第一页。
只需要一句「该页面展示客户列表,支持分页和搜索」,研发就知道怎么做了。对于某个业务逻辑大家有一致的认知,它就是领域模型。
领域模型在代码上的表现
下面假设存在 fetchCustomers
函数,它是一个支持分页、搜索的客户列表请求方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class List { constructor(fn) { this.fn = fn; } async fetch(params = { page: 1, pageSize: 10 }) { const response = await this.fn(params); this.response = response; return response; } }
const customerList = new List(fetchCustomers);
customerList.fetch().then(({ list, total }) => { console.log(list, total); });
customerList.fetch({ page: 2 });
customerList.fetch({ page: 1, name: "ltaoo" });
|
List
虽然有方法有属性,但它没有表达「业务逻辑」所以不能算领域模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class List { constructor(fn) { this.fn = fn; } async fetch(params = { page: 1, pageSize: 10 }) { const response = await this.fn(params); this.response = response; return response; } init() { return this.fetch({ page: 1, pageSize: 10 }); } goto(page) { return this.fetch({ page, pageSize: 10 }); } search(params = {}) { return this.fetch({ page: 1, pageSize: 10, ...params }); } }
const customerList = new List(fetchCustomers); customerList.init().then(({ list, total }) => { }); customerList.goto(2); customerList.search({ name: "ltaoo" });
|
将业务逻辑以合适的方法名封装在 List
中,调用时无需关心逻辑内部实现,只需要调用对应方法,这才是领域模型。 除了这几个方法,我们还可以有 loadMore
实现移动端加载更多、reset
重置列表等。
有生命力的领域模型
其实上面的逻辑写成一个 react hook
是完全没有问题的,类似这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function useList(fn) { const [response, setResponse] = useState({ list: [], total: 0 }); async function fetch(params = { page: 1, pageSize: 10 }) { const newResponse = await fn(params); setResponse(newResponse); } return [ response, { init() { fetch({ page: 1 }); }, goto(page) { fetch({ page }); }, }, ]; }
function CustomerMangePage() { const [response, { init, goto }] = useList(fetchCustomers); useEffect(() => { init(); }, []); }
|
写法上仍然简洁清晰,不过写成类的好处是不和框架耦合,无论框架怎么变更,只要仍使用 js
开发,这个 List
类是可以一直使用的。 当然,不和框架耦合,不意味着写法只能固定,我们可以创建一层框架与领域的粘合层,仍以 react hook
为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function useList(fn) { const list = useRef(new List(fn)); const [response, setResponse] = useState({ list: [], total: 0 }); useEffect(() => { list.onChange = (nextResponse) => { setResponse(nextResponse); }; }, []); return [ response, { init: list.init, goto: list.goto, }, ]; }
|
和直接实现为 react hook
用法是一样的,但它还可以在 vue 等框架中使用。
当然,说了这么多,这里是一个可运行的示例,实际上手体验下吧

点击体验