# [閱讀筆記] Inversion of Control ###### tags: `閱讀筆記` `JavaScript` <div style="display: flex; justify-content: flex-end"> > created: 2025/06/11 </div> ## 範例 假設目前沒有 `Array.prototpye.filter` 這個 method 可以用,若我想要實作一個篩選 `Array` 內容的函式我大概也會像是用一開始範例提供的方式建立(依據傳入的 config 決定要怎麼篩選): ```javascript= // ref. [Inversion of Control](https://kentcdodds.com/blog/inversion-of-control?utm_source=substack&utm_medium=email#a-word-of-caution) // let's pretend that Array.prototype.filter does not exist function filter(array, { filterNull = true, filterUndefined = true, filterZero = false, filterEmptyString = false } = {}) { let newArray = [] for (let index = 0; index < array.length; index++) { const element = array[index] if ( (filterNull && element === null) || (filterUndefined && element === undefined) || (filterZero && element === 0) || (filterEmptyString && element === '') ) { continue } newArray[newArray.length] = element } return newArray } filter([0, 1, undefined, 2, null, 3, 'four', '']) // [0, 1, 2, 3, 'four', ''] filter([0, 1, undefined, 2, null, 3, 'four', ''], { filterNull: false }) // [0, 1, 2, null, 3, 'four', ''] filter([0, 1, undefined, 2, null, 3, 'four', ''], { filterUndefined: false }) // [0, 1, 2, undefined, 3, 'four', ''] filter([0, 1, undefined, 2, null, 3, 'four', ''], { filterZero: true }) // [1, 2, 3, 'four', ''] filter([0, 1, undefined, 2, null, 3, 'four', ''], { filterEmptyString: true }) // [0, 1, 2, 3, 'four'] ``` 但是當遇到的情境變的更複雜,當 `Array` 不是一般型別是物件型別的話,或者又要新增一個篩選條件的話 `filter` callback 就變得很難使用: ```javascript= filter([{ name: 'LUN', age: 27 }, { name: 'Chun', age: 31 }]) // ??? 不能用啦 ``` ## 反轉控制(Inversion of Control) - 原本從函式內控制的方式轉成由函式外控制 雖然看起來是丟 config 供函式判斷是否要執行,但是實際執行的部分還是在函式內部處理。 ```javascript= // 函式內控制是什麼? function filter(array, { filterNull = true, filterUndefined = true, filterZero = false, filterEmptyString = false } = {}) { let newArray = [] for (let index = 0; index < array.length; index++) { const element = array[index] // 這一些就是函式內控制的部分 if ( (filterNull && element === null) || (filterUndefined && element === undefined) || (filterZero && element === 0) || (filterEmptyString && element === '') ) { continue } newArray[newArray.length] = element } return newArray } ``` - 透過 callback 傳入控制給函式使用,因為在 JavaScript 中 callback 享有一級公民地位(first class citizen) 函式或方法本身,不再追求控制,不再由函式或方法本身,來決定該怎麼做。而是挖一個 callback,讓使用函式的人決定。 ```javascript= function filter(array, filterFn) { let newArray = [] for (let index = 0; index < array.length; index++) { const element = array[index] if (filterFn(element)) { newArray[newArray.length] = element } } return newArray } filter([{ name: 'LUN', age: 27 }, { name: 'Chun', age: 31 }], (element) => element.age === 27) // [{ name: 'LUN', age: 27 }] ``` 也可以像堆積木一樣把 `filter` 當作小零件組合出一個更有語義化的 callback: ```javascript= function filter(array, filterFn) { let newArray = [] for (let index = 0; index < array.length; index++) { const element = array[index] if (filterFn(element)) { newArray[newArray.length] = element } } return newArray } function filterByAage(array, age) { return filter(array, (element) => element.age === age) } filterByAage([{ name: 'LUN', age: 27 }, { name: 'Chun', age: 31 }], 27) // [{ name: 'LUN', age: 27 }] ``` ## 其他應用範例 ### 1. render props 假設我有一個元件,這個元件應該要遍歷生成數個小元件 ```jsx= function SomeComponent(props) { const { list } = props return ( <> {list.map((ele, index) => <SomeOtherComponent {...ele} key={index} />)} </> ) } <SomeComponent list={[ { name: 'LUN', age: 27 }, { name: 'CHUN', age: 31 } ]} /> ``` 但是當 `props.list` 有可能會有其他類型的情況或者 `<SomeComponent />` 不一定要 render `<SomeOtherComponent />` 的話,就可以把原先遍歷生成元件的控制挖空,讓外部傳入決定: ```jsx= function SomeComponent(props) { const { renderer } = props return ( <> {renderer()} </> ) } <SomeComponent renderer={([1, 2, 3]) => <SomeAComponent /> } /> ``` ## 參考資料 - [Inversion of Control](https://kentcdodds.com/blog/inversion-of-control?utm_source=substack&utm_medium=email#a-word-of-caution)(文章本人)