前言
这两天看到一篇文章原文指路,算是对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 Parent: React.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 ..
原文中还有很多部分我还读不懂,有朝一日顿悟了再继续叭