זהו נושא דיון מלווה לערך המקורי שב־https://www.tocode.co.il/bundles/react/lessons/performance-lab
היי, האם הגעתי למינימום רנדרים?
לא הייתי בטוח אם יש דרך פשוטה לגרום לכך שלא יהיה רנדר לMyButton כשמשנים את הdelta.
זה יכול להיות אפשרי רק אם שומרים את delta בref במקום בstate. לא הצלחתי כל כך לממש את זה בגלל שצריך שיהיה רנדר כל פעם שמכניסים ערך בinput כדי שיהיה אפשר לראות את השינוי כל פעם בinput.
באופן כללי זה נראה לי גם די הגיוני שאם הdelta השתנתה אז אני רוצה שיהיה רנדר גם לכפתור בגלל שהפונקציה inc מתעדכנת בהתאם לערך החדש שהיא צריכה להוסיף בעת לחיצה על הכפתור.
נראה מעולה
MyButton חייב לעבור render כשמשנים את ה delta - כי זה אומר שמשהו בהתנהגות הכפתור השתנה. כאן בדיוק אתה רואה דוגמא ל render חיוני
שלום,
מצרפת את הפתרון, אשמח להערות, תודה!
import React, { useRef } from "react";
import ReactDOM, { render } from "react-dom";
import { useState, useMemo, useCallback } from "react";
import "../styles/style.scss";
const Header = React.memo(function Header(props) {
console.count("Header.render");
return <h1>My Counter Demo</h1>;
});
const DisplayValue = React.memo(function DisplayValue(props) {
console.count("DisplayValue.render");
const { val } = props;
return <p>Value: {val}</p>;
});
const DisplayMod5 = React.memo(function DisplayMod5(props) {
console.count("DisplayMod5.render");
const { val } = props;
const text =
val % 5 === 0 ? "Value is divisible by 5" : "Value does not divide by 5";
return <p>{text}</p>;
},(prevProps , nextProps)=>{
return (prevProps.val % 5 === 0) === (nextProps.val % 5 === 0);
});
const MyButton = React.memo(function MyButton(props) {
console.count("MyButton.render");
return <button onClick={props.onClick}>Click Me</button>;
});
function Counter() {
console.count("Counter.render");
const [nonce , setNonce] = useState(0);
const countRef = useRef(0); const count = countRef.current;
const deltaRef = useRef(1); const delta = deltaRef.current;
const inc = useCallback(function inc() {
countRef.current += delta;
render();
},[countRef]);
function render() {
setNonce(v=>v+1);
}
return (
<>
<Header />
<label>
Increase by:
<input type="number"
value={delta}
onChange={e => { deltaRef.current = Number(e.target.value); render();}} />
</label>
<DisplayValue val={count} />
<DisplayMod5 val={count} />
<MyButton onClick={inc} />
</>
);
}
ReactDOM.render(<Counter />, document.querySelector('main'));
למה בחרת להשתמש ב ref עבור הערכים של count ו delta ?
שתי שאלות
למה מביאים את useMemo אם הכתיבה היא React.memo
מבחינת משאבים - שימוש ב memo ו callback לא עולה במשאבים מהצד השני?
כלומר חוסכים ברנדר אבל מעמיסים על הזכרון?
הי,
אלה שני דברים שונים: ב React.Memo אנחנו משתמשים בעת הגדרת הקומפוננטה, אבל ב useMemo נשתמש כשיש לנו קומפוננטה שמייצרת קומפוננטה אחרת ואנחנו רוצים במקרה ספציפי לגרום לקומפוננטה הפנימית להיות Memoized
אני מדבר על ההבדל ביניהם יותר לעומק בוידאו כאן (יש גם טקסט עם דוגמאות):
https://www.tocode.co.il/past_workshops/86
לגבי נושא המשאבים - כן התוכנית תיקח יותר זיכרון אבל זה לא משהו שגורם לבעיות ביצועים. רנדרים מיותרים זה כן משהו שלריאקט מאוד קשה איתו ויכול להשפיע על חווית המשתמש (ושוב רק במקרים קיצוניים, רוב הזמן אף אחד לא ישים לב אם יש קצת רנדרים מיותרים)
כדי שהכפתור ירונדר רק פעם אחת.
(אחר כך שמתי לב לטעות קטנה שהיתה לי בתוך הפונקציה inc ותיקנתי אצלי:
countRef.current += deltaRef.current;
)
מבחינת הגיון של ריאקט - זה לא הגיוני להשתמש שם ב ref. השדות האלה הם באופן מובהק State של הקומפוננטה (זאת המשמעות של סטייט)
אנחנו משתמשים ב ref או בשביל לשמור מידע סטטי (למשל חיבור לאיזשהו API חיצוני או מבנה נתונים חיצוני), או בשביל לשמור קישור לאלמנט ב DOM.
נסי לארגן מחדש את הקוד אבל להשאיר את count ו delta בתור State
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { useState, useMemo, useCallback, useRef } from "react";
import "./styles.css";
function Header(props) {
console.count("Header.render");
return <h1>My Counter Demo</h1>;
}
const DisplayValue = React.memo(function DisplayValue(props) {
console.count("DisplayValue.render");
const { val } = props;
return <p>Value: {val}</p>;
})
const DisplayMod5 = React.memo(function DisplayMod5(props) {
console.count("DisplayMod5.render");
const { val } = props;
const text =
val % 5 === 0 ? "Value is divisible by 5" : "Value does not divide by 5";
return <p>{text}</p>;
}, function isEqual(prevProps, nextProps) {return (prevProps.val % 5 === 0) === (nextProps.val % 5 === 0);})
const MyButton = React.memo(function MyButton(props) {
console.count("MyButton.render");
return <button onClick={props.onClick}>Click Me</button>;
})
function Counter() {
console.count("Counter.render");
const [count, setCount] = useState(0);
const [delta, setDelta] = useState(1);
const deltaRef = useRef(delta);
const inc = useCallback(() => {
setCount(val => val + deltaRef.current);
}, [deltaRef])
useEffect(() => {
deltaRef.current = delta;
}, [delta])
return (
<>
<Header />
<label>
Increase by:
<input
type="number"
value={delta}
onChange={e => setDelta(Number(e.target.value))}
/>
</label>
<DisplayValue val={count} />
<DisplayMod5 val={count} />
<MyButton onClick={inc} />
</>
);
הכנתי קוד שלא מבצע רנדור נוסף לכפתור ולא גורם לבעיות הנראות לעין. אשמח אם תוכל לעבור על זה ולראות האם זה תקין.
הי הכל נראה טוב רק לא הבנתי למה צריך את ה ref והאפקט:
useEffect(() => {
deltaRef.current = delta;
}, [delta])
אי אפשר היה לעבוד ישירות עם delta בתוך ה useCallback של inc ?
באופן כללי useEffect זה הפונקציה שצריך הכי להיזהר ממנה בריאקט. היא נועדה אך ורק בשביל לסנכרן בין משתנה בעולם של ריאקט ל״משהו״ מחוץ לריאקט (לדוגמה - לשמור את גודל החלון במשתנה סטייט בשביל לעשות משהו כשמשתמש משנה את גודל החלון).
אם הייתי משתמש ישירות עם delta בתוך ה useCallback של inc הייתי צריך להוסיף את delta לרשימת הdependencies ואז בכל פעם שdelta הייתה משתנה הפונקציה inc הייתה נוצרת מחדש.
הייתי צריך את ה ref והאפקט כדי שהפונקציה inc תישאר מעודכנת תמיד עם הערך העדכני של delta,
בכל פעם ש delta משתנה כך גם deltaRef.current אך ה reference של deltaRef נשאר ללא שינוי, לכן הפונקציה inc לא תיווצר מחדש.
“באופן כללי useEffect … נועדה אך ורק בשביל לסנכרן בין משתנה בעולם של ריאקט ל״משהו״ מחוץ לריאקט…”
אשמח לדעת איילו בעיות יכולות להופיע אם משתמשים בה כדי לגרום לעדכון ערכים פנימיים של הקומפוננטה כתגובה לשינוי כלשהו ב state.
תודה רבה