來自前端備忘錄 - 江湖術士[1]
https://hqwuzhaoyi.github.io/2021/01/14/74.HookDDD/
領域驅動,各自只管各自的模塊,頂層再來進行組裝和分配
- 堅持根據特性區命名目錄。
- 堅持為每個特性區創建一個 NgModule。能提供限界上下文,將某些功能牢牢地鎖在一個地方,開發某個功能時,只需要關心這個模塊就夠了。
視圖的歸試圖,邏輯的歸邏輯
function SomeComponent() {
const someService = useService();
return <div>{someService.state}</div>;
}
跨組件數據傳遞?
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,它有多優美
我們先比較一下這兩種寫法,對于一個類:
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 都是 拆掉 的管道函數,框架幫你組裝,簡直就是一步到位!
效率
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
命名格式為
XxxService = useToken(useXxxService)
XxxService 為注入令牌 和文件名
useXxxService 為服務函數
<XxxService.Provider value={useXxxService()} />
XxxService.Provider 為注入點
注入令牌與服務函數緊挨
與注入節點處于同一文件結構層級
禁止除 SOA 以外的所有數據源
為何如此?
- 符合單一數據,單以職責,接口隔離原則
- 通過泛型約束,可以有更加自然的 Typescript 體驗,不需要手動聲明注入數據類型,所有類型將自動獲得
- 層次化注入,可以實現 DDD,將邏輯全部約束與一處,方便團隊協作
- 當你在根注入器上提供該服務時,該服務實例在每個需要該服務的組件和服務中都是共享的。當服務要共享方法或狀態時,這是數學意義上的最理想的選擇。
- 配合組件和功能劃分,可以方便處理嵌套結構,防止對象復制被濫用,類似深復制之類的操作應該禁止
實現一個 IOC 注入令牌的方法為
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);
}
一個典型可注冊服務為:
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,
// ...
};
}
最小權限
人為保證代碼結構種,各個組成之間的最小權限,是一個好習慣
- 所有大寫字母開頭的 tsx 文件都是組件
- 所有 use 開頭的文件,都是服務,其中,useXxxService 是可注入服務,默認將所有組件配套的服務設置為可注入服務,可以方便進行依賴管理
- 禁止在組件函數種出現任何非服務注入代碼,禁止在組件中寫入與視圖不想關的
- 為復雜結構數據定義 class
- 如果可以的話,將單例服務由全局 service 組織,嵌套結構,共享實例,頁面初始化 除外
- ? 禁止深復制
為何如此?
- 當邏輯被放置到服務里,并以函數的形式暴露時,可以被多個組件重復使用
- 在單元測試時,服務里的邏輯更容易被隔離。當組件中調用邏輯時,也很容易被模擬
- 從組件移除依賴并隱藏實現細節
- 保持組件苗條、精簡和聚焦
- 利用 class 可以減少初始化復雜度,以及因此產生的類型問題
- 局管理單例服務,可以一步消滅循環依賴問題(道理同 Redux 替代 Flux)
- 深復制有非常嚴重的性能問題,且容易產生意外變更,尤其是 useEffect 語境下
JUST USE REACT HOOKS
拋棄 class 這樣的,this 掛載變更的歷史方案,不可復用組件會污染整個項目,導致邏輯無法集中于一處,甚至出現耦合, LIFT,SOA,DDD 等架構無從談起
項目只存在
- 大寫并與文件同名的組件,且其中除了注入服務操作外,return 之前,無任何代碼
- use 開頭并與文件夾同名的服務
- use 開頭,Service 結尾,并與文件夾同名的可注入服務
- 服務中只存在 基礎 hooks,自定義 hooks,第三方 hooks,靜態數據,工具函數,工具類
以下為細化闡述為何如此設計的出發點
- 快速定位 Locate
- 一眼識別 Identify
- 盡量保持扁平結構 (Flattest)
- 嘗試 Try 遵循 DRY (Do Not Repeat Yourself, 不重復自己)
此為 LIFT 原則
- 優先將組件視為元素,而并非功能邏輯單位(視圖的歸視圖,業務的歸業務)
- 隔離原則(屬于一個成員的工作,必定屬于該成員負責的文件夾,也只能屬于該成員負責的文件夾)
- 最小依賴(禁止不必要的工具使用,比如當前需求下,引入 Redux/Flux/Dva/Mobx 等工具,并沒有解決什么問題,卻導致功能更加受限,影響隔離原則比如當兩個組件需要服務的不同實例的情況,以上工具屬于上個版本或某種特殊需求,比如前后端同構,不能影響這個版本當前需求的架構)
- 優先響應式(普及管道風格的函數式方案,大膽使用 useEffect 等 api,不提倡松散的函數組合,只要是視圖所用的數據,必須全部都為響應式數據,并響應變更)
- 測試友好(邊界清晰,風格簡潔,隔離完整,即為測試友好)
- 設計友好(支持模塊化設計)
建議的技術棧搭配
- create-react-app + react-router-dom + antd + ahooks + styled-components (大多數場景下,強烈推薦!可以上 ProComponent,但是要注意提取功能邏輯,不可將邏輯寫于組件)
- umi + ahooks (請刪除 models,services,components,utils 等非必要頂層文件夾,禁止使用 dva)
- umi (ssr) + dva + ahooks(同上,但可僅基于 dva 溝通前后端和首屏數據,非 ssr 同樣禁用 dva)
- next.js + react suite/material ui + swr(利用不到 useAntdTable 之類的功能,ahooks 就雞肋了)
Hook 使你在無需修改組件結構的情況下復用狀態邏輯
當你思維聚焦于組件時,在這種情況下,你是必須逼迫自己,在組件里寫業務邏輯,或者重新組織業務邏輯!
并且,因為 state 是不反應業務邏輯的,它也天然不可以對業務邏輯進行組合
function useSomeTable() {
// 這個是個表單,抽象的
const [form] = Form.useForm();
// 這個是個表單聯動的表格
const { tableProps, loading } = useAntdTable(
// 自動處理分頁相關問題
({ curren, pageSize }, formData) => fetch("http://sdfdsfsdf"), // 抽象的狀態請求
{
form, // 表單在這里與表格組合,實現聯動
defaultParams: {
// ...
},
// 很多功能都能集成,只需要一個配置項
debounceInterval: 300, // 節流
}
);
return {
form,
loading,
tableProps,
};
}
<Form form={SomeTable.form}><!--里面全部狀態無關,不用看了--></Form>
<Table {...tableProps} columns={} rowKey="id"/>
這個組件,存粹只有注入和視圖,一丁點的邏輯都沒有
組件里沒有邏輯, 相關的邏輯再 useFunc 種就能隨意組合,結構什么都都能你來定,結構如果優秀,任何邏輯都是 use 個函數的問題,你不會出現需要寫第二遍邏輯的情況,通過組件的 props 進行分流(map + key),你能夠非常優雅地處理嵌套復雜結構
你能將視圖和邏輯完全組織為一個結構,交給一個特定的人,完全不用關心他到底是怎么開發的
這便是 —— 邏輯視圖分離
React SOA
基本的服務
function useSimpleService() {
const [val1, setVal1] = useState(0);
const [val2, setVal2] = useState(0);
useEffect(() => {
setVal2(val1);
}, [val1]);
return {
val1,
setVal1,
val2,
};
}
- 叫它 service,是 SOA 模型下的管用叫法,意思是 —— 我只會在這樣的結構種寫邏輯,組件中的邏輯全部消失(優先將組件視為元素)
- 只暴露你需要暴露的狀態邏輯(狀態邏輯必須一起說,只做狀態復用很扯淡,畢竟 2021 年了)
- useRef,同樣也可以封裝在 Service 中,而且建議如此做,ref 的獲取不是視圖,是邏輯
組合服務
有另外一個服務,useAnotherService
function userAnotherService() {
const [val, setVal] = useLocalstorage(0);
return { vale, setVal };
}
然后與基本服務進行組合
function useSimpleService() {
const [val1, setVal1] = useState(0);
const [val2, setVal2] = useState(0);
const { setVal } = userAnotherService();
useEffect(() => {
setVal2(val1);
}, [val1]);
useEffect(() => {
setVal(val2);
}, [val2]);
return {
val1,
setVal1,
val2,
};
}
就能為基本服務動態添加功能
- 為什么不直接 import?因為需要框架內的響應式能力,這個叫控制反轉,框架將響應式的控制權轉交給了開發者
- 如果有另外一個服務,單單只要 AnotherService 的功能,你只需要調用 useAnthor Service 就好了
- 最好是調用者修改被調用者,可以對比 ahooks 對代 useRef 的改動,就是本著這個次序,因為被調用者可能被多次調用,保證復用性
- useEffect 是一種管道模型,如同 rxjs 一般,只是框架幫你按順序組裝而已(你以為為啥非要你按順序來?),是極限的函數式方案,不存在純度問題,函數式得不要不要的。但是有個要求,依賴必須寫清楚,這個依賴是管道操作中的參數,React 將你的 hook 重新組合成了管道,但是參數必須提供,在它能自動分析依賴之前
- 使用了 useAnotherService 的細節被隱藏,形成了一個樹形調用結構,這種結構被稱作 “依賴樹” 或者 “注入樹”,別盯著我,名字不是我定的
注入單例服務
當前服務如果需要被多個組件使用,服務會被初始化很多次,如何讓它只注入一次?
利用 createContext
export const SimpleService = createContext(null);
export default function useSimpleService() {
// ...
}
但是,單例需要注入到唯一節點,因此,你需要在所有需要用到這個服務的組件的最頂層:
<SimpleService.Provider value={useSimpleService()}>
{props.children}
</SimpleService.Provider>
這樣,這個服務的單例就對所有子孫組件敞開了懷抱,同時,所有子孫組件對其的修改都將生效
function SomeComponent(){
const {val1,setVal1} = useContext(SomeService)
return <div onClick={()=>{setVal1('fuck')}>val1</div>
}
- 直接在 jsx 的 provider 種 value = {useSomeService ()} 在本組件沒有任何其它響應式變量的情況下是可行的,因為不會重新初始化,在良好的架構下 —— 組件除注入,無任何邏輯,return 之前沒有東西,同時,上下文單獨封裝組件,可以作為 “模塊標識”
- 這個有共同單例 Service 的一系列組件,被稱為模塊,它們有自己的 “限界上下文”,并且,視圖,邏輯,樣式都在其中,如果這個模塊是按照功能劃分的,那么這種 SOA 實現被稱為 領域驅動設計 (DDD) ,某些架構強推的所謂’微前端’,目的就是得到這個東西
- 一定要注意,這個模塊的上層數據變更,模塊的限界上下文會刷新,這個是默認操作,這也是為何 jsx 直接賦值 的原因,如果你不需要這個東西,可以采用 const value = useService () 包裹,或者直接 memo 這個模塊標識組件
單例服務,解決深層嵌套對象問題
深層嵌套對象怎么處理?useReducer?immutable? 還是直接深復制?
你首先明白你要實現什么邏輯,深層嵌套對象之所以難處理,是因為你想在子組件實現 對深層目標的部分變更邏輯
之前你之所以有這些奇奇怪怪工具甚至深復制的需求,是因為你沒有辦法將邏輯也拆分給子組件,明白為什么如此
現在,邏輯可以拆分復用:
function useSomeService() {
const [value, setValue] = useState({
username: "",
password: "",
info: {
nickname: "",
others: [],
},
});
return { value, setValue };
}
// 注入部分省略...
修改 info:
setValue((res) => {
res.info.nickname === "fuck";
return res;
});
配合 map 修改數組:
// 分形部分:
new Array(5).map((_, key) => <SomeCompo index={key} key={key} />);
// 組件
function SomeComponent(props) {
const { setValue } = useContext(SomeService);
return (
<div
onClick={() => {
setValue((res) => {
res.info.others[props.index] = "fuck";
return res;
});
}}
></div>
);
}
如果需要劃分模塊,通過 getter,setter 傳遞這個嵌套結構:
function subInjectedService() {
const { value, setValue } = useContext(SomeService);
const info = useMemo(() => value.info.others, [value]);
const setInfo = useCallback((val) => {
setValue((res) => {
res.info.others[props.index] = val;
return res;
});
}, []);
return {
info,
setInfo,
};
}
// 忽略注入部分...
這樣的話,這個重新劃分的模塊內部,想要修改上層的數據,只需要通過 info,setInfo 即可
- 不用擔心純度和不變性的問題,因為 hooks 都是純的,沒有不純的情況
- 全局副作用是狀態 + 函數全局邏輯封裝(分層)考慮的問題,將函數和組件,視圖功能邏輯樣式全部作為模塊,副作用是以模塊為單位的,而 info 和 setInfo 的 getter,setter 封裝,叫做 —— 模塊間通訊
- useReducer 只涉及調試,也就是有個 action 名字方便你定位問題,模塊劃分如果足夠細,你根本不需要這個 action 來記錄你的變更,采用 useReducer 與 DDD 原則背離,但是也不會禁止。不過,全局 useReducer 必須明令禁止,這種方式是個災難,useReducer 必須是以模塊為單位,不能更小,也不能更大
- 組件和服務一起,處理一部分數據,保證了單例修改,不變性也不用擔心,hooks 來保證這個
- 在這里,你會發現 props 的功能好像只有’分形’,也就是 map 種將數據的標識傳遞給子組件,是的 —— 優先使用服務共享狀態邏輯
- getter,setter 叫做響應式,如果你不需要響應式修改,setter 可以刪除,但是 getter 同時還有防止重新渲染的作用,保留即可,除非純組件
服務獲取時的類型問題
如果你使用的是 Typescript ,那么,用泛型約束獲得自動類型推斷,會讓你如虎添翼
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);
}
然后將 createContext() 改為 useToken(SomeService) 即可,這樣你就擁有了指哪打哪的類型支持,無需單獨的類型聲明,代碼更加清爽
- 如果是 JAVAscript 環境,建議老老實實寫 createContext 的 defaultValue,雖然注入之后,子孫組件都不會出現 defaultValue,但是 JavaScript 語境下有代碼提示
- 不建議 typescript 下聲明 defaultValue,因為模塊外的服務調用,應該被禁止,這是 DDD 架構的基礎,如果你想要在外部使用單例服務 —— 請將其提升至外部
頂層注入服務
平凡提升模塊服務層級,可能會產生循環依賴,而且會影響模塊的封裝度,因此:
??優先思考清楚自己應用的模塊關系!
循環依賴產生根源是功能領域,功能模塊劃分有問題,優先解決根本問題,而不是轉移矛盾。如果你實在思考不清楚,又想要立刻開始開發,那么可以嘗試頂層注入服務:
function useAppService(){
return {
someService:useSomeService()
anotherService:useAnotherService()
}
}
- 模塊間進行嵌套組合將變得無比困難,不再是一個 getter,setter 能夠搞定的,如果不是絕對的必要,盡量不要采用此種方式!它有悖于 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;
}
// 注入過程省略...
- 你也可以通過各種條件篩選服務,這種方式是在前端實現的高可用
- ? 注意,服務最好只是內部實現不同,接口應該盡可能相同,否者會出現可選類型
- 最典型的應用,就是多家云服務廠商的短信驗證(驗證碼,人機校驗等),通過可選服務根據用戶網絡情況進行篩選,用最適合當前用戶的那一個
- 還有一個非常有意思的方案,通過服務來做數據 mock,因為服務直接對接視圖,你只需要模擬視圖數據即可,提供兩個服務,一個真實服務,一個 mock 服務,這樣是用真實數據還是 mock 數據,都是服務自動判斷的,對你來說沒有流程差別
樣式封裝
注意,模塊是包含了樣式的,上文在講述邏輯和視圖的封裝,接下來說說樣式
- 典型的 cssModule, styled-components 之類的方案
- shadowDom,仿真樣式(Angular 原生支持,React 可以用 cssModule 之類工具間接實現),可以實現跨技術棧樣式封裝(沒錯,所謂 ‘微前端’ 的樣式封裝)
- 樣式最好只包含排版,企業 vis 統一性是標準,沒有必要違背這個
繼續分析 SOA
從上一篇文章的例子可以看出什么呢?
首先,按照功能領域劃分文件,可以很快分析出應用的邏輯結構
也就是邏輯可讀性更強,這個可讀性不只是針對用戶的,還有針對軟件的
比如,TodoService 和 TableHandlerService 有什么關系?
useTableHandlerService

