Skip to main content

Module 31: Modern Frameworks Overview

Modern JavaScript frameworks simplify building complex, interactive web applications. This module introduces React, Vue, and Angular fundamentals.


1. React Basics

1.1 React Fundamentals

// React uses JSX (JavaScript XML)
import React from 'react';
import ReactDOM from 'react-dom/client';

// Functional Component
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}

// Arrow function component
const Greeting = ({ message }) => (
<div className="greeting">
<p>{message}</p>
</div>
);

// Rendering
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Welcome name="John" />);

// JSX expressions
function App() {
const user = { name: 'Alice', age: 28 };
const isLoggedIn = true;

return (
<div>
<h1>Welcome, {user.name}</h1>
<p>Age: {user.age}</p>
{isLoggedIn ? <p>Logged in</p> : <p>Please log in</p>}
{isLoggedIn && <button>Logout</button>}
</div>
);
}

1.2 React Hooks

// useState: Manage component state
import { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}

// useEffect: Side effects
import { useEffect } from 'react';

function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
// Runs after render
async function fetchUser() {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
setLoading(false);
}

fetchUser();

// Cleanup function
return () => {
// Cancel requests, clear timers, etc.
};
}, [userId]); // Dependency array

if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;

return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}

// useContext: Share data across components
import { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function App() {
const [theme, setTheme] = useState('light');

return (
<ThemeContext.Provider value={theme}>
<Header />
<Content />
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</ThemeContext.Provider>
);
}

function Header() {
const theme = useContext(ThemeContext);
return <header className={theme}>Header</header>;
}

// useRef: Access DOM elements or persist values
import { useRef } from 'react';

function InputFocus() {
const inputRef = useRef(null);

const focusInput = () => {
inputRef.current.focus();
};

return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}

// useMemo: Memoize expensive calculations
import { useMemo } from 'react';

function ExpensiveComponent({ items }) {
const total = useMemo(() => {
console.log('Calculating total...');
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]); // Only recalculate when items change

return <div>Total: ${total}</div>;
}

// useCallback: Memoize function references
import { useCallback } from 'react';

function TodoList() {
const [todos, setTodos] = useState([]);

const addTodo = useCallback((text) => {
setTodos(prev => [...prev, { id: Date.now(), text }]);
}, []); // Function reference stays the same

return <TodoForm onAdd={addTodo} />;
}

// Custom hooks
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});

useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);

return [value, setValue];
}

// Usage
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
}

1.3 React Component Patterns

// Composition
function Card({ children }) {
return <div className="card">{children}</div>;
}

function App() {
return (
<Card>
<h2>Title</h2>
<p>Content</p>
</Card>
);
}

// Render props
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });

useEffect(() => {
const handleMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};

window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);

return render(position);
}

// Usage
<MouseTracker render={({ x, y }) => (
<p>Mouse position: {x}, {y}</p>
)} />

// Higher-Order Component (HOC)
function withLoading(Component) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) return <div>Loading...</div>;
return <Component {...props} />;
};
}

const UserListWithLoading = withLoading(UserList);

// Controlled components
function Form() {
const [formData, setFormData] = useState({
name: '',
email: ''
});

const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};

const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
};

return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
}
React Philosophy

React is all about components and unidirectional data flow. Data flows down through props, changes flow up through callbacks.


2. Vue Basics

2.1 Vue Fundamentals

// Vue 3 Composition API
import { createApp, ref, computed, watch, onMounted } from 'vue';

// Component definition
const Counter = {
setup() {
const count = ref(0);

const increment = () => {
count.value++;
};

return {
count,
increment
};
},

template: `
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
`
};

// Single File Component (.vue)
<template>
<div class="counter">
<h2>{{ title }}</h2>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
</div>
</template>

<script>
import { ref } from 'vue';

export default {
name: 'Counter',

props: {
initialValue: {
type: Number,
default: 0
}
},

setup(props) {
const count = ref(props.initialValue);
const title = ref('Counter');

const increment = () => {
count.value++;
};

const decrement = () => {
count.value--;
};

return {
title,
count,
increment,
decrement
};
}
};
</script>

<style scoped>
.counter {
padding: 20px;
border: 1px solid #ccc;
}

button {
margin: 0 5px;
}
</style>

2.2 Vue Reactivity

// ref: Reactive primitive values
import { ref } from 'vue';

const count = ref(0);
console.log(count.value); // 0
count.value++; // Triggers reactivity

// reactive: Reactive objects
import { reactive } from 'vue';

const state = reactive({
count: 0,
user: {
name: 'John',
age: 30
}
});

state.count++; // Reactive
state.user.name = 'Jane'; // Reactive

// computed: Computed properties
import { computed } from 'vue';

const firstName = ref('John');
const lastName = ref('Doe');

const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});

console.log(fullName.value); // "John Doe"

// Writable computed
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(value) {
[firstName.value, lastName.value] = value.split(' ');
}
});

// watch: Watch reactive changes
import { watch } from 'vue';

