Chapter 1: React Philosophy
Components vs Templates
The Traditional PHP Approach
In PHP, you typically separate:
- The controller: business logic
- The view: HTML template with variables
// Controller
$users = $userRepository->findAll();
include 'views/users.php';
// View (users.php)
<ul>
<?php foreach ($users as $user): ?>
<li><?= htmlspecialchars($user->name) ?></li>
<?php endforeach; ?>
</ul>
The React Approach
In React, logic and rendering are together in a component:
function UserList({ users }) {
// Logic here if needed
const activeUsers = users.filter(u => u.active);
// Rendering
return (
<ul>
{activeUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Why? Because display logic and rendering are intrinsically linked. Artificially separating them complicates maintenance.
The Virtual DOM
The Problem with the Real DOM
Manipulating the DOM is slow. Each modification can trigger a layout recalculation and a browser repaint.
// Classic approach - each line touches the DOM
document.getElementById('name').textContent = user.name;
document.getElementById('email').textContent = user.email;
document.getElementById('role').textContent = user.role;
The React Solution
React maintains a lightweight copy of the DOM in memory (Virtual DOM). When data changes:
- React creates a new Virtual DOM
- Compares it with the old one ("diffing" algorithm)
- Calculates the minimal changes
- Applies only these changes to the real DOM
// You just describe the desired result
function UserCard({ user }) {
return (
<div>
<span id="name">{user.name}</span>
<span id="email">{user.email}</span>
<span id="role">{user.role}</span>
</div>
);
}
// React takes care of optimizing updates
Unidirectional Data Flow
The Problem with Two-Way Binding
In some frameworks, data flows in both directions: the view can modify the model and vice versa. This can create loops and make debugging difficult.
The React Solution
In React, data always flows down from parent to child via props:
App
|
▼ props (users)
UserList
|
▼ props (user)
UserCard
If a child wants to modify data, it calls a function passed by the parent:
function App() {
const [users, setUsers] = useState([]);
const deleteUser = (id) => {
setUsers(users.filter(u => u.id !== id));
};
return <UserList users={users} onDelete={deleteUser} />;
}
function UserList({ users, onDelete }) {
return (
<ul>
{users.map(user => (
<UserCard
key={user.id}
user={user}
onDelete={onDelete}
/>
))}
</ul>
);
}
function UserCard({ user, onDelete }) {
return (
<div>
{user.name}
<button onClick={() => onDelete(user.id)}>Delete</button>
</div>
);
}
Data flows down (props), actions flow up (callbacks).
Declarative vs Imperative
Imperative Approach (jQuery)
You describe how to do things, step by step:
// "When the button is clicked, find the element, modify its content..."
$('#btn').click(function() {
const count = parseInt($('#count').text()) + 1;
$('#count').text(count);
if (count > 10) {
$('#count').addClass('warning');
}
});
Declarative Approach (React)
You describe what you want based on state:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<span className={count > 10 ? 'warning' : ''}>
{count}
</span>
<button onClick={() => setCount(count + 1)}>
+1
</button>
</div>
);
}
You don't say "add the warning class". You say "if count > 10, the class is warning". React handles the rest.
Comparison with Other Approaches
| Concept | PHP/jQuery | React |
|---|---|---|
| Where is the logic? | Separate (MVC) | In the component |
| How to modify UI? | Manipulate the DOM | Change the state |
| Data flow | Bidirectional possible | Unidirectional |
| Paradigm | Imperative | Declarative |
Key Points to Remember
- Components: reusable blocks that encapsulate logic and rendering
- Virtual DOM: React automatically optimizes updates
- Unidirectional flow: data down, actions up
- Declarative: describe the result, not the steps