# [閱讀筆記] 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)(文章本人)