You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: patterns/dependency-injection/README.md
+17-95Lines changed: 17 additions & 95 deletions
Original file line number
Diff line number
Diff line change
@@ -5,7 +5,7 @@
5
5
6
6
---
7
7
8
-
Big part of the modules/components that we write have dependencies. A proper management of these dependencies is critical for the success of the project. There is a technique (some people consider it as a *pattern*) called [*dependency injection*](http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript) that helps solving the problem.
8
+
Big part of the modules/components that we write have dependencies. A proper management of these dependencies is critical for the success of the project. There is a technique (most people consider it a *pattern*) called [*dependency injection*](http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript) that helps solving the problem.
9
9
10
10
In React the need of dependency injector is easily visible. Let's consider the following application tree:
11
11
@@ -38,7 +38,7 @@ class App extends React.Component {
38
38
};
39
39
```
40
40
41
-
The string "React in patterns" should somehow reach the `Title` component. The direct way of doing this is to pass it from `App` to `Header` and then `Header`to pass it to `Title`. However, this may work for these three components but what happens if there are multiple properties and deeper nesting. Lots of components will have to mention properties that they are not interested in. It is clear that most React components receive their dependencies via props but the question is how these dependencies reach that point.
41
+
The string "React in patterns" should somehow reach the `Title` component. The direct way of doing this is to pass it from `App` to `Header` and then `Header` pass it down to `Title`. However, this may work for these three components but what happens if there are multiple properties and deeper nesting. Lots of components will act as proxy passing properties to their children.
42
42
43
43
We already saw how the [higher-order component](https://github.com/krasimir/react-in-patterns/tree/master/patterns/higher-order-components) may be used to inject data. Let's use the same technique to inject the `title` variable:
44
44
@@ -78,7 +78,7 @@ The `title` is hidden in a middle layer (higher-order component) where we pass i
78
78
79
79
## Using React's context (prior v. 16.3)
80
80
81
-
React has the concept of [*context*](https://facebook.github.io/react/docs/context.html). The *context* is something that every component may have access to. It's something like an [event bus](https://github.com/krasimir/EventBus) but for data. A single *store* which we access from everywhere.
81
+
React has the concept of [*context*](https://facebook.github.io/react/docs/context.html). The *context* is something that every React component has access to. It's something like an [event bus](https://github.com/krasimir/EventBus) but for data. A single *store* which we access from everywhere.
82
82
83
83
```js
84
84
// a place where we'll define the context
@@ -93,7 +93,7 @@ App.childContextTypes = {
93
93
title:React.PropTypes.string
94
94
};
95
95
96
-
// a place where we need data
96
+
// a place where we use the context
97
97
classInjectextendsReact.Component {
98
98
render() {
99
99
var title =this.context.title;
@@ -157,7 +157,7 @@ Title.contextTypes = {
157
157
};
158
158
```
159
159
160
-
Ideally we don't want to specify the `contextTypes` every time when we need an access to the context. This detail may be wrapped in a higher-order component. And even more, we may write an utility function that is more descriptive and helps us declare the exact wiring. I.e instead of accessing the context directly with `this.context.get('title')` we ask the higher-order component to get what we need and to pass it as a prop to our component. For example:
160
+
Ideally we don't want to specify the `contextTypes` every time when we need an access to the context. This detail may be wrapped again in a higher-order component. And even more, we may write an utility function that is more descriptive and helps us declare the exact wiring. I.e instead of accessing the context directly with `this.context.get('title')` we ask the higher-order component to get what we need and to pass it as props to our component. For example:
The `wire` function accepts a React component, then an array with all the needed dependencies (which are `register`ed already) and then a function which I like to call `mapper`. It receives what's stored in the context as a raw data and returns an object which is the actual React props for our component (`Title`). In this example we just pass what we get - a `title` string variable. However, in a real app this could be a collection of data stores, configuration or something else.
175
+
The `wire` function accepts a React component, then an array with all the needed dependencies (which are `register`ed already) and then a function which I like to call `mapper`. It receives what is stored in the context as a raw data and returns an object which is the actual React props for our component (`Title`). In this example we just pass what we get - a `title` string variable. However, in a real app this could be a collection of data stores, configuration or something else.
176
176
177
177
Here is how the `wire` function looks like:
178
178
@@ -203,7 +203,7 @@ For years the context API was not really recommended by Facebook. They mention i
203
203
204
204
Let's use the same example with the string that needs to reach a `<Title>` component.
205
205
206
-
We will start by defining a file that will contain our context creation:
206
+
We will start by defining a file that will contain our context initialization:
`createContext`accepts a default value of our context and returns an object that has `.Provider` and `.Consumer` properties. Those are actually valid React classes. The provider accepts our context and sends it down the React tree. The consumer is used to access the context and basically read data from it. And because they usually live in different files we better create a single place for their initialization.
218
+
`createContext` returns an object that has `.Provider` and `.Consumer` properties. Those are actually valid React classes. The provider accepts our context in the form of a `value` prop. The consumer is used to access the context and basically read data from it. And because they usually live in different files it is a good idea to create a single place for their initialization.
219
219
220
220
Let's say that our `App` component is the root of our tree. At that place we have to pass the context.
221
221
222
222
```js
223
223
import { Provider } from'./context';
224
224
225
+
constcontext= { title:'React In Patterns' };
226
+
225
227
classAppextendsReact.Component {
226
228
render() {
227
229
return (
228
-
<Provider value={ { title:'React In Patterns' } }>
230
+
<Provider value={ context }>
229
231
<Header />
230
232
</Provider>
231
233
);
232
234
}
233
235
};
234
236
```
235
237
236
-
The wrapped components and their children now share the same context. The `<Title>` component is the one that needs the title so that is the place where we use the `<Consumer>`.
238
+
The wrapped components and their children now share the same context. The `<Title>` component is the one that needs the `title` string so that is the place where we use the `<Consumer>`.
237
239
238
240
```js
239
241
import { Consumer } from'./context';
@@ -261,7 +263,7 @@ As we know the typical module system in JavaScript has a caching mechanism. It's
261
263
262
264
> Multiple calls to require('foo') may not cause the module code to be executed multiple times. This is an important feature. With it, "partially done" objects can be returned, thus allowing transitive dependencies to be loaded even when they would cause cycles.
263
265
264
-
How's that helping for our injection? Well, if we export an object we are actually exporting a [singleton](https://addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript) and every other module that imports the file will get the same object. This allows us to `register` our dependencies and later `fetch` them in another file.
266
+
How is that helping for our injection? Well, if we export an object we are actually exporting a [singleton](https://addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript) and every other module that imports the file will get the same object. This allows us to `register` our dependencies and later `fetch` them in another file.
265
267
266
268
Let's create a new file called `di.jsx` with the following content:
267
269
@@ -296,7 +298,7 @@ export function wire(Component, deps, mapper) {
296
298
}
297
299
```
298
300
299
-
We'll store the dependencies in `dependencies` global variable (it's global for our module, not at an application level). We then export two functions `register` and `fetch` that write and read entries. It looks a little bit like implementing setter and getter against a simple JavaScript object. Then we have the `wire` function that accepts our React component and returns a [higher-order component](https://github.com/krasimir/react-in-patterns/tree/master/patterns/higher-order-components). In the constructor of that component we are resolving the dependencies and later while rendering the original component we pass them as props. We follow the same pattern where we describe what we need (`deps` argument) and extract the needed props with a `mapper` function.
301
+
We'll store the dependencies in `dependencies` global variable (it's global for our module, not for the whole application). We then export two functions `register` and `fetch` that write and read entries. It looks a little bit like implementing setter and getter against a simple JavaScript object. Then we have the `wire` function that accepts our React component and returns a [higher-order component](https://github.com/krasimir/react-in-patterns/tree/master/patterns/higher-order-components). In the constructor of that component we are resolving the dependencies and later while rendering the original component we pass them as props. We follow the same pattern where we describe what we need (`deps` argument) and extract the needed props with a `mapper` function.
300
302
301
303
Having the `di.jsx` helper we are again able to register our dependencies at the entry point of our application (`app.jsx`) and inject them wherever (`Title.jsx`) we need.
302
304
@@ -336,88 +338,8 @@ var Title = function(props) {
336
338
exportdefaultwire(Title, ['my-awesome-title'], title=> ({ title }));
337
339
```
338
340
339
-
If we look at the `Title.jsx` file we'll see that the actual component and the wiring may live in different files. That way the component and the mapper function become easily unit testable.
340
-
341
-
## Injecting with the help of a build process
342
-
343
-
We are all processing our JavaScript before shipping it to the browser. This biggest benefit of having an intermediate process is the ability to add features which are normally not there. Like for example the support of [ES6 destructuring](http://krasimirtsonev.com/blog/article/constructive-destructuring-es6-assignment) with [Babel](http://babeljs.io/) or static type checking with [Flow](https://flowtype.org/). There are tools for dependency injection too. [InversifyJS](https://github.com/inversify/InversifyJS) is one of them and in the next section we will see how it works with React components.
344
-
345
-
### Dependency injection powered by an IoC container
346
-
347
-
Not long ago an user in Twitter asked [Michel Weststrate](https://twitter.com/mweststrate)(the author of [MobX](https://github.com/mobxjs/mobx)) the following:
348
-
349
-
> How safe is it to use mobx-react <Provider>? Or are there any other options for connecting stores to components without passing them explicitly through each component?
350
-
351
-
The [answer](https://twitter.com/mweststrate/status/750267384926208000) was the following:
352
-
353
-
> Dependency injection like InversifyJS also works nicely
354
-
355
-
[InversifyJS](https://github.com/inversify/InversifyJS) is an IoC container. We can use an IoC container to inject a value into React components without passing it explicitly through each component and without using the context.
356
-
357
-
In this demonstration we are going to use InversifyJS and [TypeScript](https://github.com/Microsoft/TypeScript). We are using InversifyJS because it works in both Node.js and web browsers. This is an important feature because some React applications use server-side rendering. We are also using TypeScript because it is the recommended by InversifyJS.
358
-
359
-
InversifyJS supports two kinds of injections:
360
-
361
-
- Constructor injection
362
-
- Property injection
363
-
364
-
In order to use "constructor injection" the IoC container needs to be able to create the instances of the classes. In React the components sometimes are just functions (not classes) and we can't delegate the creation of the instances of the components to the IoC container. This means that **constructor injection powered by IoC containers don't play nicely with React**
365
-
366
-
However, **property injection works just fine** considering the fact that we want to pass dependencies to components without passing them explicitly through each component.
367
-
368
-
Let's take a look to a basic example.
369
-
370
-
We need to start by configuring the IoC container. In InversifyJs we need to create a dictionary that maps a type identifier with a type. The dictionary entries are known as "type bindings".
371
-
372
-
In this case, we are binding the identifier `UserStore` to the class `UserStore`. This time the identifier is a Class but InversifyJS also allows us to use `Symbols` or string literals as identifiers. Symbols or string literals are required when we use interfaces.
We also need to generate a decorator using the function `makePropertyInjectDecorator`.
387
-
388
-
The generated `pInject` decorator allows us to flag the properties of a class that we want to be injected:
389
-
390
-
```ts
391
-
import { pInject } from"./utils/di";
392
-
import { UserStore } from"./store/user";
393
-
394
-
classUserextendsReact.Component<any, any> {
395
-
396
-
@pInject(UserStore)
397
-
private userStore:UserStore;
398
-
399
-
public render() {
400
-
return (
401
-
<h1>{this.userStore.pageTitle}</h1>
402
-
);
403
-
}
404
-
}
405
-
```
406
-
407
-
Injected properties are *lazy evaluated*. This means that the value of the `userStore` property is only set after we try to access it for the first time.
408
-
409
-
Based on the [React docs](https://facebook.github.io/react/docs/context.html) we should try to avoid using context:
410
-
411
-
The main advantage of using an IoC container like InversifyJS is that **we are not using the context**!
412
-
413
-
InversifyJS is also great for testing because we can declare a new bindings and inject a mock or stub instead of a real value:
414
-
415
-
```ts
416
-
kernel.bind<UserStore>(UserStore).toConstantValue({ pageTitle: "Some text for testing..." });
417
-
```
418
-
419
-
Find some real use cases of InversifyJS with React [here](https://github.com/Mercateo/dwatch/blob/master/app/src/components/site/LocaleSwitcher.tsx#L12) and [here](https://github.com/Mercateo/dwatch/blob/master/app/src/components/site/Header.tsx#L14) or learn more about InversifyJS at the official repository [here](https://github.com/inversify/InversifyJS).
341
+
*If we look at the `Title.jsx` file we'll see that the actual component and the wiring may live in different files. That way the component and the mapper function become easily unit testable.*
420
342
421
-
###Final thoughts
343
+
## Final thoughts
422
344
423
-
Dependency injection is a tough problem. Especially in JavaScript. It's not really an issue within React application but appears everywhere. At the time of this writing React offers only the `context` as an instrument for resolving dependencies. As we mentioned above this technique should be used sparingly. And of course there are some alternatives. For example using the module system or libraries like InversifyJS.
345
+
Dependency injection is a tough problem. Especially in JavaScript. Lots of people didn't realize but putting a proper dependency management is a key process.
0 commit comments