const count = ref(0);

watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});

// Watch multiple sources
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log('Multiple values changed');
});

// watchEffect: Automatically track dependencies
import { watchEffect } from 'vue';

const count = ref(0);
const doubled = ref(0);

watchEffect(() => {
doubled.value = count.value * 2;
console.log('Count or doubled changed');
});

2.3 Vue Directives

<template>
<!-- v-bind: Bind attributes -->
<img v-bind:src="imageUrl" />
<img :src="imageUrl" /> <!-- Shorthand -->

<!-- v-on: Event listeners -->
<button v-on:click="handleClick">Click</button>
<button @click="handleClick">Click</button> <!-- Shorthand -->

<!-- v-model: Two-way binding -->
<input v-model="message" />
<p>{{ message }}</p>

<!-- v-if, v-else-if, v-else: Conditional rendering -->
<div v-if="type === 'A'">Type A</div>
<div v-else-if="type === 'B'">Type B</div>
<div v-else>Other</div>

<!-- v-show: Toggle visibility -->
<div v-show="isVisible">Visible content</div>

<!-- v-for: List rendering -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>

<!-- v-for with index -->
<div v-for="(item, index) in items" :key="index">
{{ index }}: {{ item }}
</div>

<!-- v-for with object -->
<div v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</div>
</template>

<script>
import { ref, reactive } from 'vue';

export default {
setup() {
const message = ref('');
const isVisible = ref(true);
const type = ref('A');
const items = reactive([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]);

return {
message,
isVisible,
type,
items
};
}
};
</script>

2.4 Vue Component Communication

// Props (Parent to Child)
// Parent.vue
<template>
<ChildComponent :message="parentMessage" :count="count" />
</template>

<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

export default {
components: { ChildComponent },
setup() {
const parentMessage = ref('Hello from parent');
const count = ref(0);

return { parentMessage, count };
}
};
</script>

// Child.vue
<template>
<div>
<p>{{ message }}</p>
<p>Count: {{ count }}</p>
</div>
</template>

<script>
export default {
props: {
message: String,
count: {
type: Number,
required: true,
validator: (value) => value >= 0
}
}
};
</script>

// Events (Child to Parent)
// Child.vue
<template>
<button @click="handleClick">Click me</button>
</template>

<script>
export default {
emits: ['customEvent'],

setup(props, { emit }) {
const handleClick = () => {
emit('customEvent', { data: 'Hello' });
};

return { handleClick };
}
};
</script>

// Parent.vue
<template>
<ChildComponent @customEvent="handleCustomEvent" />
</template>

<script>
export default {
setup() {
const handleCustomEvent = (payload) => {
console.log('Received:', payload);
};

return { handleCustomEvent };
}
};
</script>

// Provide/Inject (Deep nesting)
// Parent.vue
import { provide, ref } from 'vue';

export default {
setup() {
const theme = ref('dark');
provide('theme', theme);
}
};

// Descendant.vue
import { inject } from 'vue';

export default {
setup() {
const theme = inject('theme');
return { theme };
}
};
Vue Philosophy

Vue emphasizes progressive adoption and template-based syntax. Start simple, scale as needed.


3. Angular Basics

3.1 Angular Fundamentals

// Component
// app.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
template: `
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
<button (click)="increment()">Increment</button>
`,
styles: [`
h1 { color: blue; }
`]
})
export class AppComponent {
title = 'My App';
count = 0;

increment() {
this.count++;
}
}

// Separate template file
// app.component.html
<div class="container">
<h1>{{ title }}</h1>
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
<button (click)="addItem()">Add Item</button>
</div>

// app.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Todo List';
items: string[] = ['Item 1', 'Item 2'];

addItem() {
this.items.push(`Item ${this.items.length + 1}`);
}
}

3.2 Angular Directives

// Structural directives
// *ngIf
<div *ngIf="isLoggedIn">Welcome back!</div>
<div *ngIf="isLoggedIn; else loginTemplate">Logged in</div>
<ng-template #loginTemplate>
<div>Please log in</div>
</ng-template>

// *ngFor
<ul>
<li *ngFor="let item of items; let i = index; let first = first; let last = last">
{{ i }}: {{ item }}
<span *ngIf="first">(First)</span>
<span *ngIf="last">(Last)</span>
</li>
</ul>

// *ngSwitch
<div [ngSwitch]="status">
<p *ngSwitchCase="'active'">Active</p>
<p *ngSwitchCase="'inactive'">Inactive</p>
<p *ngSwitchDefault>Unknown</p>
</div>

// Attribute directives
// [ngClass]
<div [ngClass]="{ 'active': isActive, 'disabled': !isEnabled }">
Content
</div>

// [ngStyle]
<div [ngStyle]="{ 'color': textColor, 'font-size': fontSize + 'px' }">
Styled text
</div>

// Property binding
<img [src]="imageUrl" [alt]="imageAlt">
<button [disabled]="isDisabled">Click</button>

