Recently I've been shopping around for a framework to replace KnockoutJS in an existing application. While KO has served it's purpose well, over the years it hasn't been maintained very actively and has largely failed to keep up with the newer JS frameworks in terms of features and community adoption.
After doing some research to find it's replacement, I settled on VueJS. It seemed to be most aligned with Knockout's MVVM pattern while at the same time being modular and extensible enough to serve as a complete MVC framework if needed using its official state management & routing libs. Above all, it seems to have a flourishing community which is important when it comes to considering a framework.
So as a KnockoutJS developer, let's go through some of the most familiar aspects of the framework and see how it translates to VueJS.
Viewmodel
In KO, the VM is can be as simple as a object literal or or a function. Here's a simple example:
var yourViewModel = function(args) {
this.someObv = ko.observable();
this.someObv.subscribe(function(newValue) {
//...
});
this.computedSomeObv = ko.computed(function() {
//...
});
this.someMethod = function(item, event) {
//...
}
};
Usage:
ko.applyBindings(new yourViewModel(someArgs), document.getElementById("element_id"));
VueJS has a very similar concept although the VM is always an object literal passed into a Vue instance. It also provides much more structure and richer event model. Here's a VM stub in VueJS:
var yourViewModel = new Vue({
data: {
someKey: someValue
},
watch: {
someKey: function(val) {
// Val has changed, do something, equivalent to ko's subscribe
}
},
computed: {
computedKey: function() {
// Return computed value, equivalent to ko's computed observables
}
},
methods: {
someMethod: function() { ... }
},
created: function () {
// Called synchronously after the instance is created.
},
mounted: function () {
// Called after the instance has just been mounted where el is replaced by the newly created vm.$el
},
updated: function () {
// Called after a data change causes the virtual DOM to be re-rendered and patched.
},
destroyed: function () {
// Called after a Vue instance has been destroyed
},
});
I didn't list all the event hooks in that example for brevity. I recommend checking out the this lifecycle diagram to get the full picture.
VueJS also offers an interesting way to organize and share common code across VMs using something called Mixins. There are certain pros and cons of using a Mixin vs just a plan old JS library but it's worth looking into.
Usage:
yourViewModel.$mount(document.getElementById("element_id"));
Something to note about the syntax above, it is entirely optional. You can also set the value of the el
attribute in your VM to #element_id
and skip explicitly calling the mount function.
Bindings
The concept of bindings is something KO developers are very familiar with. I'm sure throughout the course of working with KO, we've all created or used a lot of custom bindings. Here's what the custom binding stub looks like in KO:
ko.bindingHandlers.yourBindingName = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// This will be called once when the binding is first applied to an element,
// and again whenever any observables/computeds that are accessed change
// Update the DOM element based on the supplied values here.
}
};
Usage:
<span data-bind="yourBindingName: { some: args }" />
VueJS has something similar but it's called a "directive". Here's the VueJS directive stub:
Vue.directive('yourDirectiveName', {
bind: function(element, binding, vnode) {
// called only once, when the directive is first bound to the element. This is where you can do one-time setup work.
},
inserted: function (element, binding, vnode) {
// called when the bound element has been inserted into its parent node (this only guarantees parent node presence, not // necessarily in-document).
},
update: function(element, binding, vnode, oldVnode) {
// called after the containing component has updated, but possibly before its children have updated. The directive’s value may // or may not have changed, but you can skip unnecessary updates by comparing the binding’s current and old values
},
componentUpdated: function(element, binding, vnode, oldVnode) {
// called after the containing component and its children have updated.
},
unbind: function(element, binding, vnode) {
// called only once, when the directive is unbound from the element.
},
})
Usage:
<span v-bind="{yourDirectiveName: '{ some: args }' }" />
As you can see VueJS offers a couple of additional life-cycle hooks, but on the most part, its very similar to KnockoutJS. So transferring old bindings into new directives isn't too difficult.
In most cases you should be able to move everything in your init
function into the inserted
function. As far as the update
function goes, it will largely remain the same but you can now compare the vnode
and oldVnode
to avoid necessary updates. And lastly, if your custom binding used the KO's disposal callback i.e ko.utils.domNodeDisposal.addDisposeCallback
you can move that logic into the unbind
function.
Another thing you'll notice is that the usage syntax is a bit different, instead of using the data-bind
attribute everywhere, VueJS uses different attributes prefixed with v-
for various things such as v-bind
for binding attributes, v-on
for binding events, v-if/for
for conditionals/loops, etc.
To add to that, there is also a shorthand syntax for those which might make things confusing initially and it's probably the biggest gotchas for devs transitioning from Knockout to Vue. So I recommend taking some time to go through the template syntax documentation.
Extenders
Another tool in KO that we are very familiar with is the concept of extender which are useful for augmenting observables. Here's a simple stub for an extender:
ko.extenders.yourExtender = function (target, args) {
// Observe / manipulate the target based on args and returns the value
};
Usage:
<span data-bind="text: yourObv.extend({ yourExtender: args })" />
Closest thing to extenders in VueJS is the concept of "filters", which can be used to achieve a similar objective. Here's what a filter stub would look like:
Vue.filter('yourFilter', function (value, args) {
// Manipulate the value based on the args and return the result
});
Usage:
<span>{{ "{{" }} yourVar | yourFilter(args) }}</span>
Alternatively you can also call a filter function inside the v-bind
attribute
<span v-bind='{style: {width: $options.filters.yourFilter(yourVar, args)}}'/>
Components
KO offers the ability to create components to help organizing the UI code into self-contained, reusable chunks. Here's a simple component stub:
ko.components.register('your-component', {
viewModel: function(params) {
this.someObv = ko.observable(params.someValue);
},
template: { element: 'your-component-template' },
});
Usage:
<your-component params='someValue: "Hello, world!"'></your-component>
VueJS also has the ability to create components. They are a much more feature rich and have better lifecycle hooks compared to KO. They also feel more much "native" to the framework. Here's a simple component stub in Vue:
Vue.component('your-component', {
props: ['someValue']
data: function () {
return {
someKey: this.someValue
}
},
template: '#your-component-template'
})
Usage:
<your-component someValue="Hello, world!"></your-component>
This just scratches the surface of what's possible with components in Vue. They are definitely worth diving more into. Maybe I'll cover them more in another post.
3rd Party Plugins/Libs/Tools
Mapping - One of the commonly used plugin the KnockoutJS ecosystem has been ko.mapping plugin which helps transforming a JavaScript object into appropriate observables. With VueJS, that is not needed since Vue takes care of that under the hood by walking through all the properties of a VM and converting them to getter/setters using Object.defineProperty
. This lets Vue perform dependency-tracking and change-notification when properties are accessed or modified while keeping that invisible to the user.
Validation - In addition to mapping, Knockout-Validation library is another mainstay of the ecosystem. With VueJS, vee-validate is it's popular counterpart and provides similar features out of the box.
Debugging - It's important to have a good debugging tool for development. KnockoutJS has Knockoutjs context debugger, while VueJS offers something similar with Vue.js devtools
Lastly...
VueJS is an incredibly feature rich framework with various options for customization and code organization. It is one of the fastest growing frameworks with adoption from some big names projects such as Laravel, GitLab, and PageKit to name a few. Hopefully that will make it a good bet for the future!
I'll leave you with this chart which pretty much sums up the story of these two frameworks:
This post was originally published on my blog. If you liked this post, please share it on social media and follow me on Twitter!
Top comments (0)