美文网首页
一个前端项目中能用上的领域模型

一个前端项目中能用上的领域模型

作者: ltaoo | 来源:发表于2021-06-08 11:57 被阅读0次

    关于「领域驱动设计」在前端的应用,断断续续都能看到一些博客,但大部分看完后不明觉厉,转眼就忘记了,最重要的原因是无法应用在自己项目中。

    其实有一个大部分前端同学每天都接触的领域模型 - 「列表领域」。

    领域模型是公共认知的体现

    我们说到列表时,都知道它在 PC 端一般是表格形式,移动端是列表形式。

    1. 表格形式下它有页码,可以点击切换页码,表格显示当前页数据
    2. 列表形式它是无限滚动加载或点击加载更多,列表显示当前页及之前所有页数据
    3. PC 端移动端都有搜索功能,搜索时需要将页码重置到第一页

    不仅仅是上面的描述,还有更多对于「列表」这个概念的「公共认知」就不赘述了。这个公共认知不仅对于研发,对于产品也是一样的。
    产品经理在 PRD 中对于列表功能从不会详细描述,不会写出这样的 PRD

    ... 该页面展示客户表格,默认请求第一页数据,下方展示可以点击的页码 1 - n,点击页码时,将当前表格数据更新为点击页码对应的数据;根据关键字对客户进行搜索时,不使用当前已点击的页码而是第一页;点击重置按钮时,清空所有搜索条件,页码和表格数据回到第一页。

    只需要一句「该页面展示客户列表,支持分页和搜索」,研发就知道怎么做了。对于某个业务逻辑大家有一致的认知,它就是领域模型。

    领域模型在代码上的表现

    下面假设存在 fetchCustomers 函数,它是一个支持分页、搜索的客户列表请求方法。

    // 声明领域模型
    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);
    });
    
    // 点击页码时这样调用(后面均省略 .then)
    customerList.fetch({ page: 2 });
    
    // 搜索时这样调用
    customerList.fetch({ page: 1, name: "ltaoo" });
    

    List 虽然有方法有属性,但它没有表达「业务逻辑」所以不能算领域模型。

    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 是完全没有问题的,类似这样

    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 为例

    function useList(fn) {
      const list = useRef(new List(fn));
      const [response, setResponse] = useState({ list: [], total: 0 });
      useEffect(() => {
        // 这个需要 List 内实现每次调用 fetch 后调用 onChange
        list.onChange = (nextResponse) => {
          setResponse(nextResponse);
        };
      }, []);
      return [
        response,
        {
          init: list.init,
          goto: list.goto,
        },
      ];
    }
    

    和直接实现为 react hook 用法是一样的,但它还可以在 vue 等框架中使用。

    当然,说了这么多,这里是一个可运行的示例,实际上手体验下吧


    点击体验

    相关文章

      网友评论

          本文标题:一个前端项目中能用上的领域模型

          本文链接:https://www.haomeiwen.com/subject/yhgdeltx.html