// Event binding
<button (click)="handleClick()">Click</button>
<input (input)="handleInput($event)" (keyup.enter)="handleEnter()">

// Two-way binding
<input [(ngModel)]="name">
<p>Hello, {{ name }}!</p>

3.3 Angular Services and Dependency Injection

// Service
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root' // Singleton service
})
export class UserService {
private apiUrl = 'https://api.example.com/users';

constructor(private http: HttpClient) {}

getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}

getUser(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}

createUser(user: User): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
}

// Using service in component
// user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
selector: 'app-user-list',
template: `
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`
})
export class UserListComponent implements OnInit {
users: User[] = [];

constructor(private userService: UserService) {}

ngOnInit() {
this.userService.getUsers().subscribe(
users => this.users = users,
error => console.error('Error loading users', error)
);
}
}

3.4 Angular Lifecycle Hooks

import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';

@Component({
selector: 'app-lifecycle',
template: '<p>{{ message }}</p>'
})
export class LifecycleComponent implements OnInit, OnDestroy, OnChanges {
@Input() data: any;
message = '';

constructor() {
console.log('Constructor called');
}

ngOnChanges(changes: SimpleChanges) {
// Called when input properties change
console.log('ngOnChanges', changes);
}

ngOnInit() {
// Called once after component initialization
console.log('ngOnInit');
this.loadData();
}

ngDoCheck() {
// Called during every change detection run
console.log('ngDoCheck');
}

ngAfterContentInit() {
// Called after content projection
console.log('ngAfterContentInit');
}

ngAfterContentChecked() {
// Called after every check of projected content
console.log('ngAfterContentChecked');
}

ngAfterViewInit() {
// Called after component's view initialization
console.log('ngAfterViewInit');
}

ngAfterViewChecked() {
// Called after every check of component's view
console.log('ngAfterViewChecked');
}

ngOnDestroy() {
// Called before component is destroyed
console.log('ngOnDestroy');
// Cleanup subscriptions, timers, etc.
}

loadData() {
// Load data
}
}
Angular Philosophy

Angular is a full-featured framework with everything built-in: routing, forms, HTTP, etc. Opinionated and TypeScript-first.


4. Framework Comparison

4.1 React vs Vue vs Angular

// Counter Example in Each Framework

// REACT
function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

// VUE
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="count++">Increment</button>
</div>
</template>

<script>
import { ref } from 'vue';

export default {
setup() {
const count = ref(0);
return { count };
}
};
</script>

// ANGULAR
@Component({
selector: 'app-counter',
template: `
<div>
<p>Count: {{ count }}</p>
<button (click)="count = count + 1">Increment</button>
</div>
`
})
export class CounterComponent {
count = 0;
}

4.2 Key Differences

FeatureReactVueAngular
TypeLibraryProgressive FrameworkFull Framework
LanguageJavaScript/JSXJavaScript/TemplateTypeScript
Learning CurveMediumEasySteep
Size~45KB~33KB~200KB
Data BindingOne-wayTwo-wayTwo-way
State ManagementExternal (Redux, etc.)Vuex/PiniaRxJS/NgRx
MobileReact NativeVia Capacitor/NativeScriptIonic
Corporate BackingMetaIndependentGoogle
Choosing a Framework
  • React: Large ecosystem, job market, flexibility
  • Vue: Easy to learn, progressive, good documentation
  • Angular: Enterprise apps, full-featured, TypeScript

5. State Management

5.1 React State Management (Redux Toolkit)

// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1; },
decrement: state => { state.value -= 1; },
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});

// Component
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './store';

function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();

return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>Increment</button>
<button onClick={() => dispatch(decrement())}>Decrement</button>
</div>
);
}

5.2 Vue State Management (Pinia)

// stores/counter.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),

getters: {
doubleCount: (state) => state.count * 2
},

actions: {
increment() {
this.count++;
},

async fetchCount() {
const response = await fetch('/api/count');
this.count = await response.json();
}
}
});

// Component
<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment">Increment</button>
</div>
</template>

<script>
import { useCounterStore } from '@/stores/counter';

export default {
setup() {
const counter = useCounterStore();
return { counter };
}
};
</script>

Summary

In this module, you learned:

  • ✅ React fundamentals and hooks
  • ✅ Vue Composition API and reactivity
  • ✅ Angular components and dependency injection
  • ✅ Component patterns across frameworks
  • ✅ State management solutions
  • ✅ Framework comparison and selection criteria
  • ✅ Best practices for each framework
Next Steps

In Module 32, you'll learn about Node.js Fundamentals, building server-side JavaScript applications.


Practice Exercises

  1. Build a todo app in React using hooks
  2. Create a user dashboard in Vue with routing
  3. Build a form with validation in Angular
  4. Implement state management in your chosen framework
  5. Create a reusable component library
  6. Build a real-time chat component
  7. Implement authentication flow
  8. Compare performance of the same app in all three frameworks

Additional Resources