useTableHandlerService

useTodoService
這些邏輯關系,僅僅依靠相關工具就能定位,并生成圖形,輔助你分析領域間的關系
誰依賴誰,一目了然 —— 比如 有個 useState 的值 依賴 useLocalStorageState,肉眼看起來比較困難,但是在圖中一目了然
只是,不具名這一點有點神煩!
還有,React 內部因為沒有管理好這個部分傳遞,沒辦法像 Angular 一樣,瞬間生成一大堆密密麻麻的依賴樹,這就給 React 在大項目工程化上帶來了阻礙
不過一般項目做不到那么大,領域驅動可以幫助你做到 Angular 項目極限的 95%,剩下那 5%,也只是稍稍痛苦些而已,并且,沒有辦法給管理者看到完整藍圖
不過就國內目前前端技術管理者和產品經理的水品,你給他們看 uml 藍圖,我擔心他們也看不懂,所以這部分不用太在意,感覺有地方依賴拿不準,只顯示這個領域的藍圖就好
其次,測試邊界清晰,且易于模擬
視圖你不用測試,因為沒有視圖邏輯,什么時候需要視圖測試?比如 Form 和 FormItem 等出現嵌套注入的地方,需要進行視圖測試,這部分耦合出現的概率非常小,大部分都是第三方框架的工作
你只需要測試這些 useFunction 就好,并且提供兩個個框,比如空組件直接 use,嵌套組件先 provide 再 useContext,然后直接只模擬 useFunction 邊界,并提供測試,大家可以嘗試一下,以前覺得測試神煩,現在可以試試在清晰領域邊界下,測試可以有多愉悅
最后
誰再提狀態管理我和誰急!
你看看這個應用,哪里有狀態管理插手的地方?任何狀態管理庫都不行,它是上個時代的遮羞布
服務間通訊結構
全局單一服務(類 Redux 方案)
但是,單一服務是不得已而為之,老版本沒有邏輯復用導致的
在這種方式下,你的調試將變得無比復雜,任何一處變更將牽扯所有本該封裝為模塊的組件
所以必須配合相應的調試工具
所有多人協作項目,采用此種方式,最后的結果只有項目不可維護一條路!
中臺 + 其他服務(雙層結構)
由一個,appService 提供基礎服務,并管理服務間的調度,此種方式比第一種要好很多,但是還是有個問題,頂層處理服務關系,永遠比服務間處理服務關系來的復雜,具體問題詳見上文 “頂層注入”
樹形結構模塊
這是理論最優的結構,它的優勢不再贅述,上文有提到
劣勢有一個:
跨模塊層級的變更,容易形成循環依賴(也不叫劣勢,因為此種變更對于其他方式來說,是災難)
理清自己的業務邏輯,有必要劃出功能結構圖,再開始開發,是個好習慣,同時,功能層級發生改變,應該敏銳意識到,及時提升服務的模塊層級即可
編程范式
首先,編程范式除了實現方式不同以外,其區別的根源在于 – 關注點不同
- 函數的關注點在于 —— 變化
- 面向對象的關注點在于 —— 結構
對于函數,因為結構方便于處理變化,即輸入輸出是天然關注點,所以 ——
管理狀態和副作用很重要
js
var a = 1;
function test(c) {
var b = 2;
a = 2;
var a = 3;
c = 1;
return { a, b, c };
}
這里故意用 var 來聲明變量,讓大家又更深的體會
在函數中變更函數外的變量 —— 破壞了函數的封裝性
這種破壞極其危險,比如上例,如果其他函數修改了 a,在 重新 賦值之前,你知道 a 是多少么?如果函數很長,你如何確定本地變量 a 是否覆蓋外部變量?
無封裝的函數,不可能有可裝配性和可調試性
所以,使用函數封裝邏輯,不能引入任何副作用!注意,這個是強制的,在任何多人協作,多模塊多資源的項目中 ——
封裝是第一要務,更是基本要求?
所以,你必須將數據(或者說狀態)全部包裹在函數內部,不可以在函數內修改任何函數以外的數據!
所以,函數天然存在一個缺點 —— 封裝性需要人為保證(即你需要自己要求自己,寫出無副作用函數)
當然,還存在很多優點 —— 只需要針對輸入輸出測試,更加符合物體實際運行情況(變化是哲學基礎)
這部分沒有加重點符號,是因為它不重要 —— 對一個思想方法提優缺點,只有指導意義,因為思想方法可以綜合運用,不受限制
再來看看面相對象,來看看類結構:
class Test {
a = 1;
b = 2;
c = 3;
constructor() {
this.changeA();
}
changeA() {
this.a = 2;
}
}
這個結構一眼看去就具有 —— 自解釋性,自封裝性
還有一個涉及應用領域的優勢 —— 對觀念系統的模擬 —— 這個詞不打著重符,不需要太關心,翻譯過來就是,可以直譯人腦中的觀念(動物,人,車等等)
但它也有非常嚴重的問題 —— 初始化,自解耦麻煩,組合麻煩
需要運用到大量的’構建’,’運行’設計模式!
對的,設計模式的那些名字就是怎么來的
其實,你仔細一想,就能明白為什么會這樣 ——
如果你關注變化,想要對真實世界直接模擬,你就需要處理靜態數據,需要自己對一個領域進行人為解釋
如果你關注結構,想要對人的觀念進行模擬,你就需要處理運行時問題,需要自己處理一個運行時對象的生成問題
魚與熊掌,不可兼得,按住了這頭,那頭就會翹起來,你按住了那頭,這頭就會翹起來
想要只通過一個編程范式解決所有問題,就像用手去抓沙子,最后你什么都得不到
極限的函數式,面向對象
通過函數和對象(注意是對象,類是抽象的,觀念中的對象)的分析,很容易發現他們的優勢
函數 —— 測試簡單,模擬真實(效率高)
對象 —— 自封裝性,模擬觀念(繼承多態)
將兩者發揚光大,更加極限地使用,你會得到以下衍生范式:
管道 / 流
既然函數只需要對輸入輸出進行測試,那么,我將無數函數用函數串聯起來,就形成了只有統一輸入輸出的結構
聽不懂?換個說法 ——
只需要 e2e 測試,不需要單元測試!
如果我加上類型校驗,就可以構造出 —— 理想無 bug 系統
這樣的話,你就只剩調試,沒有測試(如果頂層加個校驗取代 e2e 的話)
而且,還有模式識別,異步親和性等很多好處,甚至可以自建設計語言(比如麻省老教材《如何設計計算機語言》就是以 lisp 作為標準)
在 js 中, Cycle.js 和 Rxjs 就是極限的管道風格函數式,還有大家熟悉并且討厭的 Node.js 的 Stream 也是如此,即便他是用 類 實現的,骨子里也是濃濃的函數式
分析一下這樣的系統,你會發現 ——
它首先關注底層邏輯 —— 也就是 or/c , is-a,and/c,not/c 這樣的函數,最后再組裝
按照范疇學的語言(就是函數式的數學解釋,不想看這個可以不看,只是補充說明):

范疇學
u of i2,i2 of g 的講法,與它的真實運行方向,是相反的!
函數的組合方式,與開發目標的構建方式,也是相反的!
它的構建方法叫做 —— 自底向上
這也是為啥你在很多 JS 的庫中發現了好多零零碎碎的東西,還有為何會有 lodash,ramda 等粒度非常小的庫了
在極限函數式編程下 ——
我先做出來,再看能干什么,比先確定干什么,再做,更重要!
因為這部分,可以第三方甚至官方自己提供!
所以,函數式是庫的第一優先級構建范式!因為作為庫的提供者,你根本不可能預測用戶會用這個庫來干什么
領域模塊
函數式可以將其優勢通過管道發揮到極致,面向對象一樣可以將其優勢發揮到極致,這便是領域模塊
領域,就是一系列相同目的,相同功能的資源的集合
比如,學校,公司,這兩個類,如果分別封裝了大量的其他類以及相關資源,共同構成一個整體,自行管理,自行測試,甚至自行構建發布,對外提供統一的接口,那這就是領域
這么說,如果實現了一個類和其相關資源的自行管理,自行測試,這就是 —— DDD
如果實現了對其的自行構建發布,這就是 —— 微服務
這種模型給了應用規模化的能力 —— 橫向,縱向擴展能力
還有高可用,即類的組合間的松散耦合范式
對于這樣的范式,你首先思考的是 —— 你要做什么!
這就是 ——
** 這種模型給了應用規模化的能力 —— 橫向,縱向擴展能力
還有高可用,即類的組合間的松散耦合范式
對于這樣的范式,你首先思考的是 —— 你要做什么!
這就是 —— 自頂向下
- 我要做什么應用?
- 這個應用有哪些功能?
- 我該怎么組織我的資源和代碼?
- 該怎么和其他職能合作?
- 工期需要多久?
現實告訴你,單用任何一種都不行
開發過程中,不止有自底向上封裝的工具,還有自頂向下設計的結構
產品經理不會把要用多少個 isObject 判斷告訴你,他只會告訴你應用有哪些功能
同理,再豐富細致的功能劃分,沒有底層一個個工具函數,也完成不了任何工作
這個世界的確處在變化之中,世界的本質就是變化,就是函數,但是軟件是交給人用,交給人開發的
觀念系統和實際運行,缺一不可!
凡是動不動就跟你說某某框架函數式,某某應用要用函數式開發的人,大多都學藝不精,根本沒有理解這些概念的本質
人類編程歷史如此久遠,真正的面向用戶的純粹函數式無 bug 系統,還沒有出現過……
當然,其在人工智能,科研等領域有無可替代的作用。不過,是人,就有組織,有公司,進而有職能劃分,大家只會通過觀念系統進行交流 —— 你所說的每一個詞匯,都是觀念,都是類!
React 提倡函數式
class OOStyle {
name: string;
password: string;
constructor() {}
get nameStr() {}
changePassword() {}
}
function OOStyleFactory() {
return new OOStyle(/* ... */);
}
這是面向對象風格的寫法(注意,只是風格,不是指只有這個是面向對象)
function funcStyle(name, password) {
return {
name,
password,
getName() {},
changePassword() {},
};
}
這個是函數風格的寫法(注意,這只是風格,這同時也是面向對象)
這兩種風格的邏輯是一樣的,唯一的區別,只在于可讀性
不要理解錯,這里的可讀性,還包括對于程序而言的可讀性,即:
自動生成文檔,自動生成代碼結構
或者由產品設計直接導出代碼框架等功能
但是函數風格犧牲了可讀性,得到了靈活性這一點,也是值得考慮的
編程其實是個權衡過程,對于我來說,我愿意
- 在處理復雜結構時使用 面向對象 風格
- 在處理復雜邏輯時,使用 函數 風格
各取所長,才是最佳方案!
Redux
// redux reducer
function todoApp(state, action) {
if (typeof state === "undefined") {
return initialState;
}
// 這里暫不處理任何 action,
// 僅返回傳入的 state。
return state;
}
這其實就是用函數風格實現的 面向對象 封裝,沒有這一步,你無法進行頂層設計!
用類的寫法來轉換一下 redux 的寫法:
class MonistRedux {
// initial,想要不變性可以將 name,password 組合為 state
name = "";
password = "";
// 惰性初始化(配合工廠)
constructor() {
this.name = "";
this.password = "";
}
// action
changeName() {}
}
只有 函數 的封裝性才受副作用限制
注意這一點,React 程序員非常容易犯的錯誤,就是到了 class 里面還在想純度的問題,恨不得將每個成員函數都變成純函數
沒必要以詞害意,需要融匯貫通
同樣,以上例子也說明,如果你的技術棧提供直接生成對象的方案 —— 你可以只用函數直接完成面向對象和函數式的設計
function Imaclass() {
return {
// ...
};
}
我就說這個是類!為什么不行?
他要成員變量有成員變量,要成員函數有成員函數,封裝,多態,哪個特性沒有?
什么?繼承?這年頭還有搞面向對象的提繼承?組合優于繼承是常識!
拋棄了繼承,你需要 this 么?你不需要,本來你就不需要 this(除了裝飾器等附加邏輯,但是函數本身就能夠實現附加邏輯 —— 高階函數)
同樣,你也可以綜合面向對象和函數式的特點,各取所長,對你的項目進行頂層構建和底層實現
這也是很方便的
Hooks,Composition,ngModule
我們來看看上面的那個函數風格的類
像不像什么東西?
function useThisClass() {
const [val1, setVal1] = useState(0);
const [val2, setVal2] = useState(0);
useEffect(() => {}, []);
const otherObject = useotherClass();
return { val1, setVal1, val2, setVal2, otherObject };
}
Hooks 恭喜各位,用得開心!
以 React 為例,老一代的 React 在 組件結構上是管道,也就是單向數據流,但是對于我們這些使用者來說,我們寫的邏輯,基本上是放養狀態,根本沒有接入 React 的體系,完全游離在函數式風格以外:

老一代的 React
換句話來說,只有 React 寫的代碼才叫函數式風格,你寫的頂多叫函數!
你沒有辦法把邏輯寫在 React 的組件之外,注意,是完全沒有辦法!
好辦,我邏輯全部寫在頂層組件,那不就行了?

新一代的 React
(其中 s/a 指的是 state,action)
為什么要有 state,action?
為什么要在每個組件里用 s/a ?
action 其實是用命令模式,將邏輯復寫為狀態,以便 Context 傳遞,為何?
因為生命周期在組件里,setState 在組件的 this 上
換句話說,框架沒有提供給你,將用戶代碼附加于框架之上的能力!
這個能力,叫做 IOC 控制反轉,即 框架將功能控制權移交到你的手上
不要把這個類 Redux 開發模式作為最自然的開發方式,否則你會非常痛苦!
只有集成度不高的系統,才需要中介模式,才需要 MVC
之前的 React/Vue 集成度不高,沒有 Redux 作為中介者 Controller,你無法將用戶態代碼在架構層級和 React/Vue 產生聯系,并且這個層級天然應該用領域模塊的思想方法來處理問題
因為框架沒有這個能力,所以你才需要這些工具
所謂的狀態管理,所謂的單一 Store ,都是沒有 IOC 的妥協之舉,并且是在完全拋棄面向對象思想的基礎上強行用函數式語言解釋的后果,是一種畸形的技術產物,是框架未達到最終形態之前的臨時方案,不要作為核心技術去學習,這些東西不是核心技術!
回頭看看 React 那些曖昧的話語,有些值得玩味:
- Hook 使你在無需修改組件結構的情況下復用狀態邏輯 (注意!是狀態邏輯,不是狀態,是狀態邏輯一起復用,不是狀態復用)
- 我們推薦用 自定義 hooks 探索更多可能
- 提供漸進式策略,提供 useReducer 實現大對象操作(好的領域封裝哪來的操作大對象?)
他決口不提 面向對象,領域驅動,和之前的設計失誤,是因為他需要顧及影響和社區生態,但是使用者不要被這些欺騙!
當你有了 hooks,Composition ,Service/Module 的時候,你應該主動拋棄所有類似
- 狀態管理
- 一定要寫純函數
- 副作用要一起處理
- 一定要保證不變形
這之類的所有言論,因為在你手上,不僅僅只有函數式一件武器
你還有面向對象和領域驅動
用領域驅動解決高層級問題,用函數式解決低層級問題,才是最佳開發范式
也就是說,函數式和面向對象,沒有好壞,他們只是兩個關注點不同的思想方法而已
你要是被這種思想方法影響,眼里只有對錯 ——
實際上是被忽悠了
管道風格的函數式(unidirectional network 單項數據流)
這是函數式語言基本特性,將一個個符合封裝要求的函數串聯起來,你就能得到統一輸入輸出
函數將淪為算式,單測作用將消失,理想無 bug 系統呼之欲出
它會形如以下結構:
func1(func2(), func3(func4(startParams)));
或者:
func1().func2().func3().func4();
看到這些形態,大家會首先想到什么?
沒錯 jsx 就是第一種結構(是的,jsx 是正宗函數式,純粹無副作用,無需測試,僅需輸入輸出校驗調試)
也沒錯,promise.then().then() 就是第二種,將函數式處理并發,異步的優勢發揮了出來
那第二種和第一種,有什么區別呢?
區別就是 ——
調度
函數是運行時的結構,如果沒有利用模式匹配,每次函數執行只有一個結果,那么整個串行函數管道的返回也只會有一個結果
如果利用了呢?它將會向路牌一樣,指示著邏輯倒流到特定的 lambda 函數中,形成分形結構
請注意,這個分形結構不只是空間上的,還有時間上的
這,就是調度!!!
說的很懸奧,大家領會一下意思就可以了,如果想要了解更多,直接去成熟工具處了解,他們有更多詳細的說明:
微軟出品的 ReactiveX[2] (脫胎于 linq),就是這方面的集大成者,網絡上有非常多的講解,還有視頻演示
Hooks api 就是 React 的調度控制權
useState 整個單項數據流的調度發起者
React 將它的調度控制權,拱手交到了你的手上,這就是 hooks,它的意義絕對不僅僅只是 “在函數式組件中修改狀態” 那么簡單
useState,加上 useReducer (個人不推薦,但是與 useState 返回類型一致)
屬于:響應式對象(注意,這里的對象是 subject,不是 object,有人能幫忙翻譯一下么?)
dispatch 是整個應用開始渲染的根源
沒錯,this.setState 也有同樣功能(但是僅僅是組件內)
useEffect 整個單項數據流調度的指揮者
useEffect 是分形指示器
在 Rxjs 中 被稱作 操作函數(operational function),將你的某部分變更,衍射到另一處變更
在這個 api 中,大量模式匹配得以正常工作
useMemo 整個單項數據流調度的控制者
最后,useMemo
它能夠通過只判斷響應值是否改變,而輸出控制
當然,你可以用 if 語句在 useEffect 中判斷是否改變來實現,但是 —— 模式匹配就是為了不寫 if 啊~
單獨提出 useMemo,是為了將 設計部分 和 運行時調度控制 部分分離,即靜態和動態分離
調度永遠是你真正開始寫函數式代碼,最應該考慮的東西,它是精華
糾結什么是什么并不重要,即便 useMemo = useEffect + if + useState,這些也不是你用這部分 api 的時候應該考慮的問題
最后
你明白這些,再加上 hooks 書寫時的要求:
不要在循環,條件或嵌套函數中調用 Hook,確保總是在你的 React 函數的最頂層調用他們。遵守這條規則,你就能確保 Hook 在每一次渲染中都按照同樣的順序被調用。這讓 React 能夠在多次的useState和useEffect調用之間保持 hook 狀態的正確。
你就能明白,為什么很多人說 React hooks 和 Rx 是同一個東西了
但是請注意,React 只是將它自己的調度控制權交給你,你若是自己再用 rx 等調度框架,且不說 requestAnimationFrame 的調度頻道React占了,兩個調度機制,彌合起來會非常麻煩,小心出現不可調式的大bug
函數式在處理業務邏輯上,有著非常恐怖的鋒利程度,學好了百利而無一害
但是請注意,函數式作為對計算過程的一般抽象,只在組件服務層級以下發揮作用,用它處理通訊,算法,異步,并發都是上上之選
但是在軟件架構層面,函數式沒有任何優勢,組件層級以上,永遠用面向對象的思維審視,是個非常好的習慣~
組件明明就只是邏輯和狀態(服務)調配的地方,它壓根就不應該有邏輯
把邏輯放到服務里
- 堅持在組件中只包含與視圖相關的邏輯。所有其它邏輯都應該放到服務中。
- 堅持把可復用的邏輯放到服務中,保持組件簡單,聚焦于它們預期目的。
- 為何?當邏輯被放置到服務里,并以函數的形式暴露時,可以被多個組件重復使用。
- 為何?在單元測試時,服務里的邏輯更容易被隔離。當組件中調用邏輯時,也很容易被模擬。
- 為何?從組件移除依賴并隱藏實現細節。
- 為何?保持組件苗條、精簡和聚焦。
React DDD 下如何處理類型問題?
泛型約束 InjectionToken
/**
* 泛型約束,對注入數據的類型推斷支持
*
* @export
* @template T
* @param {(...args: any) => T} useFunc
* @param {(T | undefined)} [initialData=undefined]
* @returns
*/
export default function getServiceToken<T>(
useFunc: (...args: any) => T,
initialData: T | undefined = undefined
) {
return createContext(initialData as T);
}
這樣,你的 useContext 就能有完整類型支持了
虛擬數據泛型約束
/**
* 獲得虛擬的服務數據
*
* @export
* @template T
* @param {(...args: any) => T} useFunc
* @param {(T | undefined)} [initialData=undefined]
* @returns
*/
export function getMockService<T>(
useFunc: (...args: any) => T,
initialData: T | undefined = undefined
) {
return initialData as T;
}
類服務
類服務在功能上,其實和函數服務是一樣的
函數返回對象,本身也是構造方式之一,屬于 js 特色 (返回數組的話,本質也是對象)
所以功能上來說,函數服務完全可以覆蓋類服務,實現面向對象
但是,需求可不止有功能一說:
- 需要有更好自解釋性
- 需要更好自封裝性
- 需要更好的可讀性(自動生成文檔,自動分析)
類型自動化:
function getClassContext<T>(constructor: new (...args: any) => T) {
return createContext((undefined as unknown) as T);
}
實現一個類
/**
* some service
*
* @export
* @class SomeService
*/
export class SomeService {
static Context = getClassContext(SomeService);
name: string;
setName: Dispatch<SetStateAction<string>>;
constructor() {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [name, setName] = useState("");
this.name = name;
this.setName = setName;
}
}
使用這個類
最后,自動分析類的結構:
可自動文檔化,在配合其他工具實現框架外需求時,能夠帶給你方便的使用體驗
但是注意,目前官方是禁止在 class 中使用 hooks 的
需要禁用提示
同時,需要保證在 constructor 中使用 hooks
React DDD 下的一些常見問題 FAQ
React DDD 下,class 風格組件能用么?
最好不要用,因為 class 風格組件的邏輯無法提取,無法連接到統一的服務注入樹中,因此會破壞應用的單一數據原則,破壞封裝復用性
所以盡量不要使用,至于目前 getSnapshotBeforeUpdate,getDerivedStateFromError 和 componentDidCatch 的等價寫法 Hooks 尚未加入,這其實并不是大問題,因為在管道風格中,錯誤優先表征為狀態,比如 useRequest 中的 error
React DDD 是相比 之前的 類 redux 分層是更簡單還是更難?
簡單太多了,整個 React DDD 的體系只需要 useXxx 表示服務 Xxx 表示組件,只需要用幾個 hooks api,通過注入提供上下文,不需要高階組件,好的模式也不需要 render props,更不需要太過重視性能調優(別擔心性能問題,除了高消耗運算惰性加載以外),你只是無法在組件層級處理錯誤而已,個人認為,錯誤還是在用戶端處理吧
尤其是在 Typescript 下,你代碼的幾乎任何一處都有完整的類型提示,不需要你去附加類型聲明或者指定 interface
但是,它會將業務問題提前暴露,在沒有想好業務邏輯關系的情況下,請不要下手寫代碼,但是這也不是它的缺點,因為在邏輯錯亂的情況下,分層直接會變得不可維護
React DDD 需要用到什么工具么?
不需要,直接使用 React hooks 就好,沒有其他工具需求
React DDD 性能會有問題么?
不會,React DDD 的方案性能比 class 風格組件 + 類 redux 分層要強得多,而且你可以精細化控制組件的調度和響應式,下限比 redux 上限還要高,上限幾乎可以摸到框架極限
React DDD 適合大體量項目么?
是的,從最小體量項目,到超大體量項目,React DDD 都很適合,原因在于回歸面向對象,承認面向對象對頂層設計有優勢的同時,業務邏輯采用極限函數式開發
無論在架構上,還是業務邏輯實現上,都會將效率,可復用性,封裝度,集成度,可調式,可測試性直接拉滿,所以不用擔心
React DDD 效率高么?
它不是高不高的問題,它是直接可以將大部分業務效率直接提高十倍的大殺器,而且還有很多第三方庫可以被直接使用,讓第三方幫你處理邏輯,比如 ahooks,swr 等等
于此同時,它直接跟業界主流工程化模式對接,有領域模塊的加持,多人協作將變得更加有效率,也能形成特別多的技術資產
Redux 之類的工具還有意義么?
沒有意義了,它只是解決框架沒有 IOC 情況下,保持和框架相同的單向數據流,保持用戶態代碼的脫耦而已,由于狀態分散不易測試,提供一個切面給你調試而已
這種方案相當于強制在前端封層,相當不合理,同時 typescript 支持還很差
在框架有 IOC 的情況下,用戶代碼的狀態邏輯實際上形成了一個和組件結構統一的樹,稱之為邏輯樹或者注入樹,依賴樹,很自然地與組件相統一,很自然地保證單向數據流和一致性
所以,Redux 之類的工具最好不要用,妄圖在應用頂層一個服務解決問題的方法,都很傻
現有項目能直接改成 React DDD 么?
這是它最大的缺點,不能!
因為問題的根源出在框架上,IOC 應該是框架的大變更,個人認為 React 應該直接暴力更新,摒棄所有老舊寫法,如同 15 年的 Angular 一樣,雖然有陣痛,但是對提升社區的好處大于壞處,當然,這是沒有考慮市場的想法
如果你想更純粹使用 React DDD,最好還是采取重構的方案
React DDD 下,應該怎么劃分文件結構?
按照功能劃分,你的功能有哪些包含關系,你的文件結構就是如何
你的功能在哪個范圍需要提供限界上下文,哪里就進行服務注入
所以類似拆分 store,action,models 之類的文件夾,就不要有了,前端沒有數據庫,即便有,也沒有到需要抽象 dao 的程度,即便抽象 dao,dao 本身也是不符合工程化和 DDD 的
所以微前端可以由 React DDD 實現是么?
是的,有了限界上下文,分開開發,分開測試,分開部署都可以實現
但是一定要在相同框架內,個人認為前端采用不同框架開發是個偽需求
現如今 Angular,React,Vue,都有 IOC,寫法都可以互通
你要講模塊剝離,直接構建的時候把模塊文件夾 cp 到指定目錄,覆蓋掉占位的文件夾即可
不過注意,‘微應用’需要有模擬頂層 context 的可選服務,當然,這些東西不管你怎么實現都是需要的
至于說重構兼容老代碼而采用 shadowDom 和 iframe 的,我只能說,作為終端開發,重構兼容老代碼只是臨時狀態,他不能作為架構,更不能作為一個技術來推廣
React DDD 和 Angular 的架構好像,為什么?
因為 Angular 15 年首先實現了 IOC,組件中推薦不要寫邏輯,也是 Angular 最早提出的方案
service(狀態邏輯單元),module(service+component+template+css+staticAssets…),領域模塊封裝也是 Angular 最早提出的,但是因為當時它走太快,社區沒跟上,生命周期復用也麻煩,因為采用裝飾器,組件還保留了一個殼,推進最佳實踐的難度比較大
而 React 的 hooks 可以更加抽象,也更簡單直接,直接就是兩個函數,服務注入也是通過組件,也就是強制與組件保持一致
這時候再推動 DDD 就非常容易且水到渠成了
但是 Angular 的很多特性 React 還不支持,比如組件樣式封裝,多語言依賴到視圖,服務搖樹,動態組件搖樹,異步服務(suspense,concurrent 還在試驗階段),還有真正解決性能問題的大殺器 platform-webworker(能夠在應用層級支持瀏覽器高刷)
但是這些需求,在沒有超大體量(世界級應用)下,用到的可能性很小,不妨礙 React 的普適性
而且 React 社區更活躍,管道風格函數是對社區的依賴是很強的,這方面 Angular 干寫 rxjs 管道有些磨人
React DDD 會是未來趨勢么?
這點我覺得毫無疑問,因為 DDD 是整個軟甲開發架構設計的趨勢,而且這個趨勢伴隨著 微服務 的普及已經不可逆轉,只要前端承認自己是編程,這個趨勢同樣也逃不過去
前端還是單節點,但是未來會有端到端
這個東西現在的可用性易用性在 React 語境下已經相當高了,未來也只會更有用
其他的不用說,光效率的提升,就可以讓開發者大呼過癮了
只是 React/Vue 還有很多歷史問題需要解決,等這些問題解決了, DDD 肯定會大跨步向前的
管道流難度會不會很高?
是的,作為極限函數式開發,在給你提供更好的類型支持,容易調試測試的支持后,首當其沖的,就是純粹函數式的爆炸難度
正常模式下,你是需要先化簡范疇運算式再寫代碼的,不過這明顯老學究了,哈哈哈
但是,React hooks 有非常活躍的社區,你不需要自己實現封裝很多邏輯,這部分可以直接求助于社區實現
需要你實現的管道功能很少
不像 Angular 寫 rxjs ,管道需要自己根據一百多個操作函數配置,腦力負擔太大,并且操作函數都是抽象的,調度權限給到你之后,復雜度又加了個 3 次方
React 的管道復用第三方,大多都是直接面向業務的,比如 swr 和 ahooks ,要直接很多
所以在,真正需要你寫的管道邏輯并不多,這一點值得慶幸
但是,管道風格也是未來趨勢,可以說管道和領域,分別是函數式和面向對象推演到極致的結果,兩者都是最佳范式,兩者都得學習
你只需要學思想方法,而且這樣的思想方法,放諸四海皆可,任何編程平臺,除了特別純粹的那種:無類型 lisp 和無 lambdajava,基本上這些概念都是想通且能夠交流的
管道也是存在于編程的方方面面,elasticSearch,mongo aggregation,node stream,graphQL,等等等等…
謝謝支持
歡迎加關注@Web代碼工,我會第一時間和你分享前端行業趨勢,學習途徑等等。2021 陪你一起度過!