---
# System prepended metadata

title: '[閱讀筆記] Inversion of Control'
tags: [閱讀筆記, JavaScript]

---

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