领域驱动,各自只管各自的模块,顶层再来进行组装和分配
- 坚持根据特性区命名目录。
- 坚持为每个特性区创建一个 NgModule。 能提供限界上下文,将某些功能牢牢地锁在一个地方,开发某个功能时,只需要关心这个模块就够了。
视图的归试图,逻辑的归逻辑
代码语言:javascript复制function SomeComponent() {
const someService = useService();
return <div>{someService.state}</div>;
}
跨组件数据传递?
代码语言:javascript复制function useGlobalService() {
return { state: "" };
}
const GlobalService = createContext(null);
function SomeComponent() {
return (
<GlobalService.Provider value={useGlobalService()}></GlobalService.Provider>
);
}
function useSomeService() {
const globalService = useContext(GlobalService);
return <div>{globalService.state}</div>;
}
上下文注入节点,本身就是按照试图来的
函数 DDD
只用函数实现 DDD,它有多优美
我们先比较一下这两种写法,对于一个类:
代码语言:javascript复制class SomeClass {
name:string,
password:string,
constructor(name,password){
this.name = name
this.password = password
}
}
const initValue = { name: "", password: "" };
function useClass() {
const [state, setState] = useState(initValue);
return { state, setState };
}
下面的自带响应式,getter,setter 也自动给出了,同时使用了工厂模式,不需要了解函数内部的逻辑。
生命周期复用
每个 useFunc 都是 拆掉 的管道函数,框架帮你组装,简直就是一步到位!
效率
代码语言:javascript复制function useSomeService() {
const [form] = useForm();
const request = useRequest();
const model = useModel();
useEffect(() => {
form.onFieldsChange = () => {
request.run(form.getFieldsValue);
};
}, [form]);
return {
model,
form,
};
}
<Form form={someService.form}>
<Form.Item name="xxx" label="xxx">
<!-- 没你service啥事了!别看!这里是纯视图 -->
</Form.Item>
</Form>
这个表单服务你想要在哪控制?
想要多个组件同时控制?
加个 token,也就是 createContext,把依赖提上去!
他特么自然了!
React Hooks 版本架构
执行 LIFT 原则
- 顶层文件夹最多包含:assets,pages,layouts,app 四个(其中 pages,layouts 是为了照顾某些 ssr 开发栈),名字可以变更,但是不可以有多余文件夹,激进的话可以只有一个 app 文件夹
- 按功能划分文件夹,每个功能只能包含以下四种文件:Xxx.less, Xxx.tsx, useXxx.ts,useXxx.spec.ts , 采用嵌套结构组织
- 一个文件夹包含该领域内所有逻辑(视图,样式,测试,状态,接口),禁止将逻辑放置于文件夹以外
- 如果需要由其他功能调用,利用 SOA 反转 为何如此?
- 功能结构即文件结构,开发人员可以快速定位代码,扫一眼就能知道每个文件代表什么,目录尽可能保持扁平,既没有重复也没有多余的名字
- 当有很多文件时(例如 10 个以上),在专用目录型结构中定位它们会比在扁平结构中更容易
- 惰性加载可路由的功能变得更容易
- 隔离、测试和复用特性更容易
- 管理上,相关领域文件夹可以分配给专人,开发效率高,可追责和计量工作量,很明显应该禁止多人同时操作同一层级文件
- 只需要对 useXxx 进行测试,测试复杂度,工作量都很小,视图测试交给 e2e
利用 SOA 实现跨组件逻辑复用
利用 注入令牌
服务函数
注入点
,实现灵活的 SOA
命名格式为
代码语言:javascript复制XxxService = useToken(useXxxService)
XxxService
为注入令牌 和文件名
useXxxService
为服务函数
<XxxService.Provider value={useXxxService()} />
XxxService.Provider
为注入点
注入令牌与服务函数紧挨
与注入节点处于同一文件结构层级
禁止除 SOA 以外的所有数据源
为何如此?
- 符合单一数据,单以职责,接口隔离原则
- 通过泛型约束,可以有更加自然的 Typescript 体验,不需要手动声明注入数据类型,所有类型将自动获得
- 层次化注入,可以实现 DDD,将逻辑全部约束与一处,方便团队协作
- 当你在根注入器上提供该服务时,该服务实例在每个需要该服务的组件和服务中都是共享的。当服务要共享方法或状态时,这是数学意义上的最理想的选择。
- 配合组件和功能划分,可以方便处理嵌套结构,防止对象复制被滥用,类似深复制之类的操作应该禁止
实现一个 IOC 注入令牌的方法为
代码语言:javascript复制import { createContext } from 'react';
/**
* 泛型约束获取注入令牌
*
* @export
* @template T
* @param {(...args: any[]) => T} func
* @param {(T | undefined)} [initialValue=undefined]
* @returns
*/
export default function useToken<T>(
func: (...args: any[]) => T,
initialValue: T | undefined = undefined,
) {
return createContext(initialValue as T);
}
一个典型可注册服务为:
代码语言:javascript复制import { useState } from "react";
import useToken from "./useToken";
export const AppService = useToken(useAppService);
export default function useAppService() {
// 可注入其他服务,以嵌套
// eq:
// const someOtherService = useSomeOtherService(SomeOtherService)
const [appName, setAppName] = useState("appName");
return {
appName,
setAppName,
// ...
};
}