Ottimizzare le prestazioni delle React app Strategie per evitare re-render inutili
Mirco Bellagamba Chi è sviluppatore front-end? Chi utilizza o ha utilizzato React? Perché avviene un re-render? Il ciclo di React Trigger Render Commit Quiz Che valore si visualizza quando si clicca "+3"?
import { useState } from 'react' ;
export default function Counter ( ) {
const [number , setNumber] = useState (0 );
return (
<>
<h1 > {number}</h1 >
<button onClick ={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button >
</>
)
}
0 +3 Quali cause provocano il re-render di un componente? Aggiornamento di stato del componente Re-render del parent component Aggiornamento del context Aggiornamento di un hook Aggiornamento delle props? Le props non influenzano il re-render di un componente*
import { useState } from 'react' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
let nextTaskId = initialTasks.length + 1 ;
function App ( ) {
const [theme, setTheme] = useState ('dawn' );
const [inputValue, setInputValue] = useState ('' );
const [tasks, setTasks] = useState (initialTasks);
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const onAddTask = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : inputValue, completed : false },
]);
setInputValue ('' );
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
</div >
</RenderCounter >
);
}
export default App ;
import { useState } from 'react' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
let nextTaskId = initialTasks.length + 1 ;
function App ( ) {
const [theme, setTheme] = useState ('dawn' );
const [inputValue, setInputValue] = useState ('' );
const [tasks, setTasks] = useState (initialTasks);
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const onAddTask = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : inputValue, completed : false },
]);
setInputValue ('' );
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
</div >
</RenderCounter >
);
}
export default App ;
import { useState } from 'react' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
let nextTaskId = initialTasks.length + 1 ;
function App ( ) {
const [theme, setTheme] = useState ('dawn' );
const [inputValue, setInputValue] = useState ('' );
const [tasks, setTasks] = useState (initialTasks);
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const onAddTask = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : inputValue, completed : false },
]);
setInputValue ('' );
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
</div >
</RenderCounter >
);
}
export default App ;
import { useState } from 'react' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
let nextTaskId = initialTasks.length + 1 ;
function App ( ) {
const [theme, setTheme] = useState ('dawn' );
const [inputValue, setInputValue] = useState ('' );
const [tasks, setTasks] = useState (initialTasks);
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const onAddTask = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : inputValue, completed : false },
]);
setInputValue ('' );
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
</div >
</RenderCounter >
);
}
export default App ;
import { useState } from 'react' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
let nextTaskId = initialTasks.length + 1 ;
function App ( ) {
const [theme, setTheme] = useState ('dawn' );
const [inputValue, setInputValue] = useState ('' );
const [tasks, setTasks] = useState (initialTasks);
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const onAddTask = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : inputValue, completed : false },
]);
setInputValue ('' );
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
</div >
</RenderCounter >
);
}
export default App ;
import { useState } from 'react' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
let nextTaskId = initialTasks.length + 1 ;
function App ( ) {
const [theme, setTheme] = useState ('dawn' );
const [inputValue, setInputValue] = useState ('' );
const [tasks, setTasks] = useState (initialTasks);
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const onAddTask = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : inputValue, completed : false },
]);
setInputValue ('' );
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
</div >
</RenderCounter >
);
}
export default App ;
{Inserire un meme divertente del boss arrabbiato per mancanza di guadagni}
import { useState } from 'react' ;
import { AdsBanner } from './components/ads-banner' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
let nextTaskId = initialTasks.length + 1 ;
function App ( ) {
const [theme, setTheme] = useState ('dawn' );
const [inputValue, setInputValue] = useState ('' );
const [tasks, setTasks] = useState (initialTasks);
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const onAddTask = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : inputValue, completed : false },
]);
setInputValue ('' );
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
<AdsBanner />
</div >
</RenderCounter >
);
}
export default App ;
Components composition Moving the state down : se uno stato è utilizzato da un solo componente figlio o da un sottoinsieme di componenti figli, delegare la gestione dello stato a questo componente.
import { useState } from 'react' ;
import { AdsBanner } from './components/ads-banner' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
let nextTaskId = initialTasks.length + 1 ;
function App ( ) {
const [theme, setTheme] = useState ('dawn' );
const [inputValue, setInputValue] = useState ('' );
const [tasks, setTasks] = useState (initialTasks);
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const onAddTask = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : inputValue, completed : false },
]);
setInputValue ('' );
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
<AdsBanner />
</div >
</RenderCounter >
);
}
export default App ;
import { useState } from 'react' ;
import { AdsBanner } from './components/ads-banner' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
let nextTaskId = initialTasks.length + 1 ;
function App ( ) {
const [theme, setTheme] = useState ('dawn' );
const [inputValue, setInputValue] = useState ('' );
const [tasks, setTasks] = useState (initialTasks);
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const onAddTask = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : inputValue, completed : false },
]);
setInputValue ('' );
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
<AdsBanner />
</div >
</RenderCounter >
);
}
export default App ;
add-task-form.tsx
import { useState } from 'react' ;
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
export function AddTaskForm ({ onSubmit }: FormProps ) {
const [inputValue, setInputValue] = useState ('' );
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const handleSubmit = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
onSubmit (inputValue);
setInputValue ('' );
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
import { useState } from 'react' ;
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
export function AddTaskForm ({ onSubmit }: FormProps ) {
const [inputValue, setInputValue] = useState ('' );
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const handleSubmit = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
onSubmit (inputValue);
setInputValue ('' );
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
<div className ="card" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
...
</div >
<div className ="card" >
<AddTaskForm onSubmit ={onAddTask} />
...
</div >
Components composition Moving the state down : se uno stato è utilizzato da un solo componente figlio o da un sottoinsieme di componenti figli, delegare la gestione dello stato a questo componente.Encapsulate the parent state : se lo stato di un parent component non influenza lo stato dei figli, creare un componente parent che si gestisce autonomamente lo stato e che accetta una children prop.
import { useState } from 'react' ;
import { AdsBanner } from '../components/ads-banner' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
import { AddTaskForm } from '../components/add-task-form' ;
let nextTaskId = initialTasks.length + 1 ;
function TodoApp ( ) {
const [theme, setTheme] = useState ('dawn' );
const [tasks, setTasks] = useState (initialTasks);
const onAddTask = (newTask: string ) => {
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : newTask, completed : false },
]);
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<AddTaskForm onSubmit ={onAddTask} />
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
<AdsBanner />
</div >
</RenderCounter >
);
}
export default TodoApp ;
import { useState } from 'react' ;
import { AdsBanner } from '../components/ads-banner' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
import { AddTaskForm } from '../components/add-task-form' ;
let nextTaskId = initialTasks.length + 1 ;
function TodoApp ( ) {
const [theme, setTheme] = useState ('dawn' );
const [tasks, setTasks] = useState (initialTasks);
const onAddTask = (newTask: string ) => {
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : newTask, completed : false },
]);
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" >
<AddTaskForm onSubmit ={onAddTask} />
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</div >
<AdsBanner />
</div >
</RenderCounter >
);
}
export default TodoApp ;
app-layout.tsx
import { useState } from 'react' ;
import { RenderCounter } from './render-counter' ;
type AppLayoutProps = React .PropsWithChildren <{ title : string }>;
export function AppLayout ({ children, title }: AppLayoutProps ) {
const [theme, setTheme] = useState ('dawn' );
return (
<RenderCounter title ="AppLayout" color ="#f6f4a8" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > {title}</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" > {children}</div >
</div >
</RenderCounter >
);
}
import { useState } from 'react' ;
import { RenderCounter } from './render-counter' ;
type AppLayoutProps = React .PropsWithChildren <{ title : string }>;
export function AppLayout ({ children, title }: AppLayoutProps ) {
const [theme, setTheme] = useState ('dawn' );
return (
<RenderCounter title ="AppLayout" color ="#f6f4a8" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > {title}</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" > {children}</div >
</div >
</RenderCounter >
);
}
import { useState } from 'react' ;
import { RenderCounter } from './render-counter' ;
type AppLayoutProps = React .PropsWithChildren <{ title : string }>;
export function AppLayout ({ children, title }: AppLayoutProps ) {
const [theme, setTheme] = useState ('dawn' );
return (
<RenderCounter title ="AppLayout" color ="#f6f4a8" >
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > {title}</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
<div className ="card" > {children}</div >
</div >
</RenderCounter >
);
}
return (
<div className ={ `app ${theme }`}>
<div className ="header" >
<h1 > Todo App</h1 >
<label >
<small className ="visually-hidden" > Theme</small >
<select
className ="theme-select"
value ={theme}
onChange ={(ev) => setTheme(ev.target.value)}
>
<option value ="dawn" > Dawn</option >
<option value ="noon" > Noon</option >
<option value ="sunset" > Sunset</option >
<option value ="midnight" > Midnight</option >
</select >
</label >
</div >
...
</div >
);
return (
<AppLayout title ="Todo App" >
...
</AppLayout >
);
import { useState } from 'react' ;
import { AdsBanner } from '../components/ads-banner' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
import { AddTaskForm } from '../components/add-task-form' ;
import { AppLayout } from '../components/app-layout' ;
let nextTaskId = initialTasks.length + 1 ;
function TodoApp ( ) {
const [tasks, setTasks] = useState (initialTasks);
const onAddTask = (newTask: string ) => {
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : newTask, completed : false },
]);
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<AppLayout title ="Todo App" >
<AddTaskForm onSubmit ={onAddTask} />
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
<AdsBanner />
</AppLayout >
</RenderCounter >
);
}
export default TodoApp ;
import { useState } from 'react' ;
import { AdsBanner } from '../components/ads-banner' ;
import { RenderCounter } from '../components/render-counter' ;
import initialTasks from '../data/initial-tasks' ;
import { AddTaskForm } from '../components/add-task-form' ;
import { AppLayout } from '../components/app-layout' ;
let nextTaskId = initialTasks.length + 1 ;
function TodoApp ( ) {
const [tasks, setTasks] = useState (initialTasks);
const onAddTask = (newTask: string ) => {
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : newTask, completed : false },
]);
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<AppLayout title ="Todo App" >
<AddTaskForm onSubmit ={onAddTask} />
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
<AdsBanner />
</AppLayout >
</RenderCounter >
);
}
export default TodoApp ;
import { useState } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
import { AddTaskForm } from '../components/add-task-form' ;
import { RenderCounter } from './render-counter' ;
let nextTaskId = initialTasks.length + 1 ;
export function TasksView ( ) {
const [tasks, setTasks] = useState (initialTasks);
const onAddTask = (newTask: string ) => {
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : newTask, completed : false },
]);
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="TasksView" color ="#add8e6" >
<AddTaskForm onSubmit ={onAddTask} />
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</RenderCounter >
);
}
import { useState } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
import { AddTaskForm } from '../components/add-task-form' ;
import { RenderCounter } from './render-counter' ;
let nextTaskId = initialTasks.length + 1 ;
export function TasksView ( ) {
const [tasks, setTasks] = useState (initialTasks);
const onAddTask = (newTask: string ) => {
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : newTask, completed : false },
]);
};
const onToggleTask = (taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
};
return (
<RenderCounter title ="TasksView" color ="#add8e6" >
<AddTaskForm onSubmit ={onAddTask} />
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</RenderCounter >
);
}
import { AdsBanner } from '../components/ads-banner' ;
import { RenderCounter } from '../components/render-counter' ;
import { AppLayout } from '../components/app-layout' ;
import { TasksView } from '../components/tasks-view' ;
function TodoApp ( ) {
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<AppLayout title ="Todo App" >
<TasksView />
<AdsBanner />
</AppLayout >
</RenderCounter >
);
}
export default TodoApp ;
Uncontrolled forms Controlled input : consentono di controllare il loro comportamento tramite uno stato di React, es. value, e una funzione di callback, es. onChange.Uncontrolled input : lo stato del componente è gestito dal DOM. È possibile soltanto specificare il valore iniziale defaultValue.Parlando in generale di uncontrolled components ci si riferisce ai componenti il cui stato può essere gestito passando delle props. Viceversa un “uncontrolled components” nasconde all‘esterno la gestione dello stato. Uncontrolled input ✅ Non richiede la gestione diretta tramite uno state evitando che il componente sia renderizzato ad ogni cambiamento. ❌ Non è reattivo: è più difficile eseguire azioni in base allo stato, come la validazione o gestire dipendenze tra input. https://www.epicreact.dev/improve-the-performance-of-your-react-forms https://react-hook-form.com
Si può limitare la mancanza di reattività tramite la “state colocation”: creando un componente che gestisce lo stato dell‘input e uno state che indica se l‘utente ha interagito con l‘input. AddTaskForm.tsx
import { useState } from 'react' ;
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
export function AddTaskForm ({ onSubmit }: FormProps ) {
const [inputValue, setInputValue] = useState ('' );
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const handleSubmit = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
onSubmit (inputValue);
setInputValue ('' );
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
import { useState } from 'react' ;
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
export function AddTaskForm ({ onSubmit }: FormProps ) {
const [inputValue, setInputValue] = useState ('' );
const onInputChange = (ev: React.ChangeEvent<HTMLInputElement> ) => {
setInputValue (ev.target .value );
};
const handleSubmit = (ev: React.FormEvent<HTMLFormElement> ) => {
ev.preventDefault ();
onSubmit (inputValue);
setInputValue ('' );
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
AddTaskForm.tsx
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
interface AddTasksControlsCollection extends HTMLFormControlsCollection {
task : HTMLInputElement ;
}
interface AddTasksFormElement extends HTMLFormElement {
readonly elements : AddTasksControlsCollection ;
}
export function AddTaskForm ({ onSubmit }: FormProps ) {
const handleSubmit = (ev: React.FormEvent<AddTasksFormElement> ) => {
ev.preventDefault ();
onSubmit (ev.currentTarget .elements .task .value );
ev.currentTarget .reset ();
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
interface AddTasksControlsCollection extends HTMLFormControlsCollection {
task : HTMLInputElement ;
}
interface AddTasksFormElement extends HTMLFormElement {
readonly elements : AddTasksControlsCollection ;
}
export function AddTaskForm ({ onSubmit }: FormProps ) {
const handleSubmit = (ev: React.FormEvent<AddTasksFormElement> ) => {
ev.preventDefault ();
onSubmit (ev.currentTarget .elements .task .value );
ev.currentTarget .reset ();
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
interface AddTasksControlsCollection extends HTMLFormControlsCollection {
task : HTMLInputElement ;
}
interface AddTasksFormElement extends HTMLFormElement {
readonly elements : AddTasksControlsCollection ;
}
export function AddTaskForm ({ onSubmit }: FormProps ) {
const handleSubmit = (ev: React.FormEvent<AddTasksFormElement> ) => {
ev.preventDefault ();
onSubmit (ev.currentTarget .elements .task .value );
ev.currentTarget .reset ();
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
interface AddTasksControlsCollection extends HTMLFormControlsCollection {
task : HTMLInputElement ;
}
interface AddTasksFormElement extends HTMLFormElement {
readonly elements : AddTasksControlsCollection ;
}
export function AddTaskForm ({ onSubmit }: FormProps ) {
const handleSubmit = (ev: React.FormEvent<AddTasksFormElement> ) => {
ev.preventDefault ();
onSubmit (ev.currentTarget .elements .task .value );
ev.currentTarget .reset ();
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
interface AddTasksControlsCollection extends HTMLFormControlsCollection {
task : HTMLInputElement ;
}
interface AddTasksFormElement extends HTMLFormElement {
readonly elements : AddTasksControlsCollection ;
}
export function AddTaskForm ({ onSubmit }: FormProps ) {
const handleSubmit = (ev: React.FormEvent<AddTasksFormElement> ) => {
ev.preventDefault ();
onSubmit (ev.currentTarget .elements .task .value );
ev.currentTarget .reset ();
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
AddTaskForm.tsx
import { RenderCounter } from './render-counter' ;
type FormProps = { onSubmit : (newTask: string ) => void };
export function AddTaskForm ({ onSubmit }: FormProps ) {
const handleSubmit = (data: FormData ) => {
onSubmit (data.get ('task' ) as string );
};
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" action ={handleSubmit} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
Split large context React Context offre un modo integrato per condividere lo stato tra i componenti all'interno dello stesso albero, eliminando la necessità di passare le props manualmente.
Split large context La comodità di uso del Context è anche il suo più grande problema.
Aggiungere proprietà non correlate a context object complessi può causare re-render inefficaci poiché ogni aggiornamento del Context forza il re-render di tutti i componenti che lo utilizzano.
Per applicazioni su larga scala con stato che cambia frequentemente, librerie di gestione dello stato dedicate come Redux, MobX, Zustand, ecc. sono generalmente scelte migliori.
import { RenderCounter } from '../components/render-counter' ;
import { AddTaskForm } from './add-task-form' ;
import { AppContextProvider } from './app-context' ;
import { AppLayout } from './app-layout' ;
import { TasksList } from './tasks-list' ;
function TodoApp ( ) {
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<AppContextProvider >
<AppLayout title ="Todo App" >
<AddTaskForm />
<TasksList />
</AppLayout >
</AppContextProvider >
</RenderCounter >
);
}
export default TodoApp ;
app-context.tsx
import { createContext, useContext, useReducer } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
const initialState = {
theme : 'dawn' ,
tasks : initialTasks,
inputValue : '' ,
};
type Task = { id : number ; text : string ; completed : boolean };
type State = {
theme : string ;
tasks : Task [];
inputValue : string ;
};
type Action =
| { type : 'addTask' ; text : string }
| { type : 'toggleComplete' ; taskId : number }
| { type : 'changeTheme' ; theme : string }
| { type : 'editNewTask' ; text : string };
let nextTaskId = initialTasks.length + 1 ;
function reducer (state: State, action: Action ) {
switch (action.type ) {
case 'addTask' :
return {
...state,
tasks : state.tasks .concat ({
id : nextTaskId++,
text : action.text ,
completed : false ,
}),
};
case 'editNewTask' :
return { ...state, inputValue : action.text };
case 'changeTheme' :
return { ...state, theme : action.theme };
case 'toggleComplete' : {
const taskIndex = state.tasks .findIndex ((t ) => t.id === action.taskId );
if (taskIndex > -1 ) {
const task = state.tasks [taskIndex];
return {
...state,
tasks : state.tasks
.slice (0 , taskIndex)
.concat ({ ...task, completed : !task.completed })
.concat (state.tasks .slice (taskIndex + 1 )),
};
}
return state;
}
default :
return state;
}
}
type TodoAppContextType = [State , React .Dispatch <Action >];
const TodoAppContext = createContext<TodoAppContextType >([
initialState,
() => {},
]);
function useAppContext ( ) {
const context = useContext (TodoAppContext );
if (!context) {
throw new Error ('useAppContext must be used within a TodoAppProvider' );
}
return context;
}
function AppContextProvider ({ children }: { children: React.ReactNode } ) {
const [state, dispatch] = useReducer (reducer, initialState);
return (
<TodoAppContext.Provider value ={[state, dispatch ]}>
{children}
</TodoAppContext.Provider >
);
}
export { AppContextProvider , useAppContext };
import { createContext, useContext, useReducer } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
const initialState = {
theme : 'dawn' ,
tasks : initialTasks,
inputValue : '' ,
};
type Task = { id : number ; text : string ; completed : boolean };
type State = {
theme : string ;
tasks : Task [];
inputValue : string ;
};
type Action =
| { type : 'addTask' ; text : string }
| { type : 'toggleComplete' ; taskId : number }
| { type : 'changeTheme' ; theme : string }
| { type : 'editNewTask' ; text : string };
let nextTaskId = initialTasks.length + 1 ;
function reducer (state: State, action: Action ) {
switch (action.type ) {
case 'addTask' :
return {
...state,
tasks : state.tasks .concat ({
id : nextTaskId++,
text : action.text ,
completed : false ,
}),
};
case 'editNewTask' :
return { ...state, inputValue : action.text };
case 'changeTheme' :
return { ...state, theme : action.theme };
case 'toggleComplete' : {
const taskIndex = state.tasks .findIndex ((t ) => t.id === action.taskId );
if (taskIndex > -1 ) {
const task = state.tasks [taskIndex];
return {
...state,
tasks : state.tasks
.slice (0 , taskIndex)
.concat ({ ...task, completed : !task.completed })
.concat (state.tasks .slice (taskIndex + 1 )),
};
}
return state;
}
default :
return state;
}
}
type TodoAppContextType = [State , React .Dispatch <Action >];
const TodoAppContext = createContext<TodoAppContextType >([
initialState,
() => {},
]);
function useAppContext ( ) {
const context = useContext (TodoAppContext );
if (!context) {
throw new Error ('useAppContext must be used within a TodoAppProvider' );
}
return context;
}
function AppContextProvider ({ children }: { children: React.ReactNode } ) {
const [state, dispatch] = useReducer (reducer, initialState);
return (
<TodoAppContext.Provider value ={[state, dispatch ]}>
{children}
</TodoAppContext.Provider >
);
}
export { AppContextProvider , useAppContext };
import { createContext, useContext, useReducer } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
const initialState = {
theme : 'dawn' ,
tasks : initialTasks,
inputValue : '' ,
};
type Task = { id : number ; text : string ; completed : boolean };
type State = {
theme : string ;
tasks : Task [];
inputValue : string ;
};
type Action =
| { type : 'addTask' ; text : string }
| { type : 'toggleComplete' ; taskId : number }
| { type : 'changeTheme' ; theme : string }
| { type : 'editNewTask' ; text : string };
let nextTaskId = initialTasks.length + 1 ;
function reducer (state: State, action: Action ) {
switch (action.type ) {
case 'addTask' :
return {
...state,
tasks : state.tasks .concat ({
id : nextTaskId++,
text : action.text ,
completed : false ,
}),
};
case 'editNewTask' :
return { ...state, inputValue : action.text };
case 'changeTheme' :
return { ...state, theme : action.theme };
case 'toggleComplete' : {
const taskIndex = state.tasks .findIndex ((t ) => t.id === action.taskId );
if (taskIndex > -1 ) {
const task = state.tasks [taskIndex];
return {
...state,
tasks : state.tasks
.slice (0 , taskIndex)
.concat ({ ...task, completed : !task.completed })
.concat (state.tasks .slice (taskIndex + 1 )),
};
}
return state;
}
default :
return state;
}
}
type TodoAppContextType = [State , React .Dispatch <Action >];
const TodoAppContext = createContext<TodoAppContextType >([
initialState,
() => {},
]);
function useAppContext ( ) {
const context = useContext (TodoAppContext );
if (!context) {
throw new Error ('useAppContext must be used within a TodoAppProvider' );
}
return context;
}
function AppContextProvider ({ children }: { children: React.ReactNode } ) {
const [state, dispatch] = useReducer (reducer, initialState);
return (
<TodoAppContext.Provider value ={[state, dispatch ]}>
{children}
</TodoAppContext.Provider >
);
}
export { AppContextProvider , useAppContext };
tasks-list.tsx
import { useAppContext } from './app-context' ;
import { RenderCounter } from '../components/render-counter' ;
export function TasksList ( ) {
const [state, dispatch] = useAppContext ();
const { tasks } = state;
function onToggleTask (taskId: number ) {
dispatch ({ type : 'toggleComplete' , taskId });
}
return (
<RenderCounter title ="TasksList" color ="#add8e6" >
<ul className ="task-list" >
{tasks.map((task) => (
<li
key ={task.id}
className ={
'task-item ' + (task.completed ? ' task-item--completed ' : '')
}
>
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</li >
))}
</ul >
</RenderCounter >
);
}
add-task-form.tsx
import { useAppContext } from './app-context' ;
import { RenderCounter } from '../components/render-counter' ;
export function AddTaskForm ( ) {
const [state, dispatch] = useAppContext ();
const { inputValue } = state;
function onAddTask (ev: React.FormEvent<HTMLFormElement> ) {
ev.preventDefault ();
dispatch ({ type : 'addTask' , text : inputValue });
dispatch ({ type : 'editNewTask' , text : '' });
}
function onInputChange (ev: React.ChangeEvent<HTMLInputElement> ) {
dispatch ({ type : 'editNewTask' , text : ev.target .value });
}
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
import { RenderCounter } from '../components/render-counter' ;
import { AddTaskForm } from './add-task-form' ;
import { AppContextProvider } from './app-context' ;
import { AppLayout } from './app-layout' ;
import { TasksList } from './tasks-list' ;
function TodoApp ( ) {
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<AppContextProvider >
<AppLayout title ="Todo App" >
<AddTaskForm />
<TasksList />
</AppLayout >
</AppContextProvider >
</RenderCounter >
);
}
export default TodoApp ;
import { RenderCounter } from '../components/render-counter' ;
import { AddTaskForm } from './add-task-form' ;
import { AppLayout } from './app-layout' ;
import { InputProvider } from './input-context' ;
import { TasksProvider } from './tasks-context' ;
import { TasksList } from './tasks-list' ;
import { ThemeProvider } from './theme-context' ;
function TodoApp ( ) {
return (
<RenderCounter title ="App" color ="#ffc0cb" >
<ThemeProvider >
<TasksProvider >
<InputProvider >
<AppLayout title ="Todo App" >
<AddTaskForm />
<TasksList />
</AppLayout >
</InputProvider >
</TasksProvider >
</ThemeProvider >
</RenderCounter >
);
}
export default TodoApp ;
theme-context.tsx
import { Dispatch , createContext, useState, useContext } from 'react' ;
const initialTheme = 'dawn' ;
type ThemeContextType = {
theme : string ;
setTheme : Dispatch <React .SetStateAction <string >>;
};
const ThemeContext = createContext<ThemeContextType >({
theme : initialTheme,
setTheme : () => {},
});
function ThemeProvider ({ children }: { children: React.ReactNode } ) {
const [theme, setTheme] = useState (initialTheme);
return (
<ThemeContext.Provider value ={{ theme , setTheme }}>
{children}
</ThemeContext.Provider >
);
}
function useTheme ( ) {
const context = useContext (ThemeContext );
if (!context) {
throw new Error ('useTheme must be used within a ColorProvider' );
}
return context;
}
export { ThemeProvider , useTheme };
import { Dispatch , createContext, useState, useContext } from 'react' ;
const initialTheme = 'dawn' ;
type ThemeContextType = {
theme : string ;
setTheme : Dispatch <React .SetStateAction <string >>;
};
const ThemeContext = createContext<ThemeContextType >({
theme : initialTheme,
setTheme : () => {},
});
function ThemeProvider ({ children }: { children: React.ReactNode } ) {
const [theme, setTheme] = useState (initialTheme);
return (
<ThemeContext.Provider value ={{ theme , setTheme }}>
{children}
</ThemeContext.Provider >
);
}
function useTheme ( ) {
const context = useContext (ThemeContext );
if (!context) {
throw new Error ('useTheme must be used within a ColorProvider' );
}
return context;
}
export { ThemeProvider , useTheme };
tasks-context.tsx
import { createContext, Dispatch , useReducer, useContext } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
type Task = { id : number ; text : string ; completed : boolean };
type Action =
| { type : 'addTask' ; text : string }
| { type : 'toggleComplete' ; taskId : number };
let nextTaskId = initialTasks.length + 1 ;
function reducer (state: Task[], action: Action ) {
switch (action.type ) {
case 'addTask' :
return state.concat ({
id : nextTaskId++,
text : action.text ,
completed : false ,
});
case 'toggleComplete' : {
const taskIndex = state.findIndex ((t ) => t.id === action.taskId );
if (taskIndex > -1 ) {
const task = state[taskIndex];
return state
.slice (0 , taskIndex)
.concat ({ ...task, completed : !task.completed })
.concat (state.slice (taskIndex + 1 ));
}
return state;
}
default :
return state;
}
}
const TasksContext = createContext<Task []>(initialTasks);
const TasksDispatch = createContext<Dispatch <Action >>(() => {});
function TasksProvider ({ children }: { children: React.ReactNode } ) {
const [state, dispatch] = useReducer (reducer, initialTasks);
return (
<TasksContext.Provider value ={state} >
<TasksDispatch.Provider value ={dispatch} >
{children}
</TasksDispatch.Provider >
</TasksContext.Provider >
);
}
function useTasks ( ) {
const context = useContext (TasksContext );
if (!context) {
throw new Error ('useTasks must be used within a TasksProvider' );
}
return context;
}
function useTasksDispatch ( ) {
const context = useContext (TasksDispatch );
if (!context) {
throw new Error ('useTasksDispatch must be used within a TasksProvider' );
}
return context;
}
export { TasksProvider , useTasks, useTasksDispatch };
import { createContext, Dispatch , useReducer, useContext } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
type Task = { id : number ; text : string ; completed : boolean };
type Action =
| { type : 'addTask' ; text : string }
| { type : 'toggleComplete' ; taskId : number };
let nextTaskId = initialTasks.length + 1 ;
function reducer (state: Task[], action: Action ) {
switch (action.type ) {
case 'addTask' :
return state.concat ({
id : nextTaskId++,
text : action.text ,
completed : false ,
});
case 'toggleComplete' : {
const taskIndex = state.findIndex ((t ) => t.id === action.taskId );
if (taskIndex > -1 ) {
const task = state[taskIndex];
return state
.slice (0 , taskIndex)
.concat ({ ...task, completed : !task.completed })
.concat (state.slice (taskIndex + 1 ));
}
return state;
}
default :
return state;
}
}
const TasksContext = createContext<Task []>(initialTasks);
const TasksDispatch = createContext<Dispatch <Action >>(() => {});
function TasksProvider ({ children }: { children: React.ReactNode } ) {
const [state, dispatch] = useReducer (reducer, initialTasks);
return (
<TasksContext.Provider value ={state} >
<TasksDispatch.Provider value ={dispatch} >
{children}
</TasksDispatch.Provider >
</TasksContext.Provider >
);
}
function useTasks ( ) {
const context = useContext (TasksContext );
if (!context) {
throw new Error ('useTasks must be used within a TasksProvider' );
}
return context;
}
function useTasksDispatch ( ) {
const context = useContext (TasksDispatch );
if (!context) {
throw new Error ('useTasksDispatch must be used within a TasksProvider' );
}
return context;
}
export { TasksProvider , useTasks, useTasksDispatch };
import { createContext, Dispatch , useReducer, useContext } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
type Task = { id : number ; text : string ; completed : boolean };
type Action =
| { type : 'addTask' ; text : string }
| { type : 'toggleComplete' ; taskId : number };
let nextTaskId = initialTasks.length + 1 ;
function reducer (state: Task[], action: Action ) {
switch (action.type ) {
case 'addTask' :
return state.concat ({
id : nextTaskId++,
text : action.text ,
completed : false ,
});
case 'toggleComplete' : {
const taskIndex = state.findIndex ((t ) => t.id === action.taskId );
if (taskIndex > -1 ) {
const task = state[taskIndex];
return state
.slice (0 , taskIndex)
.concat ({ ...task, completed : !task.completed })
.concat (state.slice (taskIndex + 1 ));
}
return state;
}
default :
return state;
}
}
const TasksContext = createContext<Task []>(initialTasks);
const TasksDispatch = createContext<Dispatch <Action >>(() => {});
function TasksProvider ({ children }: { children: React.ReactNode } ) {
const [state, dispatch] = useReducer (reducer, initialTasks);
return (
<TasksContext.Provider value ={state} >
<TasksDispatch.Provider value ={dispatch} >
{children}
</TasksDispatch.Provider >
</TasksContext.Provider >
);
}
function useTasks ( ) {
const context = useContext (TasksContext );
if (!context) {
throw new Error ('useTasks must be used within a TasksProvider' );
}
return context;
}
function useTasksDispatch ( ) {
const context = useContext (TasksDispatch );
if (!context) {
throw new Error ('useTasksDispatch must be used within a TasksProvider' );
}
return context;
}
export { TasksProvider , useTasks, useTasksDispatch };
import { createContext, Dispatch , useReducer, useContext } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
type Task = { id : number ; text : string ; completed : boolean };
type Action =
| { type : 'addTask' ; text : string }
| { type : 'toggleComplete' ; taskId : number };
let nextTaskId = initialTasks.length + 1 ;
function reducer (state: Task[], action: Action ) {
switch (action.type ) {
case 'addTask' :
return state.concat ({
id : nextTaskId++,
text : action.text ,
completed : false ,
});
case 'toggleComplete' : {
const taskIndex = state.findIndex ((t ) => t.id === action.taskId );
if (taskIndex > -1 ) {
const task = state[taskIndex];
return state
.slice (0 , taskIndex)
.concat ({ ...task, completed : !task.completed })
.concat (state.slice (taskIndex + 1 ));
}
return state;
}
default :
return state;
}
}
const TasksContext = createContext<Task []>(initialTasks);
const TasksDispatch = createContext<Dispatch <Action >>(() => {});
function TasksProvider ({ children }: { children: React.ReactNode } ) {
const [state, dispatch] = useReducer (reducer, initialTasks);
return (
<TasksContext.Provider value ={state} >
<TasksDispatch.Provider value ={dispatch} >
{children}
</TasksDispatch.Provider >
</TasksContext.Provider >
);
}
function useTasks ( ) {
const context = useContext (TasksContext );
if (!context) {
throw new Error ('useTasks must be used within a TasksProvider' );
}
return context;
}
function useTasksDispatch ( ) {
const context = useContext (TasksDispatch );
if (!context) {
throw new Error ('useTasksDispatch must be used within a TasksProvider' );
}
return context;
}
export { TasksProvider , useTasks, useTasksDispatch };
import { createContext, Dispatch , useReducer, useContext } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
type Task = { id : number ; text : string ; completed : boolean };
type Action =
| { type : 'addTask' ; text : string }
| { type : 'toggleComplete' ; taskId : number };
let nextTaskId = initialTasks.length + 1 ;
function reducer (state: Task[], action: Action ) {
switch (action.type ) {
case 'addTask' :
return state.concat ({
id : nextTaskId++,
text : action.text ,
completed : false ,
});
case 'toggleComplete' : {
const taskIndex = state.findIndex ((t ) => t.id === action.taskId );
if (taskIndex > -1 ) {
const task = state[taskIndex];
return state
.slice (0 , taskIndex)
.concat ({ ...task, completed : !task.completed })
.concat (state.slice (taskIndex + 1 ));
}
return state;
}
default :
return state;
}
}
const TasksContext = createContext<Task []>(initialTasks);
const TasksDispatch = createContext<Dispatch <Action >>(() => {});
function TasksProvider ({ children }: { children: React.ReactNode } ) {
const [state, dispatch] = useReducer (reducer, initialTasks);
return (
<TasksContext.Provider value ={state} >
<TasksDispatch.Provider value ={dispatch} >
{children}
</TasksDispatch.Provider >
</TasksContext.Provider >
);
}
function useTasks ( ) {
const context = useContext (TasksContext );
if (!context) {
throw new Error ('useTasks must be used within a TasksProvider' );
}
return context;
}
function useTasksDispatch ( ) {
const context = useContext (TasksDispatch );
if (!context) {
throw new Error ('useTasksDispatch must be used within a TasksProvider' );
}
return context;
}
export { TasksProvider , useTasks, useTasksDispatch };
add-task-form.tsx
import { useInput } from './input-context' ;
import { useTasksDispatch } from './tasks-context' ;
import { RenderCounter } from '../components/render-counter' ;
export function AddTaskForm ( ) {
const { text : inputValue, setText } = useInput ();
const dispatch = useTasksDispatch ();
function onAddTask (ev: React.FormEvent<HTMLFormElement> ) {
ev.preventDefault ();
dispatch ({ type : 'addTask' , text : inputValue });
setText ('' );
}
function onInputChange (ev: React.ChangeEvent<HTMLInputElement> ) {
setText (ev.target .value );
}
return (
<RenderCounter title ="AddTaskForm" color ="#dff2bf" >
<form className ="task-form" onSubmit ={onAddTask} >
<label className ="add-task-label" >
<span className ="visually-hidden" > Task</span >
<input
name ="task"
type ="text"
placeholder ="Add a task..."
className ="add-task-input"
value ={inputValue}
onChange ={onInputChange}
required
/>
</label >
<input className ="add-task-button" type ="submit" value ="Add" />
</form >
</RenderCounter >
);
}
Memoization La memoizzazione è una tecnica di programmazione che consiste nel salvare in memoria i valori restituiti da una funzione in modo da averli a disposizione per un riutilizzo successivo senza doverli ricalcolare. Wikipedia React utilizza la memoizzazione per memorizzare il risultato dei funzioni che rappresentano dei componenti, evitando così di doverli ri-renderizzare. Memoization in React.js memo - High Order Component che permette di evitare il re-render quando le props di un componente sono invariate.useMemo - React Hook che permette di memorizzare il risultato di una funzione tra i re-render.useCallback - React Hook che permette di memorizzare una funzione tra i re-render.Memoizing a function useMemo e useCallback sono molto simili, la differenza principale è che useMemo memorizza il risultato di una funzione, mentre useCallback memorizza la funzione stessa. Si potrebbe utilizzare useMemo per memorizzare una funzione, ma useCallback è più chiaro e leggibile. useMemo e useCallback sono equivalenti. AdsBanner
export const MemoAdsBanner = memo (AdsBanner )
AddTaskForm
export const MemoAddTaskForm = memo (AddTaskForm )
TaskItem
import { memo } from 'react' ;
import { RenderCounter } from '../components/render-counter' ;
interface Task {
id : number ;
text : string ;
completed : boolean ;
}
interface TaskItemProps {
task : Task ;
onToggleTask : (taskId: number ) => void ;
}
export const TaskItem = memo (function TaskItem ({
task,
onToggleTask,
}: TaskItemProps ) {
return (
<li
className ={ 'task-item ' + (task.completed ? ' task-item--completed ' : '')}
>
<RenderCounter title ="TaskItem" color ="#cba6e9" >
<label >
<input
name ="done"
type ="checkbox"
checked ={task.completed}
onChange ={() => onToggleTask(task.id)}
/>
{task.text}
</label >
</RenderCounter >
</li >
);
});
TasksView
import { useCallback, useState } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
import { AddTaskForm } from './add-task-form' ;
import { RenderCounter } from '../components/render-counter' ;
import { TaskItem } from './task-item' ;
let nextTaskId = initialTasks.length + 1 ;
export function TasksView ( ) {
const [tasks, setTasks] = useState (initialTasks);
const onAddTask = useCallback ((newTask: string ) => {
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : newTask, completed : false },
]);
}, []);
const onToggleTask = useCallback ((taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
}, []);
return (
<RenderCounter title ="TasksView" color ="#add8e6" >
<AddTaskForm onSubmit ={onAddTask} />
<ul className ="task-list" >
{tasks.map((task) => (
<TaskItem key ={task.id} task ={task} onToggleTask ={onToggleTask} />
))}
</ul >
</RenderCounter >
);
}
import { useCallback, useState } from 'react' ;
import initialTasks from '../data/initial-tasks' ;
import { AddTaskForm } from './add-task-form' ;
import { RenderCounter } from '../components/render-counter' ;
import { TaskItem } from './task-item' ;
let nextTaskId = initialTasks.length + 1 ;
export function TasksView ( ) {
const [tasks, setTasks] = useState (initialTasks);
const onAddTask = useCallback ((newTask: string ) => {
setTasks ((prevValues ) => [
...prevValues,
{ id : nextTaskId++, text : newTask, completed : false },
]);
}, []);
const onToggleTask = useCallback ((taskId: number ) => {
setTasks ((prevValues ) =>
prevValues.map ((currentTask ) =>
currentTask.id === taskId
? { ...currentTask, completed : !currentTask.completed }
: currentTask,
),
);
}, []);
return (
<RenderCounter title ="TasksView" color ="#add8e6" >
<AddTaskForm onSubmit ={onAddTask} />
<ul className ="task-list" >
{tasks.map((task) => (
<TaskItem key ={task.id} task ={task} onToggleTask ={onToggleTask} />
))}
</ul >
</RenderCounter >
);
}
Consigli finali React applica uno shallow comparison per verificare se le dipendenze sono cambiate. La memoizzazione è utile anche per rendere stabili i riferimenti di valori degli array di dipendenze degli hooks (anche useEffect ). Si può modificare la funzione di confronto per memo passando un secondo argomento. Se si passano oggetti, array o funzioni generati durante il rendering, la memoizzazione non funzionerà. Perché non usare sempre queste funzioni? La memoizzazione ha un costo in termini di memoria. Usare memo inutilmente aumenta il consumo di memoria creando più lavoro per il garbage collector, potenzialmente degradando le prestazioni.
La memoizzazione è utile quando si ha a che fare con componenti che richiedono molto tempo per essere renderizzati oppure quando le props di un componente non cambiano spesso a fronte di un alto numero di renderizzazioni. React Compiler È uno strumento a build time che ottimizza le app React applicando automaticamente le funzioni di memoizzazione .
Requisiti React 17+ (React 19 consigliato) Rules of React Components and Hooks must be pure React calls Components and Hooks Rules of Hooks Components and Hooks must be pureComponents must be idempotent Side effects must run outside of render Props and state are immutable Return values and arguments to Hooks are immutable Values are immutable after being passed to JSX React calls Components and HooksNever call component functions directly Never pass around hooks as regular values Rules of HooksOnly call Hooks at the top level Only call Hooks from React functions Install npm install -D babel-plugin-react-compiler@beta
eslint-plugin-react-compiler@betaInstallare il plugin Eslint è consigliato fin da subito per rilevare eventuali problemi nella propria codebase. React Compiler salta la compilazione dei componenti che non rispettano le regole di React senza bloccare la compilazione dell'intera app. React Compiler Playground
function Counter ( ) {
const [value, setValue] = useState (0 );
return (
<div >
<h1 > {value}</h1 >
<button
onClick ={() => setValue(v => v + 1)}
>+1</button >
</div >
);
}
function Counter ( ) {
const $ = _c (5 );
const [value, setValue] = useState (0 );
let t0;
if ($[0 ] !== value) {
t0 = <h1 > {value}</h1 > ;
$[0 ] = value;
$[1 ] = t0;
} else {
t0 = $[1 ];
}
let t1;
if ($[2 ] === Symbol .for ("react.memo_cache_sentinel" )) {
t1 = <button onClick ={() => setValue(_temp)}>+1</button > ;
$[2 ] = t1;
} else {
t1 = $[2 ];
}
let t2;
if ($[3 ] !== t0) {
t2 = (
<div >
{t0}
{t1}
</div >
);
$[3 ] = t0;
$[4 ] = t2;
} else {
t2 = $[4 ];
}
return t2;
}
function _temp (v ) {
return v + 1 ;
}
Non è necessario rimuovere i precedenti usi di memo, useMemo, useCallback perché React Compiler li interpreta correttamente tentando lo stesso di applicare la memoizzazione. Domande aperte Correttezza – il compilatore compila correttamente l'applicazione e ne preserva il comportamento originale?Prestazioni – il compilatore aumenta il livello di prestazioni per tutti i tipi di app React? Come si bilancia l'aumento delle dimensioni del bundle con una migliore prestazione di rendering?Developer Experience – il compilatore ti aiuta a scrivere meglio in React? È facile fare debug quando qualcosa va storto?Correttezza – il compilatore compila correttamente l'applicazione e ne preserva il comportamento originale? L'app compilata è priva di loop infiniti inaspettati e/o di esecuzioni eccessive di useEffect?Prestazioni – il compilatore aumenta il livello di prestazioni per tutti i tipi di app React, da quelle completamente statiche a quelle completamente dinamiche? Come si bilancia l'aumento delle dimensioni del bundle con una migliore prestazione di rendering?Developer Experience – il compilatore ti aiuta a scrivere meglio in React? I messaggi di errore/avvisi sono comprensibili? È facile fare debug quando qualcosa va storto? Permette di rimuovere manualmente useMemo e useCallback? React diventa più semplice da usare e capire con il compilatore abilitato?Strategie per ottimizzare il re-render Components composition Uncontrolled forms Split large context Memoization React Compiler (?) Mirco Bellagamba Software Engineer @ Madisoft
Ottimizzare le prestazioni delle React app Strategie per evitare re-render inutili Mirco Bellagamba