از کدنویسی ریاکت و یا نکست جی اس برای طراحی سایتهای مدرن و سئو لذت میبرم! دنبال توسعهدهنده خلاق برای سایت یا لندینگ پیج هستید؟ من اینجام! 😊 zil.ink/seyedahmaddev
۱۷ سوال مصاحبه ریاکت که هر توسعهدهندهای در سال ۱۴۰۴ باید بداند
ریاکت یکی از پرتقاضاترین مهارتها برای توسعه دهندگان فرانت اند در سال است. اگر برای مصاحبه توسعه دهندگی ریاکت آماده میشوید، بسیار مهم است که با بهترین شیوه ها، الگوها و مفاهیم بهروز پیش بروید.
این مقاله لیستی از ۱۷ سوال مصاحبه ریاکت را گردآوری کرده که همه چیز را از اصول پایه ریاکت تا بهینه سازیهای پیشرفته عملکرد پوشش میدهد و به شما کمک میکند در مصاحبه بعدی خود بدرخشید.
بیایید شروع کنیم!
۱. DOM مجازی در ریاکت چیست؟ چه تفاوتی با DOM واقعی و Shadow DOM دارد؟
DOM مجازی مفهومی است که در آن یک نمایش مجازی از DOM واقعی در حافظه نگهداری میشود و فقط در صورت لزوم توسط کتابخانه ای مانند ReactDOM با DOM واقعی همگام سازی میشود.
DOM مجازی یک شیء است که DOM واقعی را در حافظه نشان میدهد. از آنجایی که به روزرسانیهای DOM بخش جدایی ناپذیر از هر برنامه وب هستند اما پر هزینه ترین عملیات در توسعه فرانت اند محسوب میشوند، DOM مجازی برای بررسی بخشهایی از برنامه که نیاز به به روزرسانی دارند استفاده میشود و فقط همان بخشها را بهروز میکند که این امر به طور قابل توجهی عملکرد را بهبود میبخشد.
DOM مجازی مفهومی کاملاً متفاوت از DOM واقعی و Shadow DOM است. DOM واقعی نمایش واقعی سند HTML در ساختاری درختی است که مرورگرها برای ردیابی محتوای یک صفحه وب از آن استفاده میکنند و Shadow DOM یک روش برنامه نویسی است که به توسعه دهندگان اجازه میدهد کامپوننتهای ایزوله و قابل استفاده مجدد برای برنامه های وب ایجاد کنند.
۲. دو نوع کامپوننت در ریاکت کدامند؟ و چه زمانی باید از آنها استفاده کنیم؟
دو نوع کامپوننت در ریاکت وجود دارد:
- کامپوننتهای کلاسی
- کامپوننتهای تابعی
در گذشته، کامپوننتهای کلاسی تنها راه برای ایجاد کامپوننتهای با قابلیت در ریاکت بودند و کامپوننتهای تابعی فقط به عنوان کامپوننتهای نمایشی استفاده میشدند و اغلب به آنها کامپوننتهای "گنگ" میگفتند.
اما با انتشار ریاکت ۱۶.۸ و معرفی هوکهای ریاکت ، کامپوننتهای تابعی اکنون میتوانند state و متدهای چرخه حیات داشته باشند که آنها را به روش ترجیحی برای ایجاد کامپوننتها در ریاکت تبدیل کرده است.
کامپوننتهای تابعی بسیار سریعتر از کامپوننتهای کلاسی هستند و کد کمتری نیاز دارند، بنابراین توصیه میشود تا حد امکان از کامپوننتهای تابعی استفاده کنید. با این حال، برخی از متدهای چرخه حیات هنوز فقط در کامپوننتهای کلاسی در دسترس هستند، بنابراین ممکن است در برخی موارد خاص مانند ایجاد مرزهای خطای سفارشی خود (مثلاً استفاده از متد چرخه حیات componentDidCatch در کامپوننتهای کلاسی) نیاز به استفاده از کامپوننتهای کلاسی داشته باشید.
۳. چرا در ریاکت به key نیاز داریم؟ آیا میتوانیم key را بدون لیستها در ریاکت استفاده کنیم؟
key در ریاکت برای شناسایی عناصر منحصربهفرد DOM مجازی با داده های مربوطه که UI را هدایت میکنند استفاده میشود. استفاده از key به ریاکت کمک میکند رندرینگ را با بازیابی عناصر DOM موجود بهینهسازی کند.
key به ریاکت کمک میکند که تشخیص دهد کدام آیتمها تغییر کرده اند، اضافه یا حذف شده اند و این امکان را فراهم میکند که از عناصر DOM موجود مجدداً استفاده کند که این امر باعث افزایش عملکرد میشود.
مثال:
const Todos = ({ todos }) => {
return (
<div>
{todos.map((todo) => (
<li>{todo.text}</li>
))}
</div>
);
};
این کد باعث میشود هر بار که todos تغییر میکنند، عناصر DOM جدیدی ایجاد شوند، اما اضافه کردن prop کلید (مثلاً `<li key={todo.id}>{todo.text}</li>`) باعث میشود عناصر DOM داخل تگ ul جابه جا شوند و فقط liهای لازم به روزرسانی شوند.
key را میتوان با هر عنصری در ریاکت استفاده کرد و لزوماً نیازی به استفاده با لیستها ندارد، اما معمولاً با لیستها برای بهینه سازی رندرینگ استفاده میشود. برخی موارد استفاده غیراستاندارد (و غیرتوصیهشده) از key شامل استفاده از آنها برای مجبور کردن رندر مجدد کامپوننتها است، مانند مثال زیر:
```jsx
const TimeNow = () => {
const [key, setKey] = useState(0);
useEffect(() => {
const interval = setInterval(() => setKey((prevKey) => prevKey + 1), 1000);
return () => clearInterval(interval);
}, []);
return <div key={key}>{new Date()}</div>;
};
```
همانطور که ذکر شد، قطعاً باید از چنین کدی اجتناب کنید و بهتر است به جای استفاده از key برای مجبور کردن رندر مجدد، زمان را در state ذخیره کنید. استفاده از key برای مجبور کردن رندر مجدد یک الگوی ضد الگو است و میتواند منجر به مشکلات شدید عملکردی شود مگر اینکه دقیقاً بدانید چه کاری انجام میدهید.
۴. تفاوت بین input کنترلشده و کنترل نشده در ریاکت چیست؟
کامپوننتهای کنترل شده به state ریاکت برای مدیریت دادههای فرم متکی هستند، در حالی که کامپوننتهای کنترل نشده از خود DOM برای مدیریت داده های فرم استفاده میکنند.
در بیشتر موارد، کامپوننتهای کنترل شده ترجیح داده میشوند زیرا یک منبع واحد برای داده های فرم ارائه میدهند که مدیریت، اعتبارسنجی و ارسال دادههای فرم را آسانتر میکند.
مثال کنترل شده:
```jsx
const ControlledInputForm = () => {
const [value, setValue] = useState("");
const handleChange = (e) => setValue(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
console.log(value);
};
return (
<form ={handleSubmit}>
<input type="text" value={value} ={handleChange} />
<button type="submit">Submit</button>
</form>
);
};
```
مثال کنترل نشده:
```jsx
const UncontrolledInputForm = () => {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log(inputRef.current.value);
};
return (
<form ={handleSubmit}>
<input type="text" ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
};
```
۵. چرا باید کد JSX را transpile کنیم؟
مگر اینکه به خودتان سخت بگیرید، احتمالاً از ریاکت به این شکل استفاده نمیکنید:
```jsx
import { createElement } from "react";
const Greeting = ({ name }) => {
return createElement("h1", { className: "greeting" }, "Hello");
};
```
با این حال، این چیزی است که مرورگر میخواند - مرورگر به سادگی قادر به درک سینتکس JSX که معمولاً برای نوشتن کامپوننتهای ریاکت استفاده میشود نیست.
بنابراین ما مجبوریم از ابزارهایی مانند Babel برای تبدیل JSX به جاوااسکریپت استفاده کنیم تا مرورگر بتواند آن را اجرا کند. بنابراین وقتی کد زیر را مینویسید:
```jsx
const Greeting = ({ name }) => <h1 className="greeting">Hello</h1>;
```
این کد به کد قبلی تبدیل میشود تا مرورگر بتواند آن را تفسیر کند و کامپوننت را رندر کند.
۶. JSX چگونه از حملات تزریق جلوگیری میکند؟
از آنجا که JSX محتوا را به صورت متن رندر میکند، هر عنصری در ورودی کاربر به عنوان HTML تلقی نمیشود، بلکه فقط به عنوان متن ساده نمایش داده میشود. برای مثال، تگ اسکریپت زیر به عنوان متن رندر میشود و اجرا نمیشود:
```jsx
const MyComponent = () => {
const content = "alert('XSS')";
return <div>{content}</div>;
};
```
توجه: میتوانید این رفتار را با استفاده از dangerouslySetInnerHTML لغو کنید، اما این کار توصیه نمیشود مگر اینکه کاملاً از منبع ورودی مطمئن باشید (و اکیداً توصیه میشود محتوا را قبل از تزریق پاکسازی کنید).
```jsx
const MyComponent = () => {
const content = "alert('XSS')";
return <div dangerouslySetInnerHTML={{ __html: content }} />;
};
```
۷. چگونه میتوانیم به کامپوننتهای ریاکت استایل اضافه کنیم؟
فایلهای CSS
استفاده از فایلهای CSS یکی از رایجترین روشها برای استایل دادن به کامپوننتهای ریاکت است. این روش امکان استفاده از تمام ویژگیهای CSS را فراهم میکند و به طور پیشفرض با Create React App تنظیم شده است.
```css
/* Button.css */
.button {
background-color: blue;
color: white;
}
```
```jsx
// Button.tsx
import "./Button.css";
const Button = () => {
return <button className="button">Click me</button>;
};
```
CSS داخلی (Inline CSS)
استایل دادن به عناصر ریاکت با استفاده از CSS داخلی امکان استایل دهی کاملاً محدود به یک عنصر را فراهم میکند. با این حال، برخی ویژگیهای استایل دهی با استایلهای داخلی در دسترس نیستند. برای مثال، استایل دهی شبه کلاسها مانند :hover.
```jsx
const Button = () => {
return (
<button style={{ backgroundColor: "blue", color: "white" }}>
Click me
</button>
);
};
```
ماژولهای CSS-in-JS (Styled Components, Emotion و Styled-jsx)
ماژولهای CSS-in-JS یک گزینه محبوب برای استایل دادن به برنامههای ریاکت هستند زیرا به طور نزدیک با کامپوننتهای ریاکت ادغام میشوند. برای مثال، آنها اجازه میدهند استایلها بر اساس props ریاکت در زمان اجرا تغییر کنند. همچنین، به طور پیشفرض، بیشتر این سیستمها تمام استایلها را به کامپوننت مربوطه محدود میکنند.
```jsx
import styled from "styled-components";
const Button = styled.button`
background-color: blue;
color: white;
`;
const App = () => {
return <Button>Click me</Button>;
};
```
ماژولهای CSS
روش مورد علاقه شخصی من برای استایل دهی، ماژولهای CSS امکان استایل دهی محدود به یک کامپوننت را فراهم میکنند. این یک راه عالی برای جلوگیری از برخورد نام کلاسها (اصطلاح فانتزی برای زمانی که دو کلاس با همان نام پایان مییابند - که در پروژههای بزرگ بسیار رایج است)، سازماندهی استایلها و اضافه کردن استایلهای مشترک به چندین کامپوننت است.
```css
/* Button.module.css */
.button {
background-color: blue;
color: white;
}
```
```jsx
// Button.js
import styles from "./Button.module.css";
const Button = () => {
return <button className={styles.button}>Click me</button>;
};
```
۸. رویدادهای مصنوعی (Synthetic Events) در ریاکت چیستند؟
رویدادهای مصنوعی پاسخ رویدادهای بومی مرورگرهای مختلف را در یک API ترکیب میکنند و اطمینان حاصل میکنند که رویدادها در مرورگرهای مختلف سازگار هستند. برنامه بدون توجه به مرورگری که در آن اجرا میشود، سازگار است.
```jsx
const Component = () => {
const handleClick = (e) => {
e.preventDefault(); // رویداد مصنوعی
console.log("link clicked");
};
return <a ={(e) => handleClick}>Click me</a>;
};
```
۹. حالت Strict در ریاکت چیست؟
`<StrictMode />` یک کامپوننت است که همراه با ریاکت ارائه میشود تا دید بیشتری از مشکلات احتمالی در کامپوننتها فراهم کند. اگر برنامه در حالت توسعه اجرا شود، هر مشکلی که پیش بیاید در کنسول توسعه ثبت میشود، اما این هشدارها اگر برنامه در حالت تولید اجرا شود نشان داده نمیشوند.
توسعه دهندگان از `<StrictMode />` برای یافتن مشکلاتی مانند متدهای چرخه حیات منسوخ شده و الگوهای قدیمی استفاده میکنند تا اطمینان حاصل کنند که تمام کامپوننتهای ریاکت از بهترین شیوههای فعلی پیروی میکنند.
`<StrictMode />` میتواند در هر سطحی از سلسله مراتب کامپوننتهای یک برنامه اعمال شود که این امکان را فراهم میکند که به صورت تدریجی در یک پایگاه کد استفاده شود.
۱۰. چگونه میتوانیم خطاها را در ریاکت به صورت مناسب مدیریت کنیم؟
به طور پیشفرض، اگر یک برنامه ریاکت در حین رندرینگ خطایی ایجاد کند، ریاکت UI آن را از صفحه حذف میکند. برای جلوگیری از این، میتوانیم بخشی از UI را در یک مرز خطا (error boundary) قرار دهیم که به ما امکان میدهد خطا را بگیریم و به جای خراب کردن کل برنامه، یک UI جایگزین نمایش دهیم.
میتوانید مرز خطای سفارشی خود را بسازید:
```jsx
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// state را بهروز کنید تا رندر بعدی UI جایگزین را نشان دهد
return { hasError: true };
}
render() {
if (this.state.hasError) {
// میتوانید هر UI جایگزین سفارشی را رندر کنید یا حتی آن را به عنوان prop بپذیرید
return <FallbackComponent />;
}
return this.props.children;
}
}
```
اما برای بیشتر موارد، میتوانید از پکیج `react-error-boundary` استفاده کنید که کامپوننتهای لازم برای مدیریت خطاها در برنامههای ریاکت را ارائه میدهد.
```jsx
import { ErrorBoundary } from "react-error-boundary";
const App = () => (
<ErrorBoundary FallbackComponent={FallbackComponent}>
<MyComponent />
</ErrorBoundary>
);
```
۱۱. قوانین هوکهای ریاکت چیستند؟
سه قانون اصلی برای هوکهای ریاکت وجود دارد:
۱. هوکها را فقط از توابع ریاکت فراخوانی کنید: هوکها فقط میتوانند در داخل یک کامپوننت تابعی ریاکت یا از داخل یک هوک دیگر فراخوانی شوند. فراخوانی هوکها در توابع معمولی JS/TS به عنوان یک فراخوانی تابع معمولی تلقی میشود و همانطور که انتظار میرود کار نمیکند.
۲. هوکها را فقط در سطح بالا فراخوانی کنید: هوکها فقط میتوانند در سطح بالای یک کامپوننت تابعی ریاکت یا یک هوک سفارشی فراخوانی شوند. این اطمینان حاصل میکند که هوکها در هر رندر به همان ترتیب فراخوانی میشوند. استفاده از هوکها در داخل حلقه ها، شرایط یا توابع تو در تو باعث خطا میشود.
۳. هوکها باید با 'use' شروع شوند: تمام نامهای هوک (شامل هوکهای سفارشی) باید با کلمه "use" شروع شوند. این اطمینان حاصل میکند که ریاکت میتواند هوکها را شناسایی کند و قوانین هوکها را اعمال کند. برای مثال، useState، useEffect، useContext و غیره.
۱۲. چگونه متدهای چرخه حیات رایج را در کامپوننتهای تابعی ریاکت مدیریت کنیم؟
متدهای چرخه حیات رایج در ریاکت عبارتند از:
- componentDidMount: بعد از اینکه کامپوننت روی DOM نصب شد فراخوانی میشود. معمولاً برای دریافت داده یا انجام اثرات جانبی مانند اضافه کردن شنودگرهای رویداد استفاده میشود.
- componentDidUpdate: بعد از اینکه یک مقدار خاص در کامپوننت به روزرسانی شد فراخوانی میشود. معمولاً برای انجام اثرات جانبی بر اساس مقدار به روزرسانی شده استفاده میشود.
- componentWillUnmount: یک متد پاکسازی که قبل از اینکه کامپوننت از DOM حذف شود فراخوانی میشود. معمولاً برای حذف شنودگرهای رویداد یا لغو درخواستهای شبکه استفاده میشود.
در کامپوننتهای تابعی، این متدهای چرخه حیات را میتوان با استفاده از هوک useEffect مدیریت کرد. هوک useEffect یک تابع را به عنوان آرگومان اول و یک آرایه از وابستگیها را به عنوان آرگومان دوم میگیرد.
```jsx
const Component = () => {
useEffect(() => {
// componentDidMount
console.log("Component mounted");
return () => {
// componentWillUnmount
console.log("Component unmounted");
};
}, []); // آرایه وابستگی خالی به این معنی است که این effect فقط یک بار هنگام نصب کامپوننت اجرا میشود
useEffect(
() => {
// componentDidUpdate
console.log("Component updated");
},
[
/* وابستگیها - تغییر در این مقادیر باعث اجرای مجدد تابع میشود */
/* توجه: این تابع در هنگام نصب نیز اجرا میشود */
]
);
return <React.Fragment />;
};
```
۱۳. refها در ریاکت چیستند؟
refها متغیرهایی هستند که به شما امکان میدهند داده ها را بین رندرها حفظ کنید، درست مانند متغیرهای state، اما برخلاف متغیرهای state، به روزرسانی refها باعث رندر مجدد کامپوننت نمیشود.
refها معمولاً برای (اما نه محدود به) ذخیره ارجاع به عناصر DOM استفاده میشوند.
```jsx
const Component = () => {
const inputRef = useRef(null);
const handleClick = () => inputRef.current.focus();
return (
<div>
<input ref={inputRef} type="text" />
<button ={handleClick}>Focus Input</button>
</div>
);
};
```
۱۴. Prop drilling در ریاکت چیست؟ چگونه میتوانیم از آن جلوگیری کنیم؟
در هنگام توسعه برنامه های ریاکت ، اغلب نیاز به انتقال داده از یک کامپوننت که در سلسله مراتب بالاتر قرار دارد به یک کامپوننت که به شدت تو در تو است وجود دارد. Prop drilling به فرآیند انتقال props از یک کامپوننت منبع به کامپوننت عمیقاً تو در تو از طریق تمام کامپوننتهای میانی اشاره دارد.
اشکال استفاده از Prop drilling این است که کامپوننتهایی که در غیر این صورت نباید از داده ها اطلاع داشته باشند به داده ها دسترسی دارند، علاوه بر این، نگهداری آن سخت تر میشود.
میتوان از Prop drilling با استفاده از Context API یا نوعی کتابخانه مدیریت state جلوگیری کرد.
۱۵. برخی از تکنیکهای بهینه سازی در ریاکت را شرح دهید
استفاده از memoization
useMemo یک هوک ریاکت است که برای کش توابع پر هزینه از نظر CPU استفاده میشود. یک تابع پر هزینه از نظر CPU که به دلیل رندرهای مجدد یک کامپوننت به طور مکرر فراخوانی میشود میتواند منجر به مشکلات شدید عملکرد و کاهش UX شود.
از هوک useMemo میتوان برای کش نتایج چنین توابعی استفاده کرد. با استفاده از useMemo، تابع پر هزینه از نظر CPU فقط زمانی که نیاز است فراخوانی میشود.
لودینگ تنبل (Lazy Loading)
لودینگ تنبل یک تکنیک است که برای کاهش زمان لود اولیه یک برنامه ریاکت استفاده میشود. این تکنیک با لود کردن کامپوننتها در حین پیمایش کاربر در برنامه، خطر کاهش عملکرد برنامه وب را به حداقل میرساند.
Throttling و Debouncing
اگرچه این یک تکنیک بهینه سازی خاص ریاکت نیست، اما اغلب در برنامه های ریاکت برای بهبود عملکرد استفاده میشود. Throttling و Debouncing تکنیکهایی هستند که برای محدود کردن تعداد دفعات فراخوانی یک تابع در پاسخ به یک رویداد استفاده میشوند - آنها اغلب با ورودیهایی استفاده میشوند که بازخورد بلادرنگ به کاربر میدهند (مثلاً تایپ در یک فیلد جستجو با پیشنهادات خودکار - فراخوانی API برای دریافت پیشنهادات میتواند throttled یا debounced شود تا از ایجاد فراخوانیهای غیرضروری API جلوگیری شود).
۱۶. پورتالها چیستند؟
پورتال یک روش توصیه شده برای رندر کردن فرزندان در یک گره DOM است که خارج از سلسله مراتب DOM کامپوننت والد وجود دارد. توصیه میشود که یک گره DOM جدید برای پورتال ایجاد کنید.
```jsx
const Portal = ({ children }) => {
// توجه: فرض بر این است که ریشه پورتال از قبل در فایل HTML وجود دارد
// <div id="portal-root" />
const portalRoot = document.getElementById("portal-root");
return ReactDOM.createPortal(children, portalRoot);
};
```
۱۷. React Fiber چیست؟
React Fiber یک مفهوم در ریاکت است که برای رندر کردن سیستم سریعتر و روانتر استفاده میشود. این یک تغییر موتور داخلی است که هدف آن سریعتر و تا حدی "هوشمندتر" کردن ریاکت است. Fiber reconciler که از ریاکت ۱۶ به بعد به عنوان reconciler پیشفرض تبدیل شد، یک بازنویسی کامل از الگوریتم تطبیق ریاکت است تا برخی مشکلات دیرینه در ریاکت را حل کند.
از آنجا که Fiber ناهمزمان است، ریاکت میتواند:
- کار رندرینگ روی کامپوننتها را با رسیدن به روزرسانیهای جدید متوقف کند، از سر بگیرد یا مجدداً شروع کند
- کارهای قبلی را مجدداً استفاده کند و حتی در صورت عدم نیاز آنها را لغو کند
- کار را به قسمتهایی تقسیم کند و وظایف را بر اساس اهمیت اولویت بندی کند
این تغییر به ریاکت اجازه میدهد از محدودیتهای Stack Reconciler قبلی که وظایف نمیتوانستند قطع شوند فاصله بگیرد. این تغییر همچنین به ریاکت اجازه میدهد رندرینگ کامپوننتها را دقیق تنظیم کند و اطمینان حاصل کند که مهمترین بهروزرسانیها در اسرع وقت اتفاق میافتند.
آیا سرتان را میخارانید و فکر میکنید "reconciliation" چیست؟
در ریاکت ، reconciliation مکانیسم اصلی است که مسئول به روزرسانی کارآمد UI در پاسخ به تغییرات state یا props یک کامپوننت است. این مکانیسم حداقل مجموعه عملیات مورد نیاز برای تبدیل DOM واقعی به حالت مورد نظر نمایش داده شده توسط DOM مجازی را تعیین میکند.
همه چیز تمام شد! 🎉
مطلبی دیگر از این انتشارات
استفاده از (prevState state) یا حالت قبلی در ری اکت، مبتدی و پیچیده
مطلبی دیگر از این انتشارات
هوکهای ری اکت یا React Hooks
مطلبی دیگر از این انتشارات
آموزش ساخت تم در MUI برای اپلیکیشنهای ری اکت