|
| 1 | +--- |
| 2 | +title: Adding Instance Properties |
| 3 | +type: cookbook |
| 4 | +order: 1.1 |
| 5 | +--- |
| 6 | + |
| 7 | +## Simple Example |
| 8 | + |
| 9 | +There may be data/utilities you'd like to use in many components, but you don't want to [pollute the global scope](https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch3.md). In these cases, you can make them available to each Vue instance by defining them on the prototype: |
| 10 | + |
| 11 | +``` js |
| 12 | +Vue.prototype.$appName = 'My App' |
| 13 | +``` |
| 14 | + |
| 15 | +Now `$appName` is available on all Vue instances, even before creation. If we run: |
| 16 | + |
| 17 | +``` js |
| 18 | +new Vue({ |
| 19 | + beforeCreate: function () { |
| 20 | + console.log(this.$appName) |
| 21 | + } |
| 22 | +}) |
| 23 | +``` |
| 24 | + |
| 25 | +Then `"My App"` will be logged to the console. It's that simple! |
| 26 | + |
| 27 | +## The Importance of Scoping Instance Properties |
| 28 | + |
| 29 | +You may be wondering: |
| 30 | + |
| 31 | +> "Why does `appName` start with `$`? Is that important? What does it do? |
| 32 | + |
| 33 | +No magic is happening here. `$` is simply a convention Vue uses for properties that are available to all instances. This avoids conflicts with any defined data, computed properties, or methods. |
| 34 | + |
| 35 | +> "Conflicts? What do you mean?" |
| 36 | + |
| 37 | +Another great question! If you just set: |
| 38 | + |
| 39 | +``` js |
| 40 | +Vue.prototype.appName = 'My App' |
| 41 | +``` |
| 42 | + |
| 43 | +Then what would you expect to be logged below? |
| 44 | + |
| 45 | +``` js |
| 46 | +new Vue({ |
| 47 | + data: { |
| 48 | + // Uh oh - appName is *also* the name of the |
| 49 | + // instance property we just defined! |
| 50 | + appName: 'The name of some other app' |
| 51 | + }, |
| 52 | + beforeCreate: function () { |
| 53 | + console.log(this.appName) |
| 54 | + }, |
| 55 | + created: function () { |
| 56 | + console.log(this.appName) |
| 57 | + } |
| 58 | +}) |
| 59 | +``` |
| 60 | + |
| 61 | +It would be `"The name of some other app"`, then `"My App"`, because `this.appName` is overwritten ([sort of](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch5.md)) by `data` when the instance is created. We scope instance properties with `$` to avoid this. You can even use your own convention if you'd like, such as `$_appName` or `ΩappName`, to prevent even conflicts with plugins or future features. |
| 62 | + |
| 63 | +## Real-World Example: Replacing Vue Resource with Axios |
| 64 | + |
| 65 | +Let's say you're replacing the [now-retired Vue Resource](https://medium.com/the-vue-point/retiring-vue-resource-871a82880af4). You really enjoyed accessing request methods through `this.$http` and you want to do the same thing with Axios instead. |
| 66 | + |
| 67 | +All you have to do is include axios in your project: |
| 68 | + |
| 69 | +``` html |
| 70 | +<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.2/axios.js"></script> |
| 71 | + |
| 72 | +<div id="app"> |
| 73 | + <ul> |
| 74 | + <li v-for="user in users">{{ user.name }}</li> |
| 75 | + </ul> |
| 76 | +</div> |
| 77 | +``` |
| 78 | + |
| 79 | +Alias `axios` to `Vue.prototype.$http`: |
| 80 | + |
| 81 | +``` js |
| 82 | +Vue.prototype.$http = axios |
| 83 | +``` |
| 84 | + |
| 85 | +Then you'll be able to use methods like `this.$http.get` in any Vue instance: |
| 86 | + |
| 87 | +``` js |
| 88 | +new Vue({ |
| 89 | + el: '#app', |
| 90 | + data: { |
| 91 | + users: [] |
| 92 | + }, |
| 93 | + created () { |
| 94 | + var vm = this |
| 95 | + this.$http.get('https://jsonplaceholder.typicode.com/users') |
| 96 | + .then(function (response) { |
| 97 | + vm.users = response.data |
| 98 | + }) |
| 99 | + } |
| 100 | +}) |
| 101 | +``` |
| 102 | + |
| 103 | +## The Context of Prototype Methods |
| 104 | + |
| 105 | +In case you're not aware, methods added to a prototype in JavaScript gain the context of the instance. That means they can use `this` to access data, computed properties, methods, or anything else defined on the instance. |
| 106 | + |
| 107 | +Let's take advantage of this in a `$reverseText` method: |
| 108 | + |
| 109 | +``` js |
| 110 | +Vue.prototype.$reverseText = function (propertyName) { |
| 111 | + this[propertyName] = this[propertyName].split('').reverse().join('') |
| 112 | +} |
| 113 | + |
| 114 | +new Vue({ |
| 115 | + data: { |
| 116 | + message: 'Hello ' |
| 117 | + }, |
| 118 | + created: function () { |
| 119 | + console.log(this.message) // => "Hello" |
| 120 | + this.$reverseText('message') |
| 121 | + console.log(this.message) // => "olleH" |
| 122 | + } |
| 123 | +}) |
| 124 | +``` |
| 125 | + |
| 126 | +Note that the context binding will __not__ work if you use an ES6/2015 arrow function, as they implicitly bind to their parent scope. That means the arrow function version: |
| 127 | + |
| 128 | +``` js |
| 129 | +Vue.prototype.$reverseText = propertyName => { |
| 130 | + this[propertyName] = this[propertyName].split('').reverse().join('') |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +Would throw an error: |
| 135 | + |
| 136 | +``` log |
| 137 | +Uncaught TypeError: Cannot read property 'split' of undefined |
| 138 | +``` |
| 139 | + |
| 140 | +## When To Avoid This Pattern |
| 141 | + |
| 142 | +As long as you're vigilant in scoping prototype properties, using this pattern is quite safe - as in, unlikely to produce bugs. |
| 143 | + |
| 144 | +However, it can sometimes cause confusion with other developers. They might see `this.$http`, for example, and think, "Oh, I didn't know about this Vue feature!" Then they move to a different project and are confused when `this.$http` is undefined. Or, maybe they want to Google how to do something, but can't find results because they don't realize they're actually using Axios under an alias. |
| 145 | + |
| 146 | +__The convenience comes at the cost of explicitness.__ When just looking at a component, it's impossible to tell where `$http` came from. Vue itself? A plugin? A coworker? |
| 147 | + |
| 148 | +So what are the alternatives? |
| 149 | + |
| 150 | +## Alternative Patterns |
| 151 | + |
| 152 | +### When Not Using a Module System |
| 153 | + |
| 154 | +In applications with __no__ module system (e.g. via Webpack or Browserify), there's a pattern that's often used with _any_ JavaScript-enhanced frontend: a global `App` object. |
| 155 | + |
| 156 | +If what you want to add has nothing to do with Vue specifically, this may be a good alternative to reach for. Here's an example: |
| 157 | + |
| 158 | +``` js |
| 159 | +var App = Object.freeze({ |
| 160 | + name: 'My App', |
| 161 | + description: '2.1.4', |
| 162 | + helpers: { |
| 163 | + // This is a purely functional version of |
| 164 | + // the $reverseText method we saw earlier |
| 165 | + reverseText: function (text) { |
| 166 | + return text.split('').reverse().join('') |
| 167 | + } |
| 168 | + } |
| 169 | +}) |
| 170 | +``` |
| 171 | + |
| 172 | +<p class="tip">If you raised an eyebrow at `Object.freeze`, what it does is prevent the object from being changed in the future. This essentially makes all its properties constants, protecting you from future state bugs.</p> |
| 173 | + |
| 174 | +Now the source of these shared properties is much more obvious: there's an `App` object defined somewhere in the app. To find it, developers need only run a project-wide search. |
| 175 | + |
| 176 | +Another advantage is that `App` can now be used _anywhere_ in your code, whether it's Vue-related or not. That includes attaching values directly to instance options, rather than having to enter a function to access properties on `this`: |
| 177 | + |
| 178 | +``` js |
| 179 | +new Vue({ |
| 180 | + data: { |
| 181 | + appVersion: App.version |
| 182 | + }, |
| 183 | + methods: { |
| 184 | + reverseText: App.helpers.reverseText |
| 185 | + } |
| 186 | +}) |
| 187 | +``` |
| 188 | + |
| 189 | +### When Using a Module System |
| 190 | + |
| 191 | +When you have access to a module system, you can easily organize shared code into modules, then `require`/`import` those modules wherever they're needed. This is the epitome of explicitness, because in each file you gain a list of dependencies. You know _exactly_ each one came from. |
| 192 | + |
| 193 | +While certainly more verbose, this approach is definitely the most maintainable, especially when working with other developers and/or building a large app. |
0 commit comments