דוגמת ריפקטורינג של טפסים ב React
פוסט זה כולל טיפ קצר לעבודה עם React. אם אתם רוצים ללמוד איתי ריאקט מההתחלה ובצורה מקצועית תשמחו לשמוע שבניתי קורס מלא הכולל עשרות שיעורי וידאו והמון תרגול בו לומדים ריאקט מההתחלה ועד לנושאים המתקדמים.
לפרטים נוספים והרשמה בקרו בדף קורס ריאקט כאן באתר.
אחת השאלות שחוזרות על עצמן בעבודה עם ריאקט היא איך להתמודד עם טפסים. גישה אחת היא לנהל את כל המידע שבטופס מחוץ ל State, ואז לקרוא את המידע משדות ה input רק כשצריכים אותו. היום אני רוצה להראות גישה שניה בה המידע כולו נשמר בסטייט ומעודכן עם כל שינוי בערכי ה input.
1. למה זה כל כך מכוער?
כשאנשים מנסים לכתוב קוד שמסנכרן בין המידע ב State של פקד לבין זה שרואים על המסך בטופס בתיבות ה input התוצאה בדרך כלל לא נראית טוב. הנה דוגמא לטופס שדומה לדברים שראיתי ברשת:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {one: '', two: '', three: ''};
}
setOne = (e) => {
this.setState({ one: e.target.value });
}
setTwo = (e) => {
this.setState({ two: e.target.value });
}
setThree = (e) => {
this.setState({ three: e.target.value });
}
render() {
const { one, two, three } = this.state;
return (
<div>
<form>
<label>
one: <input type="text" value={one} onChange={this.setOne} />
</label>
<label>
two: <input type="text" value={two} onChange={this.setTwo} />
</label>
<label>
three: <input type="text" value={three} onChange={this.setThree} />
</label>
</form>
<p>{one}, {two}, {three}</p>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('main'));
הבעיה המרכזית בטופס היא החזרתיות: אני שומר שלושה דברים ב State, מגדיר שלוש פונקציות Setter, ומעביר 3 פעמים פרמטרים זהים ל input. זה מציק וככל שהטופס יגדל מבנה כזה רק יהיה קשה יותר לכתיבה ותחזוקה.
2. שכתוב 1 - מעבר ל Functional Component
הדבר הראשון שאפשר לעשות עם קומפוננטה כזאת יהיה להעביר אותה ל Functional Component. כך במקום לנהל בסטייט את כל שלושת המשתנים וכל פעם להעביר שם של שדה, נוכל להגדיר את שלושת המשתנים כמשתנים נפרדים ופונקציות העדכון יוכלו להיות פשוטות יותר.
אחרי השינוי הקוד נראה כך:
const { useState } = React;
function App(props) {
const [one, setOne] = useState('');
const [two, setTwo] = useState('');
const [three, setThree] = useState('');
return (
<div>
<form>
<label>
one: <input type="text" value={one} onChange={(e) => setOne(e.target.value)} />
</label>
<label>
two: <input type="text" value={two} onChange={(e) => setTwo(e.target.value)} />
</label>
<label>
three: <input type="text" value={three} onChange={(e) => setThree(e.target.value)} />
</label>
</form>
<p>{one}, {two}, {three}</p>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('main'));
אבל זה עדיין לא קוד שאני שמח לראות: אפשר לראות שהרבה מהקוד עדיין חוזר על עצמו, שלושת פונקציות העדכון נראות דומות מדי אחת לשניה וגם כתיבת שדות ה input דורשת יותר מדי פרמטרים.
3. שכתוב 2 - Custom Hook
אפשר לפתור את שתי הבעיות תוך שימוש ב Custom Hook. למשל אם יהיה לי Hook שמחזיר אוביקט עם השדות שאותם אני צריך לרשום ב input אני יכול להשתמש בו באופן הבא:
const { useState } = React;
function useFormField(name) {
const [data, setter] = useState('');
return {name, value: data, onChange(e) { return setter(e.target.value) } };
}
function App(props) {
const input1 = useFormField('one');
const input2 = useFormField('two');
const input3 = useFormField('three');
return (
<div>
<form>
<label>
one: <input type="text" {...input1} />
</label>
<label>
two: <input type="text" {...input2} />
</label>
<label>
three: <input type="text" {...input3} />
</label>
</form>
<p>{input1.value}, {input2.value}, {input3.value}</p>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('main'));
וזה ביטל לגמרי את החזרות בקוד הטופס ובשדות ה input שלידו. אני אוהב את הקוד הזה והרבה פעמים הייתי נשאר עם גירסא זו.
4. שיכתוב 3 - חיבור כל השדות לאוביקט אחד
למרות האהבה לגירסא הקודמת יהיו מתכנתים שלא יאהבו את העובדה שצריך לקרוא מחדש ל useFormField
עבור כל אחד מהשדות בטופס. קל לתקן את זה ולשנות את ה Hook שיחזיר אוביקט עם כל השדות:
const { useState } = React;
function useForm(...fields) {
const inputs = {};
for (let name of fields) {
const [value, setter] = useState('');
inputs[name] = { name, value, onChange: (e) => setter(e.target.value) };
}
return inputs;
}
function App(props) {
const fields = useForm('one', 'two', 'three');
return (
<div>
<form>
<label>
one: <input type="text" {...fields.one} />
</label>
<label>
two: <input type="text" {...fields.two} />
</label>
<label>
three: <input type="text" {...fields.three} />
</label>
</form>
<p>{fields.one.value}, {fields.two.value}, {fields.three.value}</p>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('main'));
אבל בינינו אני מעדיף להישאר עם הגירסא השניה. לא מפריע לי להפעיל את הפונקציה שלוש פעמים ויותר נוח לי שאני יכול בקוד הפקד לבחור את שמות המשתנים שמייצגים את השדות.
מוזמנים לראות את הגירסא האהובה עליי עובדת בקודפן בקישור: https://codepen.io/ynonp/pen/pogbggm
או לייב כאן למטה: