「✍ React DDD」

2,336 阅读3分钟

前言

这两天看到一篇文章原文指路,算是对DDD有了一点点点浅显的理解。发现其实这个在前端不太常出现的理念,离我并不遥远,毕竟通俗的说,DDD的一大特点也就是逻辑抽取&视图分离。

以下内容均为对原文的个人理解

什么是DDD

原文 -- 领域驱动,各自只管各自的模块,顶层再来进行组装和分配

看起来比较宏观,在这里只针对这句话举两个例子便于后续理解

各自只管各自的模块

逻辑都写在hooks中,对应的组件有对应的服务

const Child = () => {
  const { count, addCount } = useCountService(0)
  return <h1 onClickCapture={ addCount }> { count }</h1>
}

顶层进行组装和分配

使用useContext从顶层传递下去即可

<CountService.Provider value={ useCountService(0) } >
    <Child1 />
    <Child2 />
</CountService.Provider>
import { useCallback, useState } from 'react'
interface ICountService {
  count: number
  addCount() => void
}
const useCountService = (initCount: number): ICountService => {
  const [count, setCount] = useState(initCount)
  const addCount = useCallback(() => {
    setCount((c) => c + 1)
  }, [])
  return {
    count,
    addCount,
  }
}
export default useCountService

React SOA

要理解真正的DDD,先讲讲什么是SOA(面向服务架构)

粗暴理解:把系统按照实际业务,拆分成刚刚好大小的、合适的、独立部署的模块,每个模块之间相互独立。

以上面举过的例子useCountService为例,useCountService本身是一个服务,不足以成为一个模块,但服务本身是可以组合的,这也是React Hooks的一大特点

顶层模块:

import { createContext } from 'react'
import useCountService, { ICountService } from './useCountService'

type IService = ICountService // ICountService | IOtherService | ...

export const SimpleService = createContext<IService>(null)

export const useSimpleService = (): IService => {
  const { count, addCount } = useCountService(0)
  return {
    count,
    addCount
  }
}

泛型约束 - 处理类型问题

在上述例子中,需要在顶层模块获取/定义类型(type IService = ...),原文提供了一种泛型约束 InjectionToken的方式来获取能够自动推导类型的Context

import { createContext } from 'react'
export default function getService<T>(
  func(...args: any[]) => T,
  initialValue: T | undefined = undefined,
): React.Context<T> {
  return createContext(initialValue as T)
}

这里的func即传入的useXXXService,仅用于推导类型

顶层模块:

import getTokenService from './getTokenService'
import useCountService from './useCountService'
export const useSimpleService = () => {
  const { count, addCount } = useCountService(0)
  return {
    count,
    addCount,
  }
}
export const SimpleService = getTokenService(useSimpleService) // SimpleService 可以自动推导出useSimpleService里返回的count 和 addCount

SOA = 注入令牌 + 服务函数 + 注入点

令牌 - 即上述用于类型推导的getTokenService

服务函数 - useXxxService (useSimpleService 、useCountService)

注入点 - <XxxService.Provider value={useXxxService()} /> 中的XxxService.Provider

举个栗子

import React, { useContext } from 'react'
import { SimpleService, useSimpleService } from './useSimpleService'

// 没有用到context的组件可以用memo包裹,避免re-render
const Child1 = React.memo(() => {
  return <h1>child1</h1>
})

const Child2 = () => {
  const { count, addCount } = useContext(SimpleService)
  return <h1 onClickCapture={ addCount }>child2, { count }</h1>
}

const ParentReact.FC = () => {
  return (
    <>
      <SimpleService.Provider value={ useSimpleService() } >
        <Child1 />
        <Child2 />
      </SimpleService.Provider>
    </>
  )
}
export default Parent

用SOA解释DDD

知道了什么是SOA,再解释DDD就容易多了,以上述的栗子代码为例

Parent及其子组件这种有共同单例 Service 的一系列组件,被称为模块,它们有自己的 “限界上下文”,并且,视图,逻辑,样式都在其中,如果这个模块是按照功能划分的,那么这种 SOA 实现被称为 领域驱动设计(DDD)

可选服务

模块服务划分的另一个巨大优势,就是将逻辑变为可选项,这在重型应用中,几乎就是采用 DDD 的关键


function useServiceByOneLogic(){
  return {
    activated,
    // ...
  }
}
function useServiceByAnotherLogic(){
  return {
    activated,
    // ...
  }
}
function useSomeService(){
  const [...servicList] = [useServiceByOneLogic(),useServiceByAnotherLogic()]
  // 选择激活的服务
  const usedService = useMemo(()=>{
    for(let service of serviceList){
      if(service.activated === true){
        return service
      }
    }
  },[serviceList])
  return service
}

More ..

原文中还有很多部分我还读不懂,有朝一日顿悟了再继续叭