React: تحسين الأداء: useMemo و useCallback
آخر تحديث: 2026-08-26
1. ما ستتعلمه
- تقوم دالة
useMemoبتخزين نتيجة عملية الحساب مؤقتًا - تقوم
useCallbackبتخزين مراجع الدوال مؤقتًا - تمنع ميزة React.memo إعادة العرض غير الضرورية
- متى نستخدمه، ومتى نسيء استخدامه
- مناهج تحليل الأداء
2. قصة صفحة تصفية البحث
العلاقة بين المكونات الثلاثة لتحسين الأداء
flowchart TD
A[Parent Component Rendering] --> B{Props It's changed?}
B -->|Yes| C[React.memo Intercept?]
B -->|No| D[Skip subcomponents ✅]
C -->|A rough comparison is roughly equal| D
C -->|Shallow Comparison Inequality| E{Function/The object is new.?}
E -->|Yes| F[useCallback/useMemo]
F --> G[Stable Citation → memo Effective ✅]
E -->|No| D
style F fill:#e8f5e9,stroke:#2e7d32
style D fill:#c8e6c9,stroke:#2e7d32
style G fill:#c8e6c9,stroke:#2e7d32
(1) المشكلة: إعادة الحساب مع كل إدخال
أنشأت أليس صفحة بحث عن المنتجات تضم 10,000 منتج يمكن تصفية النتائج منها:
function ShopPage() {
const [products] = useState(generate10000Products())
const [search, setSearch] = useState('')
const [sortBy, setSortBy] = useState('name')
// ❌ Question:Re-filter after each input 10,000 Items
const filtered = products
.filter(p => p.name.includes(search))
.sort((a, b) => a[sortBy] > b[sortBy] ? 1 : -1)
return (
<div>
<input value={search} onChange={e => setSearch(e.target.value)} />
<select value={sortBy} onChange={e => setSortBy(e.target.value)}>
<option value="name">By Name</option>
<option value="price">By Price</option>
</select>
<ProductList products={filtered} />
</div>
)
}
المشكلة: في كل مرة يتم فيها إدخال قيمة من لوحة المفاتيح (setSearch)، يتسبب ذلك في تشغيل filter وsort مرة أخرى — ويتعين تصفية 10,000 سجل في كل مرة، مما يؤدي إلى بطء أداء الواجهة.
(2) حل useMemo
function ShopPage() {
const [products] = useState(generate10000Products())
const [search, setSearch] = useState('')
const [sortBy, setSortBy] = useState('name')
// ✅ useMemo:Only at search or sortBy Recalculate only when there is a change
const filtered = useMemo(() => {
console.log('Refilter...(Only when the search terms or sorting criteria change)')
return products
.filter(p => p.name.includes(search))
.sort((a, b) => a[sortBy] > b[sortBy] ? 1 : -1)
}, [search, sortBy, products])
return (
<div>
<input value={search} onChange={e => setSearch(e.target.value)} />
<select value={sortBy} onChange={e => setSortBy(e.target.value)}>
<option value="name">By Name</option>
<option value="price">By Price</option>
</select>
<ProductList products={filtered} />
</div>
)
}
المزايا: لا يتم إعادة تشغيل عملية التصفية إلا عندما يتغير المدخل إلى search أو sortBy؛ أما التغييرات الأخرى في الحالة (مثل إعادة عرض المكون الأصلي) فلا تؤدي إلى تشغيل عملية التصفية، مما يؤدي إلى تحسن في الأداء بمقدار 10 إلى 100 ضعف.
3. useMemo: تخزين نتائج الحسابات مؤقتًا
| هوك | ما يجب تذكره | قيمة الإرجاع | عند تغير التبعيات |
|---|---|---|---|
useMemo |
نتيجة الحساب | القيمة المخزنة مؤقتًا | إعادة الحساب |
useCallback |
مراجع الوظائف | الوظائف المخزنة مؤقتًا | إنشاء وظائف جديدة |
React.memo |
نتيجة عرض المكون بأكمله | تخطي إعادة العرض | إعادة العرض عند تغيير الخصائص |
(1) قواعد النحو الأساسية
const memoizedValue = useMemo(() => {
return Costly Computations()
}, [Dependency1, Dependency2])
| القسم | الوصف |
|---|---|
() => Computation |
دالة يجب تخزينها مؤقتًا وتُرجع نتيجة الحساب |
[Dependencies] |
إعادة الحساب عند تغير التبعيات |
memoizedValue |
النتائج المخزنة مؤقتًا (تُرجع النتيجة السابقة مباشرةً إذا لم تتغير التبعيات) |
(2) السيناريوهات التي ينطبق عليها ذلك
// Scene 1:Data Conversion(Filter、Sort、Grouping)
const activeUsers = useMemo(
() => users.filter(u => u.isActive).sort((a, b) => a.name.localeCompare(b.name)),
[users]
)
// Scene 2:Format(Numbers、Date、String Concatenation)
const formattedPrice = useMemo(
() => new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(price),
[price]
)
// Scene 3:Derived State(Calculating New Data from Existing Data)
const statistics = useMemo(() => ({
total: orders.length,
paid: orders.filter(o => o.status === 'paid').length,
amount: orders.reduce((sum, o) => sum + o.amount, 0),
avgAmount: orders.length ? orders.reduce((sum, o) => sum + o.amount, 0) / orders.length : 0
}), [orders])
▶ مثال: مقارنة الأداء
// ============================================
// Example:useMemo vs no useMemo Performance Comparison
// ============================================
function ExpensiveCalc({ num, unrelated }) {
// ❌ None useMemo:Executed on every render
const slowResult1 = slowFibonacci(num)
// ✅ With useMemo: num If it hasn't changed, don't recalculate it.
const slowResult2 = useMemo(() => slowFibonacci(num), [num])
return (
<div>
<p>num={num} The Fibonacci sequence</p>
<p>Without useMemo: {slowResult1}(Rendering calculations each time)</p>
<p>With useMemo: {slowResult2}(Only at num Calculate when changes occur)</p>
<p>Unrelated Status:{unrelated}(Click the button to change this value)</p>
</div>
)
}
function slowFibonacci(n) {
if (n <= 1) return n
let a = 0, b = 1, c
for (let i = 2; i <= n; i++) { c = a + b; a = b; b = c }
console.log('fibonacci Calculation!n=' + n)
return b
}
// Click to Make a Difference unrelated → without useMemo recalculates,with useMemo skips
4. useCallback: حفظ مراجع الدوال
(1) قواعد النحو الأساسية
const memoizedFn = useCallback(() => {
doSomething(a, b)
}, [a, b])
useCallback(fn, deps) تعادل useMemo(() => fn, deps) — فما يتم تخزينه هو الدالة نفسها، وليس نتيجة الدالة.
(2) لماذا يُعد useCallback ضروريًا؟
function Parent() {
const [count, setCount] = useState(0)
// ❌ Every time Parent Rendering,handleClick These are all new functions
// resulting in Child 's React.memo Failure
const handleClick = () => {
console.log('Clicked')
}
// ✅ useCallback:handleClick Citation unchanged
// Child 's React.memo Works properly(Skip Re-rendering)
const handleClick = useCallback(() => {
console.log('Clicked')
}, []) // Empty Dependency:This function will never change.
}
(3) React.memo: منع إعادة عرض المكونات الفرعية
// ============================================
// Example:useCallback + React.memo For use in combination with
// ============================================
// ---- Child component:use React.memo Package ----
const ExpensiveList = React.memo(function ExpensiveList({ items, onItemClick }) {
console.log('ExpensiveList Render Again!')
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={() => onItemClick(item.id)}>Select</button>
</li>
))}
</ul>
)
})
// ---- Parent Component ----
function App() {
const [count, setCount] = useState(0)
const [items] = useState([
{ id: 1, name: 'Project A' },
{ id: 2, name: 'Project B' },
{ id: 3, name: 'Project C' }
])
// ❌ Every time App Rendering,All function references have changed → React.memo Failure
const handleClickBad = (id) => console.log('Select:', id)
// ✅ Function references are immutable → React.memo Effective
const handleClick = useCallback((id) => {
console.log('Select:', id)
}, [])
return (
<div>
<p>Counter:{count}</p>
<button onClick={() => setCount(c => c + 1)}>Change Count</button>
{/* Tap the button to change the count → No impact ExpensiveList Rendering of */}
<ExpensiveList items={items} onItemClick={handleClick} />
</div>
)
}
// When clicking "Change Count" :
// ❌ Without useCallback → ExpensiveList Render again
// ✅ has useCallback → ExpensiveList Skip rendering(Performance Improvements)
5. متى يجب استخدامه، ومتى لا يجب استخدامه
(1) متى يتم استخدامه
| السيناريو | العنصر الجاذب | السبب |
|---|---|---|
| تصفية/فرز البيانات الضخمة | useMemo |
تجنب البحث في كميات كبيرة من البيانات في كل عملية عرض |
| الحسابات المعقدة (الرياضيات/التنسيق) | useMemo |
لا داعي لإعادة الحساب إذا بقيت النتيجة كما هي |
| تمرير المعلمات إلى مكون فرعي يستخدم React.memo | useCallback |
الحفاظ على استقرار مراجع الدوال لضمان عمل React.memo |
| التبعيات في دالة useEffect | useCallback |
منع تنفيذ دالة useEffect عدة مرات بسبب التغييرات في مراجع الدالة |
(2) الحالات التي لا تكون فيها هناك حاجة إليها
// ❌ Not necessary:Simple Calculations
const doubled = useMemo(() => count * 2, [count])
// ✅ Write it directly:count * 2 It's already pretty fast as it is
const doubled = count * 2
// ❌ Not necessary:Handle events without passing them to child components
const handleClick = useCallback(() => {
console.log('Click')
}, [])
// ✅ Just write it down.
const handleClick = () => console.log('Click')
// ❌ Not necessary:The child component isn't being used. React.memo
// useCallback It's been packaged, but no one is buying it."Stable Citation"
(3) تكلفة الإساءة
// useMemo and useCallback It involves expenses(Relies heavily on arrays)
// Abuse = Introducing Additional Comparison Logic,Slower than if it weren't optimized
// Rule of thumb:
// 1. First, let's write the version without optimization.
// 2. Feeling some lag(fps < 60)Use again
// 3. use React DevTools Profiler Measurement
6. مثال كامل: قائمة الرسائل في تطبيق الدردشة
// ============================================
// Complete Example:Chat Message List(Comprehensive Performance Optimization)
// ============================================
import { useState, useMemo, useCallback, memo } from 'react'
// ---- News Item(React.memo + useCallback) ----
const MessageItem = memo(function MessageItem({ message, onReact, onReply }) {
console.log(`Rendering Message:${message.id}`)
return (
<div style={{
padding: '12px',
margin: '8px 0',
backgroundColor: message.isMine ? '#e6f7ff' : '#f5f5f5',
borderRadius: '8px'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<strong>{message.author}</strong>
<span style={{ color: '#999', fontSize: '12px' }}>
{message.time}
</span>
</div>
<p style={{ margin: '8px 0' }}>{message.text}</p>
<div style={{ display: 'flex', gap: '8px' }}>
<button onClick={() => onReact(message.id)} style={smallBtn}>
👍 {message.reactions}
</button>
<button onClick={() => onReply(message.id)} style={smallBtn}>
💬 Reply
</button>
</div>
</div>
)
})
// ---- Main Component ----
function ChatApp() {
const [messages, setMessages] = useState(generateMessages(100))
const [filter, setFilter] = useState('All')
const [unreadCount, setUnreadCount] = useState(5)
// useMemo:Filter Messages(100 Filter by row)
const filteredMessages = useMemo(
() => filter === 'All'
? messages
: messages.filter(m => filter === 'My' ? m.isMine : !m.isMine),
[messages, filter]
)
// useCallback:Keep References Consistent,let MessageItem 's memo Effective
const handleReact = useCallback((id) => {
setMessages(prev => prev.map(m =>
m.id === id ? { ...m, reactions: m.reactions + 1 } : m
))
}, [])
const handleReply = useCallback((id) => {
console.log('Reply to a message:', id)
}, [])
// Calculate Statistical Information
const stats = useMemo(() => ({
total: messages.length,
mine: messages.filter(m => m.isMine).length,
totalReactions: messages.reduce((s, m) => s + m.reactions, 0)
}), [messages])
return (
<div style={{ maxWidth: '600px', margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2>💬 Chat({unreadCount} Unread posts)</h2>
<button onClick={() => setUnreadCount(c => c + 1)}>
Increase the unread count(Test Performance)
</button>
</div>
{/* Statistics(useMemo) */}
<div style={{ display: 'flex', gap: '16px', marginBottom: '12px', fontSize: '13px', color: '#666' }}>
<span>Total {stats.total} items</span>
<span>My {stats.mine} items</span>
<span>👍 {stats.totalReactions}</span>
</div>
{/* Filter Button */}
<div style={{ marginBottom: '12px' }}>
{['All', 'My', 'Other people's'].map(f => (
<button
key={f}
onClick={() => setFilter(f)}
style={{
padding: '4px 12px', margin: '0 4px',
backgroundColor: filter === f ? '#1890ff' : '#f0f0f0',
color: filter === f ? 'white' : '#333',
border: 'none', borderRadius: '4px', cursor: 'pointer'
}}
>
{f}
</button>
))}
</div>
{/* News List */}
<div style={{ maxHeight: '500px', overflow: 'auto' }}>
{filteredMessages.map(msg => (
<MessageItem
key={msg.id}
message={msg}
onReact={handleReact} // ✅ Stable citation
onReply={handleReply} // ✅ Stable citation
/>
))}
</div>
</div>
)
}
function generateMessages(count) {
const names = ['Alice', 'Bob', 'Charlie', 'Diana']
return Array.from({ length: count }, (_, i) => ({
id: i + 1,
author: names[i % 4],
text: `This is the ${i + 1} The content of the message`,
time: `${String(10 + i % 8).padStart(2, '0')}:${String(i % 60).padStart(2, '0')}`,
isMine: i % 3 === 0,
reactions: Math.floor(Math.random() * 5)
}))
}
const smallBtn = {
padding: '2px 8px', fontSize: '12px',
border: '1px solid #d9d9d9', borderRadius: '4px',
backgroundColor: 'white', cursor: 'pointer'
}
الناتج المتوقع: قائمة محادثة تحتوي على 100 رسالة. عند النقر على «زيادة عدد الرسائل غير المقروءة»، لن يتم إعادة عرض قائمة الرسائل (لن يظهر أي ناتج في وحدة التحكم) بسبب
useCallbackوReact.memo؛ بل سيتغير العدد فقط.
▶ المثال 2: تمنع دالة useCallback دالة useEffect من التشغيل عدة مرات
// ============================================
// Example:useCallback In conjunction with useEffect Avoiding Infinite Loops
// Features:Display useCallback in useEffect The Key Role of Dependencies
// ============================================
import { useState, useEffect, useCallback } from 'react'
function SearchComponent() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [page, setPage] = useState(1)
// ❌ Question:Each render is a new function,As useEffect Dependencies can cause an infinite loop
// const fetchResults = async () => {
// const res = await fetch(`/api/search?q=${query}&page=${page}`)
// const data = await res.json()
// setResults(data)
// }
// ✅ Resolve:useCallback Stable Function References
const fetchResults = useCallback(async () => {
// Simulation API Request
const mockData = [
{ id: 1, title: `Results ${query} - Page ${page}` },
{ id: 2, title: `Related Articles About ${query}` },
{ id: 3, title: `${query} Best Practices` }
]
setResults(mockData)
}, [query, page]) // query or page Create a new function only when there is a change
// Dependency fetchResults(Stable Citation,It won't go into an infinite loop)
useEffect(() => {
if (query.trim()) {
fetchResults()
}
}, [fetchResults]) // fetchResults Citation unchanged → useEffect Will not be executed again
return (
<div style={{ maxWidth: '500px', margin: '0 auto' }}>
<h2>🔍 Search(useCallback Demo)</h2>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Enter a search term..."
style={{ width: '100%', padding: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }}
/>
<div style={{ marginTop: '8px', display: 'flex', gap: '8px', alignItems: 'center' }}>
<button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page <= 1}>Previous Page</button>
<span>Page {page} </span>
<button onClick={() => setPage(p => p + 1)}>Next Page</button>
</div>
<ul style={{ marginTop: '12px', listStyle: 'none', padding: 0 }}>
{results.map(r => (
<li key={r.id} style={{ padding: '8px', borderBottom: '1px solid #f0f0f0' }}>
{r.title}
</li>
))}
</ul>
{!query && <p style={{ color: '#999', textAlign: 'center' }}>Enter a keyword to start your search</p>}
</div>
)
}
▶ المثال 3: استخدام useMemo لتنفيذ حسابات القوائم الافتراضية
function VirtualList({ items, itemHeight = 40, visibleHeight = 400 }) {
const [scrollTop, setScrollTop] = useState(0)
const visibleCount = Math.ceil(visibleHeight / itemHeight)
const totalHeight = useMemo(() => items.length * itemHeight, [items.length, itemHeight])
const { startIndex, endIndex, offsetY } = useMemo(() => {
const start = Math.floor(scrollTop / itemHeight)
const end = Math.min(start + visibleCount + 1, items.length)
return { startIndex: start, endIndex: end, offsetY: start * itemHeight }
}, [scrollTop, itemHeight, visibleCount, items.length])
const visibleItems = useMemo(() => items.slice(startIndex, endIndex), [items, startIndex, endIndex])
return (
<div onScroll={e => setScrollTop(e.currentTarget.scrollTop)}
style={{ height: visibleHeight, overflowY: 'auto', border: '1px solid #ddd', borderRadius: 4 }}>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ position: 'absolute', top: offsetY, width: '100%' }}>
{visibleItems.map((item, i) => (
<div key={startIndex + i} style={{ height: itemHeight, padding: '0 12px', display: 'flex', alignItems: 'center', borderBottom: '1px solid #f0f0f0' }}>
Row {startIndex + i}: {item}
</div>
))}
</div>
</div>
</div>
)
}
▶ المثال 4: تحسين أداء القوائم باستخدام React.memo + useCallback
const ListItem = React.memo(function ListItem({ name, price, onRemove }) {
console.log(`Rendering: ${name}`)
return (
<div style={{ display: 'flex', justifyContent: 'space-between', padding: 8, borderBottom: '1px solid #f0f0f0' }}>
<span>{name} - ${price}</span>
<button onClick={() => onRemove(name)} style={{ color: '#ff4d4f', border: 'none', cursor: 'pointer' }}>Remove</button>
</div>
)
})
function ProductList() {
const [products, setProducts] = useState([
{ name: 'Keyboard', price: 79 },
{ name: 'Mouse', price: 49 },
{ name: 'Monitor', price: 399 },
{ name: 'Headphones', price: 129 },
])
const [filter, setFilter] = useState('')
const handleRemove = useCallback((name) => {
setProducts(prev => prev.filter(p => p.name !== name))
}, [])
const filtered = useMemo(() => products.filter(p => p.name.toLowerCase().includes(filter.toLowerCase())), [products, filter])
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<input value={filter} onChange={e => setFilter(e.target.value)} placeholder="Filter..." style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
{filtered.map(p => <ListItem key={p.name} name={p.name} price={p.price} onRemove={handleRemove} />)}
</div>
)
}
▶ المثال 5: متى لا ينبغي استخدام useMemo — مقارنة مع الأنماط السيئة
function BadExample({ count }) {
// ❌ Simple calculations are not required useMemo——Create memo Expenses > The Calculation Itself
const doubled = useMemo(() => count * 2, [count])
// ✅ Direct Calculation
const doubledOk = count * 2
// ❌ Each render creates a new object as useMemo Dependency
const config = useMemo(() => ({ count }), [count])
const badResult = useMemo(() => config.count * 3, [config]) // config A new reference each time!
// ✅ Using Primitive Types Directly as Dependencies
const goodResult = useMemo(() => count * 3, [count])
return <p>Doubled: {doubledOk}, Tripled: {goodResult}</p>
}
function GoodExample({ items, query }) {
// ✅ Filter/Sorting large amounts of data useMemo
const filtered = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
).sort((a, b) => a.name.localeCompare(b.name))
}, [items, query])
// ✅ Complex objects passed to child components use useMemo
const chartData = useMemo(() => ({
labels: filtered.map(i => i.name),
values: filtered.map(i => i.price),
}), [filtered])
return (
<div>
<p>Found: {filtered.length} items</p>
<Chart data={chartData} />
</div>
)
}
❓ أسئلة شائعة
useMemo وuseCallback دائمًا على تحسين الأداء؟dependencies في كل عملية عرض. وقد يؤدي الإفراط في استخدامهما إلى إبطاء الأداء فعليًّا. التوصية الرسمية لـ React: ابدأ بكتابة النسخة غير المُحسَّنة، ولا تقم بالتحسين إلا عندما تلاحظ حدوث تأخير. القاعدة العامة: استخدمهما فقط للحسابات التي يتم تمريرها إلى مكون فرعي من نوع React.memo أو تلك التي تتطلب وقتًا إضافيًا يزيد عن 1 مللي ثانية.useMemo والمتغير العادي؟useMemo «ذاكرة» — فإذا لم تتغير التبعيات، فإنه يستخدم القيمة المخزنة في ذاكرة التخزين المؤقت مباشرةً دون إعادة تقييم التعبير. أما المتغير العادي فيُعاد تقييمه عند كل عملية عرض. ومع ذلك، يحتاج useMemo إلى مقارنة مصفوفة التبعيات أثناء العرض، لذا فإن «الحسابات البسيطة تكون في الواقع أبطأ مع useMemo.»useCallback للحفاظ على مرجع الدالة؟useEffect. إذا لم ينطبق أي من هذين الشرطين، فيمكنك ببساطة كتابة الدالة مباشرةً؛ ولا داعي لاستخدام useCallback.useMemo وuseCallback؟useMemo(() => value, deps) بتخزين نتيجة (قيمة) عملية حسابية في ذاكرة التخزين المؤقت، بينما يقوم useCallback(() => fn, deps) بتخزين مرجع الدالة في ذاكرة التخزين المؤقت. وفي الواقع، فإن useCallback(fn, deps) يعادل useMemo(() => fn, deps). استخدم useCallback عندما تحتاج إلى تخزين دالة تم تمريرها إلى مكون فرعي؛ واستخدم useMemo عندما تحتاج إلى تخزين نتيجة عملية حسابية.📖 ملخص
useMemoتخزين نتائج الحساب مؤقتًا؛ إذا بقيت التبعيات دون تغيير، فلا تُعاد الحساباتuseCallbackتخزين مراجع الدوال مؤقتًا لمنع إعادة عرض المكونات الفرعية بالتزامن مع React.memoReact.memoإجراء مقارنة سطحية بين قيم الخصائص (props)؛ إذا لم تتغير قيم الخصائص، يتم تخطي عرض المكون الفرعي- لا تقم بالتحسين في مرحلة مبكرة جدًا — اكتب أولاً كودًا يعمل بشكل جيد، ولا تقم بتحسينه إلا عندما يبدأ في التعطل.
- تحديد معوقات الأداء باستخدام أداة تحليل الأداء في React DevTools
📝 تمارين
- المشكلة الأساسية (الصعوبة ⭐): أنشئ مكونًا
PrimeCalculatorيتلقى رقمًا n كمدخل ويحسب العدد الأولي رقم n. استخدمuseMemoلتخزين النتيجة مؤقتًا حتى لا يتم إعادة حسابها في كل مرة يتم فيها إدخال قيمة جديدة. - تمرين متقدم (مستوى الصعوبة ⭐⭐): أنشئ مكونًا
ProductTableيحتوي على 500 سجل منتج ويدعم التصفية حسب الفئة والفرز حسب السعر. استخدمuseMemoلتحسين منطق التصفية والفرز، واستخدمuseCallbackمعReact.memoلتحسين عرض القائمة. - التحدي (الصعوبة: ⭐⭐⭐): قم بإنشاء مكون
DataDashboardيقوم بتجميع 10,000 سجل طلب عبر ثلاثة أبعاد مختلفة (الوقت، والمنطقة، والفئة)، واستخدمuseMemoلتنفيذ التخزين المؤقت الهرمي (بحيث يتم إعادة حساب الطبقات ذات الصلة فقط عند تغير الأبعاد).