# Why do we have Dependency Injection in web development
###### 2020.08.30
###### tags: `自主學習` `翻譯`
---
原始文章
[Why do we have Dependency Injection in web development](https://indepth.dev/why-do-we-have-dependency-injection-in-web-development/)
---
在使用 OOP 語言開發客戶端和伺服器端的應用程式時,使用 DI 這種設計模式已經是開發的一部分。
DI 是一種讓 class 和其依賴達成控制反轉 (IoC) 的 的技巧。
引用自 [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) :
> 閉包(Closure)是函式以及該函式被宣告時所在的作用域環境(lexical environment)的組合。
在 [Vue.js](https://css-tricks.com/building-renderless-vue-components/) 中有 <code>Renderless Components</code>。
在 [React](https://reactjs.org/docs/render-props.html) 中有 <code>Render Props</code>
作者用兩個例子,對照上述兩個前端框架,實作 Angular 中無渲染的功能性元件
<br>
## 使用結構型指令
對照 Vue.js 的 [toggle](https://css-tricks.com/building-renderless-vue-components/) 例子:
``` html=1
<toggle>
<div slot-scope="{ on, setOn, setOff }">
<button @click="click(setOn)">Blue pill</button>
<button @click="click(setOff)">Red pill</button>
<div>
<span v-if="on">It's all a dream, go back to sleep.</span>
<span v-else>
I don't know how far the rabbit hole goes,
I'm not a rabbit, neither do I measure holes.
</span>
</div>
</div>
</toggle>
```
<code>toggle</code> 元件負責提供 API 用來作為畫面渲染的開關。元件本身並不關注畫面的渲染或樣式。
Angular 中的結構型指令,藉由新增和移除 DOM 元素來改變 DOM 版面。並且能夠提供 <code>context</code> 物件讓其他元件使用。
下面是一個藉由 <code>context</code>,暴露 API 給外部的 <code>toggle</code> 元件:
``` javascript=1
type Toggle = {
on: boolean;
setOn: Function;
setOff: Function;
toggle: Function;
}
@Directive({ selector: '[toggle]' })
export class ToggleDirective implements OnInit {
on = true;
@Input('toggleOn') initialState = true;
constructor(private tpl: TemplateRef<{ $implicit: Toggle }>,
private vcr: ViewContainerRef) {
}
ngOnInit() {
this.on = this.initialState;
this.vcr.createEmbeddedView(this.tpl, {
$implicit: {
on: this.on,
setOn: this.setOn,
setOff: this.setOff,
toggle: this.toggle,
}
});
}
setOn() { this.on = true }
setOff() { this.on = false }
toggle() { this.on = !this.on }
}
```
藉由 <code>this.vcr.createEmbeddedView()</code>,我們創造畫面(第一個參數) 並且將 API 藉由 <code>context</code> (第二個參數) 暴露給外部。其中 <code>TemplateRef</code> 的泛型會做為暴露給外部的 <code>context</code> 的類型,IDE 會做型別檢查。
使用 <code>toggle</code> 元件:
``` html=1
<div *toggle="let controller; on: false">
<button (click)="controller.setOn()">Blue pill</button>
<button (click)="controller.setOff()">Red pill</button>
<div>
<span *ngIf="controller.on">...</span>
<span *ngIf="!controller.on">...</span>
</div>
</div>
```

<br>
## 使用 ExportAs
對照 React 的 [Render Props](https://reactjs.org/docs/render-props.html) 例子:
``` javascript=1
class Mouse extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{this.props.children(this.state)}
</div>
);
}
}
```
React 的 <code>Render Props</code> 提供了一種方式,讓封裝在元件內的狀態和行為能夠分享給外部元件。上述例子中,<code>Mouse</code> 元件用來追蹤並保存滑鼠的位置。
因此可以像這樣使用 <code>Mouse</code> 元件:
``` javascript=1
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>
</div>
);
}
}
```
在 Angular 中可以用 <code>exportAs</code> 來實現這樣的功能性元件:
``` javascript=1
@Component({
selector: 'mouse',
exportAs: 'mouse',
template: `
<div (mousemove)="handleMouseMove($event)">
<ng-content></ng-content>
</div>
`
})
export class MouseComponent {
private _state = { x: 0, y: 0 };
get state() {
return this._state;
};
handleMouseMove(event) {
this._state = {
x: event.clientX,
y: event.clientY
};
}
}
```
<code>MouseComponent</code> 並不關注畫面內容,而是藉由 <code>@Component()</code> 裝飾器中 <code>exportAs</code> 屬性,暴露出自己的 API,讓外部元件可以在範本中使用他的 API:
``` javascript=1
@Component({
selector: 'mouse-tracker',
template: `
<mouse #mouse="mouse">
<p>The mouse position is {{ mouse.state.x }}, {{ mouse.state.y }}</p>
</mouse>
`
})
export class MouseTrackerComponent {}
```
上面的範本中的 <code>#mouse="mouse"</code> 即是接取到 <code>MouseComponent</code> 的實例。
也可以用這種方式實作第一個例子中的 <code>toggle</code> 功能。
<br>
## 總結
使用結構型指令的好處,在於可以藉由定義 <code>context</code> 的型別,而明確定義哪些是要暴露給外部的 API,並且可以簡單地控制是否要根據狀態渲染畫面。
如果像 <code>MouseComponent</code> 的例子,還是必須具有部分的畫面(用來定義追蹤滑鼠移動的區塊)時,就可以使用 <code>@Component()</code> 的 <code>exportAs</code> 功能。