If you've mastered JavaScript, you'll find that learning front-end frameworks like React, Vue, and Angular becomes significantly easier. These frameworks share many underlying principles and patterns, which means that once you become proficient in one, you'll find it much simpler to pick up the others. Understanding JavaScript fundamentals such as state management, component-based architecture, event handling, and two-way data binding will give you a strong foundation to navigate and excel in any of these frameworks.
In this guide, we'll compare React, Vue, and Angular by looking at how they handle common tasks such as state management, component lifecycle, refs, props, two-way data binding, and event emission from child to parent components. By understanding these core concepts in each framework, you'll see just how similar they are and how your JavaScript skills can be seamlessly transferred from one to another.
In React, state management within a component is handled using hooks such as useState
:
import React, { useState } from "react";
function MyComponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
In Angular, state is typically managed using services and BehaviorSubject
from RxJS for more complex state management:
// counter.service.ts
import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
@Injectable({
providedIn: "root",
})
export class CounterService {
private countSubject = new BehaviorSubject<number>(0);
count$ = this.countSubject.asObservable();
increment() {
this.countSubject.next(this.countSubject.value + 1);
}
}
// my-component.component.ts
import { Component } from "@angular/core";
import { CounterService } from "./counter.service";
@Component({
selector: "app-my-component",
template: `
<div>
<p>Count: {{ count | async }}</p>
<button (click)="increment()">Increment</button>
</div>
`,
})
export class MyComponent {
count = this.counterService.count$;
constructor(private counterService: CounterService) {}
increment() {
this.counterService.increment();
}
}
In Vue, state can be managed using the data
option for local state and Vuex for more complex state management:
// MyComponent.vue
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
};
},
methods: {
increment() {
this.count += 1;
},
},
};
</script>
React uses lifecycle methods like useEffect
to handle side effects:
import React, { useEffect } from "react";
function MyComponent() {
useEffect(() => {
console.log("Component mounted");
return () => {
console.log("Component unmounted");
};
}, []);
return <div>My Component</div>;
}
Angular lifecycle hooks are methods like ngOnInit
and ngOnDestroy
:
import { Component, OnInit, OnDestroy } from "@angular/core";
@Component({
selector: "app-my-component",
template: "<div>My Component</div>",
})
export class MyComponent implements OnInit, OnDestroy {
ngOnInit() {
console.log("Component mounted");
}
ngOnDestroy() {
console.log("Component unmounted");
}
}
Vue lifecycle hooks include methods like mounted
and beforeDestroy
:
export default {
template: "<div>My Component</div>",
mounted() {
console.log("Component mounted");
},
beforeDestroy() {
console.log("Component unmounted");
},
};
React uses useRef
to reference DOM elements:
import React, { useRef } from "react";
function MyComponent() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
In Angular, ViewChild
is used to reference DOM elements:
import { Component, ViewChild, ElementRef } from "@angular/core";
@Component({
selector: "app-my-component",
template: `
<input #inputElement />
<button (click)="focusInput()">Focus Input</button>
`,
})
export class MyComponent {
@ViewChild("inputElement") inputElement: ElementRef;
focusInput() {
this.inputElement.nativeElement.focus();
}
}
Vue uses the ref
attribute to reference DOM elements:
<template>
<div>
<input ref="inputElement" />
<button @click="focusInput">Focus Input</button>
</div>
</template>
<script>
export default {
methods: {
focusInput() {
this.$refs.inputElement.focus();
},
},
};
</script>
In React, props are passed to components via attributes:
function MyComponent({ message }) {
return <div>{message}</div>;
}
// Usage
<MyComponent message="Hello, World!" />;
In Angular, @Input
is used to pass data to child components:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-my-component',
template: '<div>{{ message }}</div>',
})
export class MyComponent {
@Input() message: string;
}
// Usage in a parent component template
<app-my-component [message]="'Hello, World!'"></app-my-component>
In Vue, props are passed via attributes in the template:
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
props: ["message"],
};
</script>
<!-- Usage -->
<MyComponent message="Hello, World!" />
React does not have built-in two-way binding. State is updated via callbacks:
function MyComponent() {
const [value, setValue] = useState("");
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
Angular provides two-way binding with ngModel
:
import { Component } from "@angular/core";
@Component({
selector: "app-my-component",
template: '<input [(ngModel)]="value" />',
})
export class MyComponent {
value: string = "";
}
Vue offers two-way binding with the v-model
directive:
<template>
<input v-model="value" />
</template>
<script>
export default {
data() {
return {
value: "",
};
},
};
</script>
In Vue, a child component can emit an event, and the parent component can listen for that event and respond accordingly.
Child Component (MyChildComponent.vue):
<template>
<button @click="notifyParent">Click me</button>
</template>
<script>
export default {
methods: {
notifyParent() {
this.$emit("childClicked", "Some data from child");
},
},
};
</script>
Parent Component (MyParentComponent.vue):
<template>
<div>
<MyChildComponent @childClicked="handleChildClick" />
</div>
</template>
<script>
import MyChildComponent from "./MyChildComponent.vue";
export default {
components: {
MyChildComponent,
},
methods: {
handleChildClick(data) {
console.log("Event received from child:", data);
},
},
};
</script>
In React, this is typically handled by passing a callback function as a prop to the child component. The child component calls this function to notify the parent component.
Child Component (MyChildComponent.js):
function MyChildComponent({ onChildClick }) {
return (
<button onClick={() => onChildClick("Some data from child")}>
Click me
</button>
);
}
export default MyChildComponent;
Parent Component (MyParentComponent.js):
import React from "react";
import MyChildComponent from "./MyChildComponent";
function MyParentComponent() {
const handleChildClick = (data) => {
console.log("Event received from child:", data);
};
return (
<div>
<MyChildComponent onChildClick={handleChildClick} />
</div>
);
}
export default MyParentComponent;
In Angular, child components emit events using EventEmitter
, and the parent component listens for these events.
Child Component (my-child.component.ts):
import { Component, Output, EventEmitter } from "@angular/core";
@Component({
selector: "app-my-child",
template: '<button (click)="notifyParent()">Click me</button>',
})
export class MyChildComponent {
@Output() childClicked = new EventEmitter<string>();
notifyParent() {
this.childClicked.emit("Some data from child");
}
}
Parent Component (my-parent.component.ts):
import { Component } from "@angular/core";
@Component({
selector: "app-my-parent",
template:
'<app-my-child (childClicked)="handleChildClick($event)"></app-my-child>',
})
export class MyParentComponent {
handleChildClick(data: string) {
console.log("Event received from child:", data);
}
}
- React: Uses hooks and props to manage state and communication. It's flexible and allows for custom solutions.
- Vue: Offers a balance of structure and flexibility with built-in directives like
v-model
and event emission through$emit
. - Angular: Provides a comprehensive framework with built-in solutions for state management, communication, and two-way binding using services,
EventEmitter
, andngModel
.
Understanding these core concepts in each framework will allow you to leverage your JavaScript skills and easily transition between React, Vue, and Angular, making you a versatile and effective front-end developer.