اگه از کتاب خوشتون اومد به گیتهابمون مراجعه کنین و بهمون⭐️ بدین. اگر هم قصد مشارکت داشتید خیلی خوشحال میشیم 😃 http://github.com/mariotek
میتونین خیلی راحت از نسخه آنلاین استفاده کنین یا اگه به فایل کتاب میخوایین دسترسی داشته باشین، از بخش ریلیزهای گیت هاب
به فرمتهای مختلف دانلود کنین.
در ابتدا، ممنونم از شما که با خرید این کتاب بهمون کمک کردین که بتونیم قدمی در راه کمک به افراد نیازمند برداریم و با درآمد حاصل از فروش این کتاب
کمکی هر چند کوچیک در راه مسئولیت اجتماعیمون برداریم، به همدیگه کمک کنیم، با هم مهربونتر باشیم و در کنار هم پیشرفت کنیم.
تشکر گرم من رو، دورادور پذیرا باشین و امیدوارم این کتاب به جهت افزایش دانشتون و کمک به پیشرفت شغلیتون کمکی کرده باشه.
کتابی که پیشروی شماست، حاصل تلاش نه فقط من، بلکه چندین نفر از بهترین و حرفهایترین دوستان بنده هم هست که در اینجا به ترتیب میزان زحمتی که متقبل شدن اسمشونو قید میکنم و کمال تشکر رو ازشون دارم:
مهسا مصباح
امین آشتیانی
مهدی رحیمی
من جعفررضائی، پلتفرم ماریوتک رو با هدف آموزش اصولی و رایگان، تاسیس کردم و این کتاب هم از مجموعه ماریوتک منتشر میشه. ما ماریوتک رو متعلق به همه میدونیم، پس اگه بعضی تایمهای بیکاری داری که فکر میکنی میتونی باهامون توی این مسیر همراه باشی حتما بهم ایمیل بزن. ایدههای ماریوتک برای افزایش آگاهی و دانش تا حد امکان رایگان خواهد بود و تا به اینجا هم، تنها هزینههای چاپ برداشته شده و مابقی به موسسات خیریه داده شدن.
مطالب این کتاب میتونن تا حد بسیار خوبی دانش شما رو توی مسائل کلیدی مربوط به React.js و کتابخونههای پیرامون اون افزایش بدن. سوالات چالشی و کلیدی مطرح شده توی کتاب اکثرا سوالاتی هستند که توی مصاحبههای استخدامی پرسیده میشن و مسلط بودن به اونا میتونه شانس موفقیت شما برای موقعیتهای شغلی که مدنظر دارین افزایش بده. مطالب این کتاب به دلیل ترجمه بودن تا حد زیادی قابل دستکاری نبودن و سعی شده تا حد امکان حق گردآورنده محفوظ باشه و با نسخه اصلی سورس که توسط Sudheer Jonna جمعآوری شده تفاوت معنایی نداشته باشه. بخشی از مطالب کتاب اصلی به خاطر قدیمی بودن منقضی شده بودن و به عنوان مترجم بخشهای زیادی از نمونه کدها و مطالب قدیمی تصحیح شدند. در آخر، امیدوارم همیشه شاد و خندان و خوشحال باشین. مخلصیم
ریاکت یه کتابخونه متنباز هست که برای ساختن رابط کاربری به خصوص برنامههای تک صفحهای استفاده میشه. از این کتابخونه برای مدیریت لایه view توی برنامههای وب و موبایل استفاده میشه. توسط Jordan Walke تولید شده که یه مهندس نرمافزار توی شرکت فیسبوک هستش. اولین بار سال ۲۰۱۱ و روی برنامه اینستاگرام مورد استفاده قرار گرفت.
اصلیترین ویژگیهای ریاکت اینا هستن:
از VirtualDOM به جای RealDOM استفادهمیکنه چون هزینه تغییرات RealDOM زیاده (یعنی پیدا کردن DOM Element و حذف یا به روز رسانی با سرعت کمتری انجام میشه)
از SSR یا همون Server Side Rendering پشتیبانی میکنه
از جریان دادهها یا data binding به صورت یک طرفه (unidirectional) پیروی میکنه
برای توسعه view از UI کامپوننتهای reusable/composable استفاده میکنه
JSX یه افزونه با سینتکسی شبیه به XML برای ECMAScript است (مخفف Javascript XML). اگه بخوایم ساده بگیم وظیفه اش اینه که سینتکسی ساده تر از React.createElement
دراختیارتون قرار میده، شما میتونین Javascript رو در کنار ساختاری شبیه به HTML داشته باشید.
تو مثال زیر میبینید که نوشته داخل تگ h1 مثل یک تابع Javascript به تابع render تحویل داده میشه.
function component
function App() { return ( <div> <h1>{"Welcome to React world!"}</h1> </div> ); }
class component
class App extends React.Component { render() { return ( <div> <h1>{"Welcome to React world!"}</h1> </div> ); } }
Element یک شی ساده است که وظیفه داره اون چیزی که روی صفحه نمایش داده میشه رو توصیف کنه، حالا ممکنه به صورت یک DOM node باشه یا به صورت componentهای دیگه. Element ها میتونن شامل Elements های دیگه به عنوان props باشند. ساختن یک Element در React کار ساده و کم دردسریه اما وقتی که ساخته شد هیچ وقت نمیشه تغییرش داد.
تو مثال زیر یک شی که توسط React Element ساخته شده رو میبینیم:
const element = React.createElement("div", { id: "login-btn" }, "Login");
تابع React.createElement
که توی قطعه کد بالا میبینید یه object شبیه به این برمیگردونه:
{
type: 'div',
props: {
children: 'Login',
id: 'login-btn'
}
}
و آخرش هم با استفاده از ReactDOM.render
میتونیم توی DOM , Render کنیم
<div id="login-btn">Login</div>
درحالیکه یه component میتونه به روشهای مختلفی ساخته بشه. میتونه یه class باشه با یه متد render
. یا حتی به عنوان یه جایگزین سادهتر به صورت یک تابع تعریف بشه. در هر دو حالت کامپوننت ساخته شده props رو به عنوان ورودی دریافت میکنه و یه خروجی رو به صورت یه JSX tree برمیگردونه. به مثال زیر دقت کنیم که چطور با استفاده از یه تابع و JSX یک کامپوننت ساخته میشه:
const Button = ({ onLogin }) => ( <div id={"login-btn"} onClick={onLogin}> Login </div> );
JSX به React.createElement
ترنسپایل (transpile) میشه:
const Button = ({ onLogin }) => React.createElement( "div", { id: "login-btn", onClick: onLogin }, "Login" );
تو سوال قبل یه اشاره کوچیک کردیم که دوتا راه برای ساختن کامپوننت وجود داره.
۱. Function Components: این سادهترین راه برای ساختن یه کامپوننته. یه Pure Javascript Function رو در نظر بگیرید که Props که خودش یه object هست رو به عنوان پارامتر ورودی میگیره و یه React Element به عنوان خروجی برمیگردونه مثل همین مثال پایین:
function Greeting({ message }) { return <h1>{`Hello, ${message}`}</h1>; }
۲. Class Components: شما میتونین از class که در ES6 به جاواسکریپت اضافه شده برای این کار استفاده کنیم. کامپوننت مثال قبلی رو اگه بخواییم با class پیاده سازی کنیم اینجوری میشه:
class Greeting extends React.Component { render() { return <h1>{`Hello, ${this.props.message}`}</h1>; } }
فقط یادتون نره تو این روش متدrender
یه جورایی required میشه.
میشه گفت هیچ لزومی به اینکار نیست(مگر در مواقع خیلی خاص مثل error boundaryها) و از ورژن 16.8 ریاکت به بعد و با اضافه شدن هوکها به فانکشن کامپوننتها، شما میتونین از state یا lifecycle methodها یا تمامی فیچرهایی که قبلا فقط در کلاس کامپوننت ها قابل استفاده بود، توی فانکشن کامپوننتهاتون استفاده کنین.
ولی قبلا اگه کامپوننت نیاز به state یا lifecycle methods داشت از کلاس کامپوننتها باید استفاده میکردیم و در غیر این صورت میرفتیم سراغ فانکشن کامپوننتها.
برای اینکه ریرندر شدن یه کامپوننت رو کنترل کنیم، توی کلاس کامپوننتها بجای Component از PureComponent کامپوننتمون رو میساختیم، در حقیقت PureComponent
دقیقا مثل Component
میمونه فقط تنها تفاوتی که داره اینه که برخلاف Component
خودش به صورت خودکار متد shouldComponentUpdate
رو هندلمیکنه.
وقتی که props یا state در کامپوننت تغییرمیکنه، PureComponent
یه مقایسه سطحی روی props و state انجام میده (shallow comparison) در حالیکه Component این مقایسه رو به صورت خودکار انجام نمیده و به طور پیشفرض کامپوننت هربار که shouldComponentUpdate
فراخوانی بشه re-render میشه. بنابراین توی Component باید این متد override بشه.
برای انجام اینکار روی فانکشن کامپوننتها، میشه از React.memo
استفاده کرد.
State در هر کامپوننت بسته به کلاس کامپوننت بودن یا فانکشن بودن نوع متفاوتی داره، مثلا در کلاس کامپوننتها state یه آبجکته که یه سری اطلاعات که در طول عمر کامپوننت ما ممکنه تغییر کنه رو در خودش ذخیرهمیکنه. ما باید تمام تلاشمون رو بکنیم که stateمون در ساده ترین حالت ممکن باشه و تاجایی که میتونیم تعداد کامپوننتهایی که stateful هستن رو کاهش بدیم. به عنوان مثال بیایید یه کامپوننت User رو که یه state داره بسازیم:
function
const User = () => { const [message, setMessage] = useState('Hello react world!'); return ( <div> <h1>{message}</h1> </div> ); }
class
class User extends React.Component { constructor(props) { super(props); this.state = { message: "Welcome to React world", }; } render() { return ( <div> <h1>{this.state.message}</h1> </div> ); } }
State و Props بهم شبیه هستن ولی Stateها کاملا در کنترل کامپوننت هستن و فقط مختص به همون کامپوننت هستن(private). یعنی state ها در هیچ کامپوننتی به غیر از اونی که مالکstate هست در دسترس نخواهند بود.
_Prop_ها ورودی کامپوننتها هستن. میتونن یه مقدار ساده یا یه object شامل یه مجموعه مقدار باشن که در لحظه ایجاد کامپوننت و بر اساس یه قاعده نام گذاری که خیلی شبیه به attributeهای HTML هست، به کامپوننت پاس داده میشن. در واقع این مقادیر، دادههایی هستن که از کامپوننت پدر به فرزند تحویل داده میشن.
هدف اصلی وجود Props در ریاکت ایجاد ساختارهای زیر در یک کامپوننتـه، به طورکلی میشه گفت که هدفشون:
1 - پاس دادن مقادیر به کامپوننتهای فرزند.
2 - پاس دادن متد تغییر state و trigger کردن اون متد در زمان تغییر دلخواه.
3 - استفاده از اونا برای پاس دادن jsx و رندر کردن یه المنت دلخواه داخل یه کامپوننت دیگه.
به عنوان مثال، یه کامپوننت با استفاده ازrenderProp
میسازیم:
<Element renderProp={<span>Hi there</span>} />
این renderProp
(یا هرچیز دیگهای که شما میتونین اسمشو بزارین) در نهایت تبدیل به یک property خواهد شد که داخل props ورودی کامپوننت به شکل object قابل دسترس هست.
const Element = (props) => { return <div> {props.renderProp} </div> }
توی کامپوننت بالا یه jsx با prop به کامپوننت Element داده میشه و توی اون رندر میشه 😃
هر دوتاشون javascript plain object هستن، یعنی یه object که یه سری property داره که به یه سری مقدار ختم میشن و خبری از فانکشن و چیزهای دیگه روی این object وجود نداره(تقریباِ). هردوتاشون وظیفه دارن مقادیری که روی render تاثیر گذار هست رو نگهداری کنن اما عملکردشون با توجه به کامپوننت متفاوت خواهد بود. Props شبیه به پارامترهای ورودی یک فانکشن، به کامپوننت پاس داده میشن در حالیکه state شبیه به متغییرهایی که داخل فانکشن ساخته شدن، توسط خود کامپوننت ایجاد و مدیریت میشه.
اگه یه بار تلاش کنید که مستقیما state رو آپدیت کنید متوجه میشین که کامپوننت شما مجددا render نمیشه.
// Wrong let [message, setMessage] = useState('test'); message = "Hello world";
به جای اینکه مستقیما state رو آپدیت کنیم باید از متد setState در Class Component و از useState در Function Components استفاده کنیم. این متدها یک آپدیت در شی state رو برنامه ریزی و مدیریت میکنن و وقتی تغییر انجام شد کامپوننت شما re-render خواهد شد.
const [message, setMessage] = React.useState("Hello world"); setMessage("New Hello world");
توی کلاس کامپوننتها میشه بعد از انجام شدن یه setState، یه کار خاص دیگهای رو توی callback انجام داد.
callback function زمانی که setState تموم شد و کامپوننت مجددا render شد فراخوانی میشه. از اونجایی که setState
به شکل asynchronous یا همون غیرهمزمان اجرا میشه از callback برای کارهایی استفاده میشه که بعد از تابع setState قراره اجرا بشن.
نکته مهم: بهتره که به جای callback از lifecycle متدها استفاده کنیم.
setState({ name: "John" }, () => console.log("The name has updated and component re-rendered") );
این سازوکار رو، میشه روی فانکشن کامپوننتها با یه کاستوم هوک به شکل زیر پیادهسازی کرد:
function useStateCallback(initialState) { const [state, setState] = useState(initialState); const cbRef = useRef(null); // mutable ref to store current callback const setStateCallback = useCallback((state, cb) => { cbRef.current = cb; // store passed callback to ref setState(state); }, []); useEffect(() => { // cb.current is `null` on initial render, so we only execute cb on state *updates* if (cbRef.current) { cbRef.current(state); cbRef.current = null; // reset callback after execution } }, [state]); return [state, setStateCallback]; }
یه کم پیچیده به نظر میرسه ولی خب میتونه یه هوک خیلی کاربردی باشه. به شکل زیر هم ازش استفاده میکنیم:
const App = () => { const [state, setState] = useStateCallback(0); // same API as useState + setState with cb const handleClick = () => { setState( prev => prev + 1, // 2nd argument is callback , `s` is *updated* state s => console.log("I am called after setState, state:", s) ); }; return <button onClick={handleClick}>Increment</button>; }
توی HTML، عنوان رخداد حتما باید با حرف کوچیک شروع بشه یا اصطلاحا lowercase باشه:
<button onclick="activateLasers()"></button>
ولی توی ریاکت از camelCase پیروی میکنه:
<button onClick={activateLasers}>Test</button>
توی HTML میتونیم برای جلوگیری از اجرای رفتار پیشفرض(preventDefault) یه مقدار false
برگرودونیم:
<a href="#" onclick='console.log("The link was clicked."); return false;' />
ولی توی ریاکت برای انجام این مورد حتما باید از preventDefault
استفاده بشه:
function handleClick(event) { event.preventDefault(); console.log("The link was clicked."); }
()
()
جلوی اسم تابع نیست.(برای مثال به کد اول و تابع "activateLasers" دقت کنید)توی کلاس کامپوننتها به سه روش مختلف میتونیم this رو به تابع هندلر موردنظرمون bind کنیم:
Bind کردن توی Constructor: توی کلاسهای جاواسکریپتی متدها به صورت پیشفرض bound نمیشن. همین موضوع توی کلاس کامپوننتهای ریاکتی برای متدهای موجود هم رخ میده که اکثرا توی متد سازنده یا همون constructor میآییم bind میکنیم.
class Component extends React.Componenet { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { // this now refers to the current Component } render(){ return ( <button onClick={this.handleClick}>{"Click me"}</button> ); } }
استفاده از فیلد عمومی کلاس(public): اگه از روش اول خوشتون نمیاد این روش هم میتونه context درست رو موقع callbackها براتون فراهم کنه.
handleClick = () => { console.log("this is:", this); }; <button onClick={this.handleClick}>{"Click me"}</button>
توابع arrow توی callback: میتونین از توابع arrow به شکل مستقیم توی callbackها استفاده کنین.
<button onClick={(event) => this.handleClick(event)}>{"Click me"}</button>
نکته: اگه متدهای callback به عنوان prop به کامپوننتهای فرزندشون پاس داده بشن، ممکنه اون کامپوننتها re-renderingهای ناخواستهای داشته باشن. توی اینگونه موارد روش توصیه شده استفاده از .bind
یا فیلد عمومی کلاس برای مدیریت پرفورمنس هستش.
میتونیم از توابع arrow استفاده کنیم که با wrap کردن دور event handler و پاس دادن مقدار موردنظرمون بهش کارمونو انجام بدیم:
const handleClick = (id) => { console.log("Hello, your ticket number is", id); }; return users.map(user => <button onClick={() => handleClick(user.id)}>Test</button>);
جدا از این روشها، میشه با ایجاد یه curry، یه تابع دیگه دور تابع هندلر خودمون wrap کنیم و پارامتر رو به اون پاس بدیم:
const handleClick = (id) => () => { console.log("Hello, your ticket number is", id); }; return users.map(user => <button onClick={handleClick(user.id)} />);
SyntheticEvent
یه رخداد cross-browser هست که بهعنوان یه wrapper دور eventهای اصلی مرورگر قرار میگیره. رابط API برای کارکردن با اون دقیقا مثل رخداد native مرورگرهاست که شامل stopPropagation
و preventDefault
میشه، با این تفاوت که این رخدادها بر روی همه مرورگرها کار میکنن.
برای بررسی یه شرط میتونیم از عبارت شرطی if استفاده کنیم، البته عملگرهای درون خطی سهگانه(ternary) هم میشه استفاده کرد که از ویژگیهای خود js هستن. جدا از این ویژگیها، میتونیم هر عبارتی داخل آکولاد و توی JSX به اصطلاح embed یا ترکیب کنیم و با عملگر منطقی &&
ترکیب کنیم، مثال پایینی رو ببنید:
<h1>Hello!</h1>; { messages.length > 0 && !isLogin ? ( <h2>You have {messages.length} unread messages.</h2> ): ( <h2>You don't have unread messages.</h2> ); }
پارامتر key
یه attribute ویژه است و موقعی که داریم یه آرایه از المانها رو ایجاد میکنیم این پارامتر رو باید بهشون به عنوان prop بدیم. Keyها به ریاکت کمک میکنن که بدونه باید کدوم اِلمان رو دقیقا اضافه، حذف یا به روز کنه.
اکثرا از ID یا از یه دیتای یونیک به عنوان key استفاده میکنن:
const todoItems = todos.map((todo) => <li key={todo.id}>{todo.text}</li>);
مواقعی که یه آیدی خاص برای المانها نداریم، ممکنه بیایید و از اندیس یا همون index به عنوان key استفاده کنید:
const todoItems = todos.map((todo, index) => ( <li key={index}>{todo.text}</li> ));
نکته:
استفاده از indexها برای key توصیه نمیشه چون ممکنه ترتیب عناصر خیلی راحت عوض بشه و این میتونه پرفورمنس برنامه رو تحت تاثیر بزاره.
اگه بیایین و لیست مورد نظر رو به جای li
مثلا با یه کامپوننت به اسم ListItem جایگزین کنین و prop موردنظر key رو به جای li
به اون پاس بدیم، یه warning توی کنسول خواهیم داشت که میگه key پاس داده نشده.
ref به عنوان یه مرجع برای دسترسی مستقیم به اِلمان موردنظرمون استفاده میشه. تا حد امکان و توی اکثر مواقع بهتره از اونا استفاده نکنیم، البته خیلی میتونن کمک کننده باشن چون دسترسی مستقیمی به DOM element یا instance اصلی component بهمون میدن.
دو تا روش وجود داره:
استفاده از React.createRef
و پاس دادن اون به element مورد نظرمون با attributeref
.
const Component = () => { const myRef = React.createRef(); return <div ref={myRef} />; };
اگه از نسخه ۱۶.۸ به بالاتر هم استفاده میکنیم که یه هوک به اسم useRef هست و میتونیم به سادگی توی کامپوننتهای تابعی ازش استفاده کنیم. مثل:
const RenderCounter = () => { const counter = useRef(0); // Since the ref value is updated in the render phase, // the value can be incremented more than once counter.current = counter.current + 1; return <h1>{`The component has been re-rendered ${counter} times`}</h1>; };
Ref forwarding ویژگیایه که به بعضی از کامپوننتها این اجازه رو میده ref دریافت شده رو به کامپوننت فرزند انتقال بدن.
const ButtonElement = React.forwardRef((props, ref) => ( <button ref={ref} className="CustomButton"> {props.children} </button> )); // Create ref to the DOM button: const ref = React.createRef(); <ButtonElement ref={ref}>{"Forward Ref"}</ButtonElement>;
ترجیح اینه که از callback refs به جای findDOMNode
استفاده کنیم، چون findDOMNode
از توسعه کدهای ریاکت در آینده جلوگیریمیکنه و ممکنه خطاهای ناخواسته ایجاد کنه.
رویکرد قدیمی استفاده از findDOMNode
:
class MyComponent extends Component { componentDidMount() { findDOMNode(this).scrollIntoView(); } render() { return <div></div>; } }
رویکرد توصیه شده:
const MyComponent = () => { const nodeRef = useRef(); useEffect(() => { nodeRef.current.scrollIntoView(); }, []); return <div ref={nodeRef} />; }
اگه قبلا با ریاکت کار کرده باشین، احتملا با یه API قدیمی تر آشنا هستین که توی اون ویژگی ref یه رشتهست، مثل ref='textInput'
و DOM به صورت refs.textInput
قابل دسترسیـه. البته این ویژگی توی نسخه ۱۶ ریاکت حذف شده و توصیه نمیشه استفاده بشه، فقط برای یادگیری هست.
ریاکت رو مجبور میکنن که عناصر در حال اجرا رو دنبال کنه و این مساله یکم مشکل سازه چون باعث میشه خطاهای عجیب و غریب وقتی که ماژول ریاکت باندل میشه، رخ بده.
قابل انعطاف نیستن. اگه یه کتابخونه خارجی یه ref رو روی فرزند قرار بده، کاربر نمیتونه یه ref دیگهای رو روی اون اضافه کنه.
با یه آنالیزگر استاتیک مثل Flow کار نمیکنه و در حقیقت Flow یا تایپاسکریپت نمیتونه حدس بزنه که فریمورک چه کاری روی ref
انجام میده، مثل نوع داده اون(که ممکنه متفاوت باشه). refهای callbackدار بیشتر با آنالیزگرها سازگارترن.
اون طور که اکثر مردم از الگوی "render callback" انتظار دارن کار نمی کنه(برای مثال <DataGrid renderRow={this.renderRow} />
)
class MyComponent extends Component { renderRow = (index) => { // This won't work. Ref will get attached to DataTable rather than MyComponent: return <input ref={"input-" + index} />; // This would work though! Callback refs are awesome. return <input ref={(input) => (this["input-" + index] = input)} />; }; render() { return <DataTable data={this.props.data} renderRow={this.renderRow} />; } }
Virtual DOM(VDOM) یه کپی داخل memory از DOM واقعی هستش. این کپی از المانهای رابط کاربری توی حافظه رم نگهداری میشه و همواره با DOM اصلی و واقعی همگام سازی(sync) میشه. این مرحله بین تابع رندر و نمایش elementها روی صفحه رخ میده و به مجموعه اتفاقاتی که برای مدیریت این موارد انجام میشه reconciliation میگن.
Virtual DOM توی سه مرحله ساده کار میکنه.
هر زمان که دادههای اساسی تغییر میکنن، کل رابط کاربری توسط DOM مجازی مجددا رندر میشه.
تفاوت بین DOM قبلی و جدید محاسبه میشه.
بعد از انجام محاسبات، DOM واقعی فقط با مواردی که واقعاً تغییر کردن به روز میشه.
shadow DOM یه تکنولوژی مرورگره که در ابتدا برای تعیین متغیرها و ایزولهسازی css دروب کامپوننت(Web component) طراحی شده بود. virtual DOM مفهومیه که توسط کتابخونهها برای مدیریت DOM توسط جاواسکریپت با استفاده از APIهای مرورگرها اجرا شده.
Fiber موتور جدید برای عملیات reconciliation هست یا میشه گفت که پیادهسازی مجدد الگوریتم هسته ریاکت نسخه ۱۶ هست. هدف پیادهسازی ReactFiber برای بهبود کارکرد توی جاهایی مثل ایجاد انیمیشن، کار روی layout، کار با gestureها و قابلیت اینکه عملیات در حال اجرا رو متوقف، قطع یا مجددا فعال کنیم ساخته شده. البته میتونه برای اولویتبندی بروزسانیهای لازم توی DOM رو هم مدیریت کنه.
هدف پیادهسازی ReactFiber برای بهبود کارکرد توی ناحیههایی مثل انیمیشن، layout، کار با gestureها بود. میشه گفت مهمترین ویژگی incremental-rendering بوده که قابلیت بخشبندی(chunk کردن) عملیات اجرایی و متوقف و اجرا کردن اون توی فریمهای مختلف هست.
کامپوننتی که عناصر ورودی رو توی فرمهای ورودی کاربر کنترلمیکنه به عنوان کامپوننت کنترل شده شناخته میشن، توی این کامپوننتها هر تغییر state یه تابع نگهدارنده مرتبط باهاش رو داره.
به عنوان مثال، اگر یه input داشته باشیم و بخواییم اسم فرد رو بگیریم و به شکل حروف بزرگ نگهداری کنیم، باید از handleChange مثل زیر استفاده کنیم:
const [name, setName] = useState(''); const handleChange = (event) => { setName(event.target.value.toUpperCase()); } return <input value={name} onChange={handleChange} />
کامپوننتهای کنترل نشده کامپوننتهایی هستن که stateهاشون رو به صورت داخلی ذخیره می کنن و ما میتونیم با استفاده از یک ref و از روی DOM مقدار فعلی اون input رو پیدا کنیم. این یکم شبیه HTML سنتیه.
مثلا توی کامپوننت UserProfile زیر، ورودی name
با استفاده از ref قابل دسترسیه.
const UserProfile = () => { const inputRef = useRef(); const handleSubmit = (event) => { alert("A name was submitted: " + inputRef.current.value); event.preventDefault(); } return ( <form onSubmit={handleSubmit}> <label> {"Name:"} <input type="text" ref={input} /> </label> <input type="submit" value="Submit" /> </form> ); }
اکثر مواقع، توصیه میشه که از کامپوننتهای کنترلشده بجای این روش برای پیادهسازی فرمها و دریافت داده استفاده کنیم.
عناصر JSX به توابع React.createElement
تبدیل میشن تا عناصر ریاکتی بسازن که برای نمایش UI استفاده میشن. درحالی که cloneElement
برای کلون کردن یه عنصر و فرستادنش به عنوان prop جدید استفاده میشه.
وقتی که کامپوننتهای مختلف نیاز به یه داده خاص دارن که بین اونا مشترکه بهتره stateهای مشترک رو تا حد امکان به نزدیکترین کامپوننت بالاییشون انتقال بدیم. این مورد به این معنیه که اگه دو کامپوننت فرزند داریم که یه state مشخص رو دارن مدیریت میکنن توی خودشون، اون state رو میبریم توی کامپوننت والد و بجای مدیریت یه state توی دوتا کامپوننت، از یه جا و توی کامپوننت والد اون رو مدیریت میکنیم.
چرخه حیات کامپوننت سه مرحله داره:
Mounting: کامپوننت آماده اجرا روی DOM مرورگر هستش. این مرحله مقداردهی اولیه از متدهای lifecycle constructor
، getDerivedStateFromProps
، render
و componentDidMount
رو پوشش میده.
Updating: در این مرحله، کامپوننت از دو طریق به روزرسانی میشه، ارسال propهای جدید و به روزرسانی state از طریق setState
. این مرحله متدهای getDerivedStateFromProps
، shouldComponentUpdate
، render
، getSnapshotBeforeUpdate
و componentDidUpdate
رو پوشش میده.
Unmounting: در مرحله آخر کامپوننت مورد نیاز نیست و از DOM مرورگر حذف میشه. این مرحله فقط شامل متد componentWillUnmount
میشه.
البته بهتره اینجا این نکته رو بگیم که ریاکت برای به روز کردن DOM یه سری فازبندیهایی داره که خود اون مرحله رو توی سه تا فاز انجام میده. این پایین به این فازبندیها اشاره میکنیم.
Render کامپوننت بدون هیچ سایدافکتی رندر میشه. این فقط در مورد کامپوننتهای خالص صدقمیکنه و در این مرحله، ریاکت میتونه رندر رو متوقف، حذف یا restart کنه.
Pre-commit قبل از اینکه کامپوننت تغییرات رو روی DOM اعمال کنه، لحظهای وجود داره که به ریاکت اجازه میده از DOM داخل متد getSnappshotBeforeUpdate
بخونه.
Commit ریاکت با DOM کارمیکنه و lifecycleهای آخر رو به ترتیب اجرامیکنه، componentDidMount
برای نصب، componentDidUpdate
برای به روزرسانی و componentWillUnmount
برای غیرفعال کردن.
مراحل ریاکت ۱۶.۳ (یا نسخه تعاملی)
قبل از ورژن ۱۶.۳
ریاکت ۱۶.۳ به بعد
componentDidMount: بعد از اولین رندر اجرا میشه و همه درخواستهای AJAX، DOM یا بروزرسانی state و تنظیمات event listeners اجرا میشه.
shouldComponentUpdate: تعیینمیکنه که کامپوننت به روز بشه یا نه. به طور پیش فرض مقدار true
رو برمیگردونه. اگه مطمئن باشیم که کامپوننت بعد از اینکه state یا props به روزرسانی میشه نیازی به رندر شدن نداره، میتونیم مقدار false
رو برگردونیم. اینجا جای خوبی برای بهبود عملکرده چون این امکان رو بهمون میده که اگه کامپوننت prop جدید میگیره از render مجدد جلوگیری کنیم.
getSnapshotBeforeUpdate: درست قبل از رندر مجدد خروجی به DOM اجرا میشه. هر مقداری که توسط این متد برگشت داده میشه به متد componentDidUpdate
انتقال داده میشه. برای گرفتن اطلاعات از موقعیت اسکرول DOM مفیده.
componentDidUpdate: بیشتر برای به روزرسانی DOM در پاسخ به تغییرات state یا Prop استفاده میشه. این متد زمانی که shouldComponentUpdate
مقدار false
رو برگردونه قابل استفاده ست.
componentWillUnmount این متد برای کنسل کردن همه درخواستهای شبکه خروجی یا حذف همه event listenerهای مرتبط با کامپوننت استفاده میشه.
قبل ورژن ۱۶.۳
componentWillMount: قبل از رندر اجرا میشه و برای پیکربندی سطح برنامه توی کامپوننت ریشه استفاده میشه.
componentDidMount: بعد از اولین رندر اجرا میشه و همه درخواستهای AJAX، DOM یا بروزرسانی state و تنظیمات event listeners اجرا میشه.
componentWillReceiveProps: Executed when particular prop updates to trigger state transitions.
shouldComponentUpdate: تعیینمیکنه که کامپوننت به روز بشه یا نه. به طور پیش فرض مقدار true
رو برمیگردونه. اگه مطمئن باشیم که کامپوننت بعد از اینکه state یا props به روزرسانی میشه نیازی به رندر شدن نداره، میتونیم مقدار false
رو برگردونیم. اینجا جای خوبی برای بهبود عملکرده چون این امکان رو بهمون میده که اگه کامپوننت prop جدید میگیره از render مجدد جلوگیری کنیم.
componentWillUpdate: قبل از رندر مجدد کامپوننت وقتی که تغییرات state و props توسط shouldComponentUpdate
مقدار true
رو برگردونده باشه اجرا میشه.
componentDidUpdate: بیشتر برای به روزرسانی DOM در پاسخ به تغییرات state یا Prop استفاده میشه. این متد زمانی که shouldComponentUpdate
مقدار false
رو برگردونه قابل استفاده ست.
componentWillUnmount این متد برای کنسل کردن همه درخواستهای شبکه خروجی یا حذف همه event listenerهای مرتبط با کامپوننت استفاده میشه.
کامپوننت با مرتبه بالا(HOC) تابعـیه که یه کامپوننت میگیره و یه کامپوننت جدید برمیگردونه. اصولا این الگوییه که از ماهیت تلفیقی ریاکت گرفته شده.
ما اینا رو به عنوان کامپوننتهای خالص میشناسیم چون میتونن هر کدوم از کامپوننتهای فرزندشون رو که به صورت پویا ارائه شدن رو بپذیرن ولی هیچ کدوم از رفتارهای کامپوننتهای ورودی خودشون رو تغییر نمیدن.
const EnhancedComponent = higherOrderComponent(WrappedComponent);
HOC خیلی جاها میتونه استفاده بشه:
استفاده مجدد از کد، منطق و مفهوم bootstrap.
استفاده برای Render hijacking و کنترل خروجی رندر کامپوننت.
مفهوم state و دستکاری اون.
دستکاری propها.
میتونیم propهای انتقال داده شده به کامپوننت رو با استفاده از الگوی props proxy اضافه یا ویرایش کنیم:
function HOC(WrappedComponent) { return class Test extends Component { render() { const newProps = { title: "New Header", footer: false, showFeatureX: false, showFeatureY: true, }; return <WrappedComponent {...this.props} {...newProps} />; } }; }
Context روشی رو برای انتقال داده بین کامپوننتها فراهم میکنه بدون اینکه بخوایم توی هر سطح به صورت دستی دادهها رو منتقل کنیم. به عنوان مثال، معتبر بودن کاربر، چند زبانگی و فایلهای زبان، قالب UI مواردی هستن که توی خیلی از کامپوننتها و به شکل عمومی لازم داریم که در دسترس باشن و میتونیم از context برای مدیریتشون استفاده کنیم.
const { Provider, Consumer } = React.createContext(defaultValue);
children یه prop هستش که بهمون اجازه میده کامپوننتها رو به عنوان فرزند و به شکل داده به کامپوننتهای دیگه انتقال بدیم (prop.children
)، درست مثل propهای دیگهای که استفاده میکنیم. درخت کامپوننت که بین تگ باز و بسته کامپوننتها قرار داره به اون کامپوننت به عنوان prop children
پاس داده میشه.
یه تعداد متد برای کار بااین propها روی react API وجود داره که شامل React.Children.map
، React.Children.forEach
، React.Children.count
، React.Children.only
، React.Children.toArray
میشه.
یه مثال ساده از استفاده از children prop این پایین نوشته شده.
const MyDiv = React.createClass({ render: function () { return <div>{this.props.children}</div>; }, }); ReactDOM.render( <MyDiv> <span>{"Hello"}</span> <span>{"World"}</span> </MyDiv>, node );
کامنتها توی React/JSX شبیه به جاواسکریپت هستن اما کامنتهای چند خطی توی آکولاد قرار میگیرن.
کامنتهای تک خطی:
<div> {/* Single-line comments(In vanilla JavaScript, the single-line comments are represented by double slash(//)) */} {`Welcome ${user}, let's play React`} </div>
کامنتهای چند خطی:
<div> {/* Multi-line comments for more than one line */} {`Welcome ${user}, let's play React`} </div>
کلاس constructor تا زمانی که متد super
صدا زده نشده نمیتونه از this
استفاده کنه. همین مورد در رابطه با کلاسهای ES6 هم صدق میکنه. دلیل اصلی انتقال پارامترهای props به متد فراخوان super
دسترسی داشتن به this.props
توی constructor هستش.
با پاس دادن props:
class MyComponent extends React.Component { constructor(props) { super(props); console.log(this.props); // prints { name: 'John', age: 42 } } }
بدون پاس داده شدن props:
class MyComponent extends React.Component { constructor(props) { super(); console.log(this.props); // prints undefined // but props parameter is still available console.log(props); // prints { name: 'John', age: 42 } } render() { // no difference outside constructor console.log(this.props); // prints { name: 'John', age: 42 } } }
کد بالا نشون میده که this.props
فقط توی constructor متفاوت عمل میکنه و بیرون از constructor عملکردش عادیه، دلیلش هم اینه که توی متد سازنده کلاس، هنوز instance کامل ساخته نشده و در حال ساخته شدنه.
وقتی state یا props یه کامپوننت تغییرمیکنه، ریاکت با مقایسه عنصر تازه return شده و نمونه render شده قبلی تصمیم میگیره که به روزرسانی DOM واقعا ضروریه یا نه. وقتی این دو مقدار با هم برابر نباشه، ریاکت به روزرسانی DOM رو انجام میده. به این فرایند reconciliation گفته میشه.
اگر برای تبدیل کد JSX از ES6 یا babel استفاده میکنین میتونید این کار رو با computed property names انجام بدین.
handleInputChange(event) { this.setState({ [event.target.id]: event.target.value }) }
اینجا ما یه فیلد از Object رو به شکل متغیر داریم پر میکنیم، البته روی فانکشن کامپوننتها و با استفاده از هوک useState هم میشه به شکل داینامیک یه object رو پر کرد و فقط لازمه یه state به شکل object داشته باشیم:
const [myState, setMyState] = useState(); const handleInputChange = (event) => { setMyState({ [event.target.id]: event.target.value }); }
باید مطمئن باشیم که موقع استفاده از تابع هندلرمون به عنوان پارامتر، اون تابع صدا زده نشه. مثلا:
// Wrong: handleClick is called instead of passed as a reference! return <button onClick={this.handleClick()}>{'Click Me'}</button>
و به جاش تابع رو بدون پرانتز(فراخوانی) و به شکل رفرنس پاس بدیم:
// Correct: handleClick is passed as a reference! return <button onClick={this.handleClick}>{'Click Me'}</button>
نه، تابع React.lazy
در حال حاضر فقط خروجی پیش فرض(default export) رو پشتیبانیمیکنه. اگه بخوایم ماژولهایی رو import کنیم که به شکل پیشفرض export نشدن، میتونیم یه ماژول واسطه تعریف کنیم که اونا رو به عنوان پیش فرض مجددا تعریف میکنه. همچنین تضمین میکنه که tree shaking همچنان به کار خودش ادامه میده و کامپوننت موردنظر کدهای استفاده نشده رو نمیگیره.
بیاین یه کامپوننتی بنویسیم که چندین کامپوننت رو به عنوان خروجی ارائه میده.
// MoreComponents.js export const SomeComponent = /*... */; export const UnusedComponent = /*... */;
و کامپوننت MoreComponents.js
رو در فایل واسطه IntermediateComponent.js
مجددا به عنوان خروجی تعریف کنیم.
// IntermediateComponent.js export { SomeComponent as default } from "./MoreComponents.js";
حالا میتونیم ماژول خودمون رو با استفاده از تابع lazy به شکل زیر import کنیم.
import React, { lazy } from "react"; const SomeComponent = lazy(() => import("./IntermediateComponent.js"));
class
یه کلمه کلیدی توی جاواسکریپتـه و فایل با پسوند JSX در حقیقت همون فایل جاواسکریپتـه. دلیل اصلی استفاده ریاکت از className
به جای class
همینه و یه مقدار رشتهای رو به عنوان پارامتر className
پاس میدیم. مثل کد زیر:
render() { return <span className='menu navigation-menu'>Menu</span> }
یه الگوی مشخص توی ریاکت وجود داره که برای کامپوننتهایی استفاده میشه که چندین عنصر یا کامپوننت رو برمیگردونن. _Fragment_ها این امکان رو فراهم میکنن که بتونیم لیستی از فرزندان رو بدون اضافه کردن نودهای اضافی به DOM گروه بندی کنیم.
render() { return ( <React.Fragment> <ChildA /> <ChildB /> <ChildC /> </React.Fragment> ) }
همچنین یه حالت مختصرتر هم وجود داره که به شکل زیر میتونیم fragment بسازیم:
render() { return ( <> <ChildA /> <ChildB /> <ChildC /> </> ) }
البته میدونیم که نه فقط div بلکه از بقیه تگهای html هم میشه بجای fragment استفاده کرد ولی به دلایل زیر بهتره از fragment استفاده بشه:
Fragmentها یه کم سریعترن و با ایجاد نکردن DOM node اضافی حافظه کمتری استفاده میکنن. این فقط روی nodeهای بزرگ و درختهای بزرگ و عمیق مزیت داره.
بعضی از مکانیزمهای CSS مثل Flexbox و CSS Grid روابط والد و فرزندی خاصی دارند و اضافه کردن div در وسط، حفظ طرح مورد نظرمون را دشوارمیکنه.
DOM Inspector بهم ریختگی کمتری داره و میشه راحتتر کدهای برنامه رو دیباگ کرد.
Portal روشی توصیه شده برای رندر کردن کامپوننت فرزند به شکل DOM و خارج از سلسله مراتب DOM کامپوننت والد هستش.
ReactDOM.createPortal(child, container);
اولین آرگومان یه فرزند قابل رندر شدن هستش، مثل عنصر، رشته، یا fragment. آرگومان دوم عنصر DOM هستش.
اگه رفتار یه کامپوننت مستقل از state اون کامپوننت باشه بهش کامپوننت stateless گفته میشه. میتونیم از یه تابع یا یه کلاس برای ساخت کامپوننتهای stateless استفاده کنیم،
اگه رفتار یه کامپوننتی به state اون کامپوننت وابسته باشه، به عنوان کامپوننت statefull شناخته میشه.
const App = () => { const [count, setCount] = useState(0); return ( //... ); }
قبل از نسخه 16.8 ریاکت:
قبل از اینکه هوکها این امکان رو بهمون بدن که بتونیم از state و ویژگیهای دیگه ریاکت استفاده کنیم بدون نوشتن کلاس کامپوننتها استفاده کنیم، نمیتونستیم فانکشن کامپوننت رو statefull کنیم و مجبور بودیم برای کامپوننت ساده فوق یه همچین کلاسی بنویسیم:
class App extends Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { // ... } }
وقتی برنامه توی حالت development یا در حال توسعه هست، ریاکت به شکل خودکار تمام propهایی که ما توی کامپوننت استفاده کردیم رو چک میکنه تا مطمئن بشه همهشون نوع درستی دارن. اگه هر کدوم از propها type درستی نداشته باشن توی کنسول بهمون یه warning نشون میده، البته توی حالت production این حالت غیر فعاله.
propهای اجباری با پراپرتی isRequired مشخص میشن، همچنین یهسری انواع prop از پیش تعریف شده وجود دارن که میتونیم ازشون استفاده کنیم:
PropTypes.number
PropTypes.string
PropTypes.array
PropTypes.object
PropTypes.func
PropTypes.node
PropTypes.element
PropTypes.bool
PropTypes.symbol
PropTypes.any
PropType
ها رو برای یه کامپوننت تستی به اسم User
اینطوری میشه تعریف کرد:
import React from "react"; import PropTypes from "prop-types"; const User = (props) => { return ( <> <h1>{`Welcome, ${props.name}`}</h1> <h2>{`Age, ${props.age}`}</h2> </> ); } User.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number.isRequired, };
نکته: در ورژن 15.5 ریاکت propTypeها از React.PropType
به کتابخونه جدید prop-types
انتقال پیدا کردن.
افزایش عملکرد برنامه با Virtual DOM.
خوندن و نوشتن راحتتر کدها با JSX.
امکان رندر شدن در هر دو سمت کاربر و سرور (SSR).
ادغام راحت با فریم ورکها (Angular, Backbone).
امکان نوشتن تستهای واحد یا ادغام شده از طریق ابزارهایی مثل Jest.
ریاکت یک کتابخونه برای ساخت لایه view هستش نه یک فریمورک کامل.
وجود یک منحنی یادگیری(سختی یادگیری یا همون learning curve) برای کسایی که به تازگی می خوان برنامه نویسی وب رو یاد بگیرن.
یکپارچهسازی ریاکت در فریمورکهای مبتنی بر MVC به یه کانفیگ اضافهای نیاز داره.
پیچیدگی کد با inline templating و JSX افزایش پیدامیکنه.
خیلی کامپوننتهای کوچیک یا boilerplateهای کوچیک براش ساخته شدن و ممکنه کمی گیج کننده باشه.
Error boundaryها یا به اصطلاح تحت الفظی مرزهای خطا کامپوننتهایی هستن که خطاهای جاواسکریپت رو هرجایی توی درخت فرزنداش رخ داده باشن catch میکنن و خطای موردنظر رو log میکنن و علاوه براین میتونن یه UI به اصطلاح fallback رو بجای کامپوننت crash شده نشون بدن.
توی یه کلاس کامپوننت با گذاشتن متد componentDidCatch(error, info)
یا static getDerivedStateFromError
میتونیم یه boundary برای زمانی که خطایی رخ میده درست کنیم. مثل:
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { // You can also log the error to an error reporting service logErrorToMyService(error, info); } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true }; } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>{"Something went wrong."}</h1>; } return this.props.children; } }
بعدشم میشه ازش مثل یه کامپوننت عادی استفاده کرد:
<ErrorBoundary> <MyWidget /> </ErrorBoundary>
نکته: از این قابلیت با استفاده از کامپوننتهای functional نمیشه استفاده کرد و در حقیقت احتمالا نیازی هم بهش ندارین، چون اکثر مواقع برای کل برنامه یه error boundary تعریف میکنیم که میتونه try..catch
باشه.
ریاکت توی نسخه 15 با استفاده از متد unstabled_handleError
error boundaryها رو مدیریت کرده.
این متد توی نسخه 16 به componentDidCatch
تغییر کرده.
به طور معمول به دو روش میشه نوع prop ورودی در برنامههای ریاکتی رو چک کرد. روش اول استفاده از کتابخانه prop-types و اعتبارسنجی ورودیهای کامپوننتـه و روش دوم که برای برنامههایی با کدهای بیشتر توصیه میشه، استفاده از _static type checker_هایی مثل flow یا typeScript هست که چک کردن نوع داده رو در زمان توسعه و کامپایل انجام میده ویژگیهای مثل auto-completion رو ارائه میده.
پکیج react-dom
متدهای DOM-specific یا مخصوص DOM رو ارائه میده که میتونه توی سطوح بالای برنامه شما استفاده بشه.
اکثر کامپوننتها نیازی به استفاده از این ماژولها ندارن. تعدادی از متدهای این پکیج اینها هستن:
متد render
متد hydrate
متد unmountComponentAtNode
متد findDOMNode
متد createPortal
این متد برای رندرکردن کامپوننت پاس داده شده، توی یه المنت DOM که به عنوان container پاس داده شده، استفاده میشه و یه رفرنس به کامپوننت برمیگردونه. اگه کامپوننت ریاکت قبلا توی container مورد نظر رندر شده باشه با یه update فقط DOMهایی که نیازمند به روز شدن باشن رو ری-رندر میکنه.
ReactDOM.render(element, container[, callback])
اگه پارامتر سوم که یه callback هست پاس داده بشه، هر موقع که رندر یا بهروزرسانی انجام بشه اون تابع هم اجرا میشه.
ReactDOMServer
این امکان رو بهمون میده که کامپوننتها رو به صورت استاتیک رندر کنیم (معمولا روی node server استفاده میشه). ReactDOMServer عمدتا برای پیاده سازی سمت سرور استفاده میشه (SSR).
متد renderToString
متد renderToStaticMarkup
برای مثال ممکنه یه سرور روی node بسازین که ممکنه Express، Hapi یا Koa باشه و متد renderToString
رو برای تبدیل کردن کامپوننت root به html اجرا کنید و نتیجه بدست اومده رو به عنوان response به کلاینت پاس بدین.
// using Express import { renderToString } from "react-dom/server"; import MyPage from "./MyPage"; app.get("/", (req, res) => { res.write( "<!DOCTYPE html><html><head><title>My Page</title></head><body>" ); res.write('<div id="content">'); res.write(renderToString(<MyPage />)); res.write("</div></body></html>"); res.end(); });
ویژگی dangerouslySetInnerHTML
جایگزین ریاکت واسه استفاده از innerHTML
توی DOM مرورگره و کارکردش درست مثل innerHTML
هستش، استفاده از این ویژگی به خاطر حملات cross-site-scripting(XSS) ریسک بالایی داره.
برای اینکار باید یه آبجکت innerHTML
به عنوان key و یه متن html به عنوان value به این prop بفرستیم(یا شاید همون پاس بدیم).
توی مثال پایینی کامپوننت از ویژگی dangerouslySetInnerHTML
برای قرار دادن HTML استفاده کرده.
function createMarkup() { return { __html: "First · Second" }; } function MyComponent() { return <div dangerouslySetInnerHTML={createMarkup()} />; }
attribute پیشفرض مورد استفاده برای استایلدهی style
هستش که یه object جاواسکریپت رو به عنوان مقدار ورودی دریافت میکنه. همه propertyهای اون بجای css عادی camelCase هستن. این روش با استایلدهی عادی توی جاواسکریپت یه کم متفاوت، بهینهتر و امنتره، چون جلوی حفرههای امنیتی مثل XSS رو میگیره.
const divStyle = { color: "blue", backgroundImage: "url(" + imgUrl + ")", }; function HelloWorldComponent() { return <div style={divStyle}>Hello World!</div>; }
مدیریت رویدادها روی اِلمانهای ریاکت یه سری تفاوتهای کلی با نحوه مدیریت اونا روی js داره:
event handlerهای ریاکت به جای حروف کوچیک به صورت حروف بزرگ نامگذاری میشن.
با JSX ما یه تابع رو به جای رشته به عنوان event handler پاس میدیم.
وقتی از setState
استفاده میکنیم، جدا از اینکه به یه آبجکت استیتی اختصاص داده میشه ریاکت اون کامپوننت و همه فرزندای اون کامپوننت رو دوباره رندر میکنه. ممکنه این ارور رو بگیرین: شما فقط می تونید کامپوننت mount شده یا در حال mount رو به روز رسانی کنید. پس باید بجای setState از this.state
برای مقداردهی state توی constructor استفاده کنیم.
توی فانکشن کامپوننتها هم اگه داخل بدنه تابع یه setState کنیم، کامپوننت توی حلقه رندر بینهایت میافته و خطا میگیریم.
keyها باید پایدار، قابل پیش بینی و منحصر به فرد باشن تا ریاکت بتونه اِلمانها رو رهگیری کنه.
تو کد زیر key هر عنصر براساس ترتیبی که توی لیست داره مقدار قرار می گیره و به دادههایی که میگیرن ربطی نداره. این کار بهینه سازیهایی که میتونه توسط ریاکت انجام بشه رو محدود میکنه.
todos.map((todo, index) => <Todo {...todo} key={index} />);
اگه از دادههای همون element به عنوان کلید بخوایم استفاده کنیم، مثلا todo.id. چونکه همه ویژگیهایی که یه کلید باید داشته باشه رو داره، هم استیبله و هم منحصر به فرد، توی این حالت ریاکت میتونه بدون اینکه لازم باشه دوباره همه اِلمنتها رو ارزیابی کنه رندر رو انجام بده.
todos.map((todo) => <Todo {...todo} key={todo.id} />);
توصیه میشه که از مقدار دهی اولیه غیرهمزمان(async) در متد componentWillMount
استفاده نشه. componentWillMount
درست قبل از mount شدن اجرا میشه و اون لحظه قبل از فراخوانی متد render
هست، پس setState کردن توی این متد باعث re-render شدن نمیشه. باید از ایجاد هر ساید افکتی توی این متد خودداری کنیم و دقت کنیم که اگه مقدار دهی اولیه غیر همزمانای داریم این کار رو توی متد componentDidMount
انجام بدیم نه در متد componentWillMount
.
componentDidMount() { axios.get(`api/todos`) .then((result) => { this.setState({ messages: [...result.data] }) }) }
معادل کد زیر با هوک:
useEffect(() => { axios.get(`api/todos`) .then((result) => { setMessages([...result.data]) }) }, []);
اگه propهای یه کامپوننت بدون اینکه اون کامپوننت رفرش بشه تغییر کنه، مقدار جدید اون prop نمایش داده نمیشه چون state جاری اون کامپوننت رو به روز رسانی نمیکنه، مقدار دهی اولیه state از propها فقط زمانی که کامپوننت برای بار اول ساخته شده اجرا میشه.
کامپوننت زیر مقدار به روزرسانی شده رو نشون نمیده:
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { records: [], inputValue: this.props.inputValue, }; } render() { return <div>{this.state.inputValue}</div>; } }
و نکته جالبش اینه که استفاده از propها توی متد render مقدار رو به روز رسانیمیکنه:
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { record: [], }; } render() { return <div>{this.props.inputValue}</div>; } }
توی فانکشن کامپوننتها هم دقیقا به همین شکل هست، مقدار اولیه useState اگه از prop ورودی کامپوننت باشه، با تغییر دادن prop مقدار اون state عوض نمیشه، دلیلش هم اینه که هوک useState فقط توی اولین رندر اجرا میشه و بعدش دیگه باید با استفاده از متد setterاش مقدار اون state رو عوض کنیم.
بعضی وقتا ما می خوایم کامپوننتهای مختلفی رو بسته به بعضی stateها رندر کنیم. JSX مقدار false
یا undefined
رو رندر نمیکنه، بنابراین ما میتونیم از short-circuiting شرطی برای رندر کردن بخش مشخصی از کامپوننتمون استفاده کنیم در صورتی که اون شرط مقدار true رو برگردونده باشه.
const MyComponent = ({ name, address }) => ( <div> <h2>{name}</h2> {address && <p>{address}</p>} </div> );
اگه به یه شرط if-else
نیاز دارین، میتونیم از عبارت شرطی سهگانه(ternary operator) استفاده کنیم:
const MyComponent = ({ name, address }) => ( <div> <h2>{name}</h2> {address ? <p>{address}</p>: <p>{"Address is not available"}</p>} </div> );
وقتی ما prop هارو spread میکنیم این کارو با ریسک اضافه کردن اتریبیوتهای HTML انجام میدیم که این کار خوبی نیست، به جای این کار میتونیم از...rest استفاده کنیم که فقط propهای مورد نیاز رو اضافهمیکنه.
const ComponentA = () => ( <ComponentB isDisplay={true} className={"componentStyle"} /> ); const ComponentB = ({ isDisplay,...domProps }) => ( <div {...domProps}>{"ComponentB"}</div> );
میتونیم کلاس کامپوننت ها رو decorate کنیم، که درست مثل پاس دادن کامپوننتها به تابع هستش. Decoratorها روش قابل خواندن و انعطاف پذیرتری برای تغییر فانکشنالیتی کامپوننتها هستن.
@setTitle("Profile") class Profile extends React.Component { //.... } /* title is a string that will be set as a document title WrappedComponent is what our decorator will receive when put directly above a component class as seen in the example above */ const setTitle = (title) => (WrappedComponent) => { return class extends React.Component { componentDidMount() { document.title = title; } render() { return <WrappedComponent {...this.props} />; } }; };
نکته: Decoratorها ویژگیهایی هستن که در حال حاضر به ES7 اضافه نشدن، ولی توی پیشنهاد stage 2 هستن.
در حال حاضر کتابخانههایی وجود داره که با هدف memoize کردن ایجاد شدن و میتونن توی کامپوننتهای تابع استفاده بشن، به عنوان مثال کتابخونه moize
میتونه یه کامپوننت رو توی بقیه کامپوننتها memoize کنه.
import moize from "moize"; import Component from "./components/Component"; // this module exports a non-memoized component const MemoizedFoo = moize.react(Component); const Consumer = () => { <div> {"I will memoize the following entry:"} <MemoizedFoo /> </div>; };
به روز رسانی: توی ورژن 16.6.0 ریاکت ،React.memo
رو داریم که کارش اینه که یه کامپوننت با الویت بالاتر فراهممیکنه که کامپوننت رو تا زمانی که propها تغییر کنن، memoize میکنه. برای استفاده ازش کافیه زمان ساخت کامپوننت از React.memo استفاده کنیم.
const MemoComponent = React.memo(function MemoComponent(props) { /* render using props */ }); // OR export default React.memo(MyFunctionComponent);
ریاکت در حال حاضر به رندر سمت نود سرور مجهزه، یه ورژن خاصی از DOM رندر در دسترسه که دقیقا از همون الگوی سمت کاربر پیروی میکنه.
import ReactDOMServer from "react-dom/server"; import App from "./App"; ReactDOMServer.renderToString(<App />);
خروجی این روش یه HTML معمولی به صورت یه رشتهست که داخل body صفحه به عنوان response سرور قرار میگیره.
در سمت کاربر، ریاکت محتوای از قبل رندر شده رو تشخیص میده و به صورت فرآیند همگامسازی با اونا رو انجام میده(rehydration).
میشه از پلاگین DefinePlugin
که روی وبپک قابل استفاده هست استفاده کرد و مقدار NODE_ENV
رو روی production
ستکرد، با اینکار خطاهای اضافی یا اعتبارسنجی propTypeها روی پروداکشن غیرفعال میشه و جدای این موارد، کدهای نوشته شده بهینهسازی میشن و مثلا کدهای بلااستفاده حذف میشن، کم حجمسازی انجام میشه و درنتیجه سرعت بهتری رو میتوته به برنامه بده چون سایز bundle ایجاد شده کوچیکتر خواهد بود.
ابزار CLI(محیط کدهای دستوری) create-react-app
این امکان رو بهمون میده که برنامههای ریاکت رو سریع و بدون مراحل پیکربندی بسازیم و اجرا کنیم.
مثلا، بیاین برنامه Todo رو با استفاده از CRA بسازیم:
# Installation
$ npm install -g create-react-app
# Create new project
$ create-react-app todo-app
$ cd todo-app
# Build, test and run
$ npm run build
$ npm run test
$ npm start
این شامل همه اون چیزیه که ما واسه ساختن یه برنامه ریاکت لازم داریم:
React، JSX، ES6 و روند پشتیبانی syntax.
موارد اضافی زبان شامل ES6 و عملگر object spread و اینا.
Autoprefixed CSS، بنابراین نیازی به -webkit- یا پیشوندهای دیگهای نداریم.
یه اجرا کننده تست تعاملی با پشتیبانی داخلی برای coveraage reporting.
یه سرور live development که اشتباهات معمول رو بهمون هشدار میده.
یه اسکریپت بیلد برای پک و باندل کردن css، js و تصاویر برای production همراه با hashها و sourcemap ها.
وقتی یه نمونهای از کامپوننت ساخته میشه و داخل DOM اضافه میشه، متدهای lifecycle به ترتیب زیر صدا زده میشن.
متد constructor
متد static getDerivedStateFromProps
متد render
متد componentDidMount
متدهای lifecycle روشهای ناامن کدنویسی هستن و با رندر async مشکل بیشتری پیدا میکنن.
متد componentWillMount
متد componentWillReceiveProps
متد componentWillUpdate
تو ورژن 16.3 ریاکت این متدها با پیشوند UNSAFE_
متمایز شدن و تو نسخه 17 ریاکت حذف شد.
بعد از اینکه یه کامپوننت بلافاصله بدون خطا و مثل قبل rerender شد، متد استاتیک getDerivedStateFromProps
صدا زده میشه.
این متد یا state آپدیت شده رو به صورت یه آبجکت برمی گردونه یا null رو برمی گردونه که معنیش اینه propهای جدید به آپدیت شدن state نیازی ندارن.
class MyComponent extends React.Component { static getDerivedStateFromProps(props, state) { //... } }
متد componentDidUpdate
تمام مواردی که توی متد componentWillReceiveProps
هست رو پوشش میده.
متد جدید getSnapshotBeforeUpdate
بعد از آپدیتهای DOM صدا زده میشه.
مقدار برگشتی این متد به عنوان پارامتر سوم به متد componentDidUpdate
پاس داده میشه.
class MyComponent extends React.Component { getSnapshotBeforeUpdate(prevProps, prevState) { //... } }
متد componentDidUpdate
تمام مواردی که توی متد componentWillUpdate
استفاده میشه رو پوشش میده.
هوکها میتونن بسیاری از نیازهای ما رو موقع تولید کامپوننتهای ریاکتی حل کنن.
کامپوننتهای با اولویت بالاتر و render propها هر دوشون فقط یه child رو رندر میکنن ولی هوکها روش راحت تری رو ارائه میدن که از تودرتو بودن درخت کامپوننتها جلوگیری میکنه.
برای نام گذاری کامپوننتها توصیه میشه که از نامگذاری هنگام export گرفتن به جای displayName
استفاده کنیم. استفاده از displayName
برای نام گذاری کامپوننت:
export default React.createClass({ displayName: "TodoApp", //... });
روش توصیه شده:
const TodoApp = () => (); export default TodoApp;
ترتیب توصیه شده متدها از mounting تا render stage:
متدهای static
متد constructor
متد getChildContext
متد componentWillMount
متد componentDidMount
متد componentWillReceiveProps
متد shouldComponentUpdate
متد componentWillUpdate
متد componentDidUpdate
متد componentWillUnmount
event handlerها مثل onClickSubmit
یا onChangeDescription
متدهای دریافت کننده برای رندر مثل getSelectReason
یا getFooterContent
متدهای رندر اختیاری مثل renderNavigation
یا renderProfilePicture
متد render
یه کامپوننت switcher کامپوننتیـه که یکی از چندتا کامپوننت موردنظر رو رندر میکنه. لازمه که برای تصمیم گیری بین کامپوننتها از object جاواسکریپتی استفاده کنیم.
برای مثال، کدپایین با بررسی prop موردنظر page
بین صفحات مختلف سويیچ میکنه:
import HomePage from "./HomePage"; import AboutPage from "./AboutPage"; import ServicesPage from "./ServicesPage"; import ContactPage from "./ContactPage"; const PAGES = { home: HomePage, about: AboutPage, services: ServicesPage, contact: ContactPage, }; const Page = (props) => { const Handler = PAGES[props.page] || ContactPage; return <Handler {...props} />; }; // The keys of the PAGES object can be used in the prop types to catch dev-time errors. Page.propTypes = { page: PropTypes.oneOf(Object.keys(PAGES)).isRequired, };
دلیلش اینه که setState
یه عملیات async یا ناهمزمانه.
stateها در ریاکت به دلایل عملکردی تغییر میکنن، بنابراین یه state ممکنه بلافاصله بعد از اینکه setState
صدا زده شد تغییر نکنه.
یعنی اینکه وقتی setState
رو صدا می زنیم نباید به state جاری اعتماد کنیم چون نمیتونیم مطمئن باشیم که اون state چی میتونه باشه.
راه حلش اینه که یه تابع رو با state قبلی به عنوان یه آرگومان به setState
پاس بدیم.
بیاین فرض کنیم مقدار اولیه count صفر هستش. بعد از سه عملیات پشت هم، مقدار count فقط یکی افزایش پیدا میکنه.
const [count, setCount] = useState(0); setCount(count + 1); setCount(count + 1); setCount(count + 1); // count === 1, not 3
اگه ما یه تابع به setState
پاس بدیم، مقدار count به درستی افزایش پیدا میکنه.
this.setState((prevState, props) => ({ count: prevState.count + props.increment, })); // this.state.count === 3 as expected
React.StrictMode
یه کامپوننت مفید برای هایلایت کردن مشکلات احتمالی توی برنامه ست.
<StrictMode>
درست مثل <Fragment>
هیچ المان DOM اضافهای رو رندر نمیکنه، بلکه warningها و additional checks رو برای فرزندان اون کامپوننت فعالمیکنه.
این کار فقط در حالت development فعال میشه.
import React from "react"; function ExampleApplication() { return ( <div> <Header /> <React.StrictMode> <div> <ComponentOne /> <ComponentTwo /> </div> </React.StrictMode> <Footer /> </div> ); }
توی مثال بالا، strict mode فقط روی دو کامپوننت <ComponentOne>
و <ComponentTwo>
اعمال میشه.
_Mixin_ها روشی برای جدا کردن کامپوننتهایی با عملکرد مشترک بودن. با توسعه یافتن ریاکت دیگه Mixinها نباید استفاده بشن و میتونن با کامپوننتهای با اولویت بالا(HOC) یا _decorator_ها جایگزین بشن.
یکی از بیشترین کاربردهای mixinها PureRenderMixin
بود. ممکنه تو بعضی از کامپوننتها برای جلوگیری از re-renderهای غیر ضروری وقتی propها و state با مقادیر قبلی شون برابر هستن از این mixinها استفاده کنیم:
const PureRenderMixin = require("react-addons-pure-render-mixin"); const Button = React.createClass({ mixins: [PureRenderMixin], //... });
نکته مهم: mixinهای ریاکت منقضی شدن و دیگه کاربردی ندارن، این سوال فقط برای افزایش آگاهی توی کتاب باقی میمونه.
کاربرد اصلی متد isMounted
برای جلوگیری از فراخوانی setState
بعد از unmount شدن کامپوننت هستش چونکه باعث ایجاد یه خطا میشه.
خطاش یه چیزی مثل اینه:
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
توی کلاس کامپوننتها هم این شکلی بعضا جلوشو میگرفتن:
if (this.isMounted()) { this.setState({...}) }
دلیل اینکه این روش توصیه نمیشه اینه که خطایی رو که ریاکت بهمون میداد رو داره دور میزنه و حلش نمیکنه. بهتره setState رو جایی انجام بدیم که توی مواقعی که کامپوننت mount نیست اجرا نشه.
البته توی نسخههای جدید ریاکت این کار رو خیلی سادهتر میشه انجام داد و فقط کافیه یه هوکی بنویسیم که یه ref رو مقداردهی میکنه و بعد با بررسی اون ref میشه فهمید که کامپوننت mount شده یا نه، مثلا:
export const useIsMounted = () => { const componentIsMounted = useRef(true); useEffect( () => () => { componentIsMounted.current = false; }, [] ); return componentIsMounted; };
یا حتی یه پکیجی ساخته شده به اسم ismounted
که میتونه بهمون کمک کنه که متوجه بشیم کامپوننت mount شده یا نه. ولی حواسمون باشه که ازش درست استفاده کنیم.
_pointer Event_ها یه روش واحدی رو برای هندل کردن همه ی ایونتهای ورودی ارائه میدن.
در زمانهای قدیم ما از موس استفاده میکردیم و برای هندل کردن ایونتهای مربوط به اون از event listenerها استفاده میکردیم ولی امروزه دستگاههای زیادی داریم که با داشتن موس ارتباطی ندارن، مثل قلمها یا گوشیهای صفحه لمسی.
باید یادمون باشه که این ایونتها فقط تو مرورگرهایی کار میکنن که مشخصه Pointer Events رو پشتیبانی میکنن.
ایونتهای زیر در React DOM در دسترس هستن:
onPointerDown
onPointerMove
onPointerUp
onPointerCancel
onGotPointerCapture
onLostPointerCapture
onPointerEnter
onPointerLeave
onPointerOver
onPointerOut
اگه ما با استفاده از JSX کامپوننتمون رو رندر میکنیم، اسم کامپوننت باید با حرف بزرگ شروع بشه در غیر این صورت ریاکت خطای تگ غیر قابل تشخیص رو میده.
این قرارداد به خاطر اینه که فقط عناصر HTML و تگهای svg می تونن با حرف کوچیک شروع بشن.
class SomeComponent extends Component { // Code goes here }میتونیم کلاس کامپوننتهایی که با حرف کوچیک شروع میشن رو هم تعریف کنیم ولی وقتی داریم ایمپورت میکنیم باید شامل حروف بزرگ هم باشن:
class myComponent extends Component { render() { return <div />; } } export default myComponent;
وقتی داریم تو یه فایل دیگهای ایمپورت میکنیم باید با حرف بزرگ شروع بشه:
import MyComponent from "./MyComponent";
بله. اون قدیما ریاکت DOM attributeهای ناشناخته رو نادیده میگرفت، اگه JSX رو با یه ویژگیای نوشته بودیم که ریاکت تشخیص نمیداد، اونو نادیده میگرفت. به عنوان مثال:
<div mycustomattribute="something" />
در ریاکت ورژن 15 یه div خالی توی DOM رندر میکنیم:
<div />
در ریاکت ورژن 16 هر attribute ناشناختهای توی DOM از بین میره:
<div mycustomattribute="something" />
این برای attributeهای غیر استاندارد مرورگرهای خاص، DOM APIهای جدید و ادغام با کتابخانههای third-party مفیده.
وقتی داریم از کلاسهای ES6 استفاده میکنیم باید state رو توی constructor مقداردهی اولیه کنیم و وقتی از React.createClass
استفاده میکنیم باید از متد getInitialState
استفاده کنیم.
استفاده از کلاسهای ES6:
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { /* initial state */ }; } }
استفاده از React.createClass
:
const MyComponent = React.createClass({ getInitialState() { return { /* initial state */ }; }, });
نکته: React.createClass
در ورژن 16 ریاکت حذف شده و به جای اون میشه از کلاسهای ساده جاواسکریپت استفاده کرد.
در حالت پیش فرض، وقتی state یا prop کامپوننت تغییر میکنه، کامپوننت دوباره رندر میشه. اگه متد render
به دادههای دیگهای وابسته باشه، توی فانکشن کامپوننتها میتونیم یه state تعریف کنیم و با ست کردن یه مقدار جدید توی اون state عامدانه باعث رندر مجدد کامپوننت بشیم. مثل:
const [tick, setTick] = useState(0); const reRender = () => setTick(tick => tick++);
توی مثال بالا ما یه تابع تولید کردیم که با هر بار فراخوانی اون، میتونیم انتظار رندر شدن کامپوننت رو داشته باشیم.
توی کلاس کامپوننتها، میتونیم با فراخوانی متد forceUpdate
به ریاکت بگیم که این کامپوننت نیازه که دوباره رندر بشه.
component.forceUpdate(callback);
توصیه میشه که از متد forceUpdate
استفاده نکنیم و توی render
فقط از this.props
و this.state
استفاده کنیم.
اگه بخوایم به this.props
توی constructor
دسترسی پیدا کنیم باید propها رو از طریق متد super
پاس بدیم.
استفاده از super(props)
:
class MyComponent extends React.Component { constructor(props) { super(props); console.log(this.props); // { name: 'John',... } } }
استفاده از super
:
class MyComponent extends React.Component { constructor(props) { super(); console.log(this.props); // undefined } }
بیرون از constructor
هردو متد مقادیر یکسانی رو برای this.props
نشون میدن.
خیلی ساده میتونیم از Array.prototype.map
با سینتکس _arrow توابع _ ES6 استفاده کنیم، برای مثال آرایهای از آیتمهای یه آبجکت توی آرایهای از کامپوننتها نوشته میشه:
<tbody> {items.map((item) => ( <SomeComponent key={item.id} name={item.name} /> ))} </tbody>
نمیتونیم با استفاده از حلقه for
تکرار رو انجام بدیم:
<tbody> for (let i = 0; i < items.length; i++) { <SomeComponent key={items[i].id} name={items[i].name} /> } </tbody>
به خاطر اینکه تگهای JSX داخل function calls تبدیل میشن ما نمیتونیم از statementها داخل عبارات استفاده کنیم.
ریاکت و در حقیقت JSX داخل یه attribute استفاده از متفیر به شکل عادی رو پشتیبانی نمیکنه.
مثلا کد پایین کار نمیکنه:
<img className="image" src="images/{this.props.image}" />
اما ما میتونیم هر عبارت js رو داخل کرلی براکت({}
) به عنوان مقدار کلی attribute قرار بدیم.
مثلا، تکه کد پایین کار میکنه:
<img className="image" src={"images/" + props.image} />
با استفاده از template strings هم میتونیم بنویسیم:
<img className="image" src={`images/${this.props.image}`} />
اگه بخوایم آرایهای از آبجکتها رو به یه کامپوننت با شکل خاصی پاس بدیم، از React.PropTypes.shape
به عنوان یه آرگومان برای React.PropTypes.arrayOf
استفاده میکنیم.
ReactComponent.propTypes = { arrayWithShape: React.PropTypes.arrayOf( React.PropTypes.shape({ color: React.PropTypes.string.isRequired, fontSize: React.PropTypes.number.isRequired, }) ).isRequired, };
نباید از کرلی براکت({}
) داخل کوتیشن(''
) استفاده کنیم چون به عنوان یه رشته در نظر گرفته میشه.
<div className="btn-panel {this.props.visible ? 'show': 'hidden'}">
به جاش میتونیم کرلی بریس رو به بیرون انتقال بدیم. (فراموش نکنیم که از space بین classNameها استفاده کنیم.)
<div className={'btn-panel ' + (this.props.visible ? 'show': 'hidden')}>
با استفاده از Template strings هم میتونیم بنویسیم:
<div className={`btn-panel ${this.props.visible ? 'show': 'hidden'}`}>
پکیج ریاکت شامل React.createElement
، React.Component
، React.Children
و helperهای دیگه که مربوطه به کلاس کامپوننتها و المنتها هستش. میتونیم اینا رو به عنوان isomorphic یا universal helpers که واسه ساختن کامپوننتها نیاز داریم, در نظر بگیریم.
پکیج react-dom
شامل ReactDOM.render
میشه و داخل react-dom/server
میتونیم با استفاده از متدهای ReactDOMServer.renderToString
و ReactDOMServer.renderToStaticMarkup
server-side rendering رو پشتیبانی کنیم.
تیم ریاکت سعی کرده تمام ویژگیهای مرتبط با DOM رو جدا کنه و اونا رو توی یه کتابخونه جدا به اسم ReactDom قرار بده. ریاکت ورژن ۱۴ اولین نسخهای بود که توش این کتابخونهها از هم جدا شدن. با یه نگاه به بعضی از پکیجهای ریاکت مثل react-native
، react-art
، react-canvas
و react-three
مشخص میشه که زیبایی و جوهر ریاکت هیچ ربطی به مرورگرها یا DOM نداره. برای ساختن محیطهای بیشتری که ریاکت بتونه رندر بشه، تیم ریاکت اومد و پکیج اصلی ریاکت رو به دو بخش تقسیم کنه: react
و react-dom
.
از این طریق تونست کامپوننتهایی تولید کنه بین ریاکت وب و ریاکت نیتیو و... قابل اشتراک باشه.
اگه سعی کنیم که با استفاده از for attribute یه عنصر <label>
متصل به یه متن رو رندر کنیم، اون وقت ویژگی HTML بودن رو از دست میده و یه خطا توی کنسول بهمون نشون میده.
<label for={'user'}>{'User'}</label> <input type={'text'} id={'user'} />
از اونجایی که for
یه کلمه کلیدی رزرو شده توی جاواسکریپته، به جاش باید از htmlFor
استفاده کنیم.
<label htmlFor={'user'}>{'User'}</label> <input type={'text'} id={'user'} />
میتونیم از spread operator توی ریاکت استفاده کنیم:
<button style={{...styles.panel.button,...styles.panel.submitButton }}> {"Submit"} </button>
اگه داریم از ریکت نیتیو استفاده میکنیم میتونیم از شکل آرایهای استایلها استفاده کنیم:
<button style={[styles.panel.button, styles.panel.submitButton]}> {"Submit"} </button>
میتونیم به رخداد resize
توی componentDidMount
گوش کنیم و ابعاد(width
و height
) رو تغییر بدیم. البته باید حواسمون باشه که این listener رو باید توی متد componentWillUnmount
حذفش کنیم.
class WindowDimensions extends React.Component { constructor(props) { super(props); this.updateDimensions = this.updateDimensions.bind(this); } componentWillMount() { this.updateDimensions(); } componentDidMount() { window.addEventListener("resize", this.updateDimensions); } componentWillUnmount() { window.removeEventListener("resize", this.updateDimensions); } updateDimensions() { this.setState({ width: window.innerWidth, height: window.innerHeight, }); } render() { return ( <span> {this.state.width} x {this.state.height} </span> ); } }
همین کار رو با استفاده از هوکها هم میشه انجام داد و برای این کار همین کد رو توی useEffect
مینویسیم.
const [dimensions, setDimensions] = useState(); useEffect(() => { window.addEventListener("resize", updateDimensions); function updateDimensions() { setDimensions({ width: window.innerWidth, height: window.innerHeight, }); } return () => { window.removeEventListener("resize", updateDimensions); }; }, []); return ( <span> {this.state.width} x {this.state.height} </span> );
وقتی که از متد setState
روی کلاس کامپوننت استفاده میکنیم، مقادیر فعلی و قبلی با هم ترکیب میشن. replaceState
حالت فعلی رو با stateای که میخواییم جایگزینش میکنه. معمولا اگه از setState
برای جایگزین کردن استفاده کنیم، همه کلیدهای قبلی رو پاک کنیم. البته میشه بجای استفاده از replaceState
با استفاده از setState
بیاییم و state رو برابر با false
یا null
قرار بدیم.
توی کلاس کامپوننتها هنگام به روز شدن state یه سری متدها فراخوانی میشه. با استفاده از این متدها میشه state و prop فعلی رو با مقادیر جدید مقایسه کرده و یه سری کار که مدنظر داریم رو انجام بدیم.
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
با استفاده از هوک useEffect هم این امکان بسادگی قابل انجامه و فقط کافیه به dependencyهای این هوک متغیر مربوط به state رو بدیم.
const [someState, setSomeState] = useState(); useEffect(() => { // code }, [someState]);
استفاده از متد Array.prototype.filter
آرایهها روش خوبیه.
برای مثال میتونیم یه تابع به اسم removeItem
برای به روز کردن state به شکل زیر در نظر بگیریم.
removeItem(index) { this.setState({ data: this.state.data.filter((item, i) => i !== index) }) }
توی نسخههای بالاتر از (>=16.2) میشه. برای مثال تکه کد پایین یه سری مثال برای رندر کردن یه مقدار غیر htmlای هست:
render() { return false }
render() { return null }
render() { return [] }
render() { return <React.Fragment></React.Fragment> }
render() { return <></> }
البته حواستون باشه که return کردن undefined
کار نخواهد کرد.
میشه گفت زیاد ربطی به ریاکت یا غیر ریاکت بودن برنامه نداره ولی در کل میشه با استفاده از تگ <pre>
و استفاده از optionهای متد JSON.stringify
این کار رو انجام داد:
const data = { name: "John", age: 42 }; class User extends React.Component { render() { return <pre>{JSON.stringify(data, null, 2)}</pre>; } } React.render(<User />, document.getElementById("container"));
فلسفه ساختاری ریاکت به شکلیه که propها باید immutable باشن و از بالا به پایین و به صورت سلسهمراتبی مقدار بگیرند. به این معنی که پدر هر کامپوننت میتونه هر مقداری رو به فرزند پاس بده و فرزند حق دستکاری اونو نداره.
میشه با ایجاد یه ref برای المنت input
و استفاده از اون توی componentDidMount
یا useEffect
اینکار رو کرد:
const App = () => { const nameInputRef = useRef(); useEffect(() => { nameInputRef.current.focus(); }, []); return ( <div> <input defaultValue={"Won't focus"} /> <input ref={nameInputRef} defaultValue={"Will focus"} /> </div> ); };
همین کد در کلاس کامپوننت:
class App extends React.Component { componentDidMount() { this.nameInput.focus(); } render() { return ( <div> <input defaultValue={"Won't focus"} /> <input ref={(input) => (this.nameInput = input)} defaultValue={"Will focus"} /> </div> ); } } ReactDOM.render(<App />, document.getElementById("app"));
فراخوانی متد setState
با استفاده از یه object برای ترکیب شدن اون:
استفاده از Object.assign
برای ایجاد یه کپی از object:
const user = Object.assign({}, this.state.user, { age: 42 }); this.setState({ user });
استفاده از عملگر spread:
const user = {...this.state.user, age: 42 }; this.setState({ user });
فراخوانی setState
با یه تابع callback: به این شکل میشه پیادهسازی کرد:
this.setState((prevState) => ({ user: { ...prevState.user, age: 42, }, }));
ریاکت اجازه ترکیب کردن تغییرات state رو با استفاده از متد setState
فراهم کرده، همین موضوع باعث بهبود پرفورمنس میشه. توی کلاس کامپوننتها this.props
و this.state
ممکنه به صورت asynchronous و همزمان به روز بشن، نباید به مقدار اونا برای محاسبه مقدار بعدی اعتماد کرد.
برای مثال به این شمارنده که درست کار نمیکنه دقت کنیم:
// Wrong this.setState({ counter: this.state.counter + this.props.increment, });
روش توصیه شده فراخوانی متد setState
با یه تابع بجای object هست. این تابع مقدار state قبلی رو به عنوان پارامتر اول و prop رو به عنوان ورودی دوم میگیره و این تابع رو زمانی که مقادیر ورودیش تغییر پیدا کنن فراخوانی میکنه.
// Correct this.setState((prevState, props) => ({ counter: prevState.counter + props.increment, }));
خیلی ساده میشه از مقدار React.version
برای گرفتن نسخه جاری استفاده کرد.
const REACT_VERSION = React.version; ReactDOM.render( <div>{`React version: ${REACT_VERSION}`}</div>, document.getElementById("app") );
import دستی از core-js
:
یه فایل ایجاد کنیم و اسمشو بزاریم (یه چیزی مثل) polyfills.js
و توی فایلindex.js
بیایید import کنیمش. کد npm install core-js
یا yarn add core-js
رو اجرا کنیم و ویژگیهایی که لازم داریم رو از corejs بارگذاری کنیم.
import "core-js/fn/array/find"; import "core-js/fn/array/includes"; import "core-js/fn/number/is-nan";
استفاده از سرویس Polyfill:
از سایت polyfill.io CDN واسه گرفتن مقدار شخصی سازی شده براساس مرورگر هر فرد استفاده کنیم و خیلی ساده یه خط کد به index.html
اضافه کنیم:
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.includes"></script>
مثلا توی تکه کد فوق ما برای polyfill کردن Array.prototype.includes
درخواست دادیم.
برای اینکار لازمه که کانفیگ HTTPS=true
رو برای env جاری ست کنیم، برای اینکار حتی لازم هم نیست فایل .env بسازیم و میتونیم توی فایل package.json
بخش scripts رو به شکل پایین تغییر بدیم:
"scripts": { "start": "set HTTPS=true && react-scripts start" }
یا حتی به شکل set HTTPS=true && npm start
هم میشه تغییر داد.
یه فایل به اسم .env
توی مسیر اصلی پروژه ایجاد میکنیم و مسیر مورد نظر خودمون رو اونجا مینویسم:
NODE_PATH=src/app
بعد از این تغییر سرور develop رو ریستارت میکنیم بعدش دیگه میتونیم هر چیزی رو از مسیر src/app
بارگذاری کنیم و لازم هم نباشه مسیر کاملشو بهش بدیم. اینکار رو میشه با بخش module resolve توی webpack هم انجام داد.
یه listener به آبجکت history
اضافه میکنیم تا بتونیم لود شدن صفحه رو track کنیم:
history.listen(function (location) { window.ga("set", "page", location.pathname + location.search); window.ga("send", "pageview", location.pathname + location.search); });
توی نسخه ۶ از react-router-dom دسترسی مستقیم به history برداشته شده تا پشتیبانی از suspense راحتتر باشه، توی این نسخه میشه از useLocation به شکل زیر استفاده کرد:
const location = useLocation(); useEffect(() => { window.ga("set", "page", location); window.ga("send", "pageview", location); }, [location]);
لازمه که از setInterval
استفاده کنیم تا تغییرات رو اعمال کنیم و البته حواسمون هست که موقع unmount این interval رو حذف کنیم که باعث memory leak نشه.
const intervalRef = useRef(); useEffect(() => { intervalRef.current = setInterval(() => this.setState({ time: Date.now() }), 1000); return () => { clearInterval(intervalRef.current); } }, [location]);
توی کلاس کامپوننت هم به شکل:
componentDidMount() { this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000) } componentWillUnmount() { clearInterval(this.interval) }
ریاکت به شکل اتوماتیک پیشوندهای مخصوص مرورگرها روی css رو اعمال نمیکنه. لازمه که تغییرات رو به شکل دستی اضافه کنیم.
<div
style={{
transform: "rotate(90deg)",
WebkitTransform: "rotate(90deg)", // note the capital 'W' here
msTransform: "rotate(90deg)", // 'ms' is the only lowercase vendor prefix
}}
/>
لازمه که از default برای export کردن کامپوننتها استفاده کنیم
import React from "react"; import User from "user"; export default class MyProfile extends React.Component { render() { return <User type="customer">//...</User>; } }
با استفاده از کلمه کلیدی export default میتونیم کامپوننت MyProfile(یا هر متغیر و کلاس دیگهای) رو به عنوان یه عضو از ماژول فعلی معرفی کرد و بعد از این، برای import کردن اون لزومی به استفاده از عنوان این کامپوننت نیست.
همه کامپوننتهای ریاکت لازمه که با حرف بزرگ شروع بشن، ولی توی این مورد هم یه سری استثناها وجود داره. تگهایی که با property و عملگر dot کار میکنن رو میشه به عنوان کامپوننتهایی با حرف کوچک تلقی کرد. برای مثال این تگ میتونه syntax معتبری برای ریاکت باشه که با حروف کوچیک شروع میشه:
const Component = () => { return ( <obj.component /> // `React.createElement(obj.component)` ) }
الگوریتم reconciliation ریاکت بعد از رندر کردن کامپوننت با بررسی رندرهای مجدد، بررسی میکنه که این کامپوننت قبلا رندر شده یا نه و اگه قبلا رندر شده باشه، تغییرات جدید رو روی همون instance قبلی رندر میکنه و instance جدیدی ساخته نمیشه، پس تابع سازنده هم تنها یکبار صدا زده میشه.
میتونیم از فیلد استاتیک
ES7 برای تعریف ثابت استفاده کنیم.
class MyComponent extends React.Component { static DEFAULT_PAGINATION = 10; }
فیلدهای استاتیک بخشی از فیلدهای کلاس(class properties) هستن که توی پروپوزال stage 3 معرفی شدن.
میتونیم از ref برای بدست آوردن رفرنس HTMLInputElement
مورد نظر استفاده کنیم و object بدست اومده رو توی یه متغیر یا property نگهداری کنیم، بعدش از اون رفرنس میتونیم برای اعمال رخداد کلیک استفاده کنیم
که HTMLElement.click
رو فراخوانی میکنه. این فرآیند توی دو گام قابل انجام هستش:
ایجاد ref توی متد render:
<input ref={inputRef} />
اعمال رخداد click توی event handler:
inputRef.current.click();
اگه بخواییم از async
/await
توی ریاکت استفاده کنیم، لازمه که Babel و پلاگین transform-async-to-generator رو استفاده کنیم. توی React Native اینکار با Babel و یه سری transformها انجام میشه.
دو روش معروف برای پوشههای ریاکت وجود داره:
گروه بندی براساس ویژگی یا route:
یک روش معروف قراردادن فایلهای CSS، JS و تستها کنارهم به ازای هر ویژگی یا route هست، مثل این ساختار:
common/
├─ Avatar.js
├─ Avatar.css
├─ APIUtils.js
└─ APIUtils.test.js
feed/
├─ index.js
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
└─ FeedAPI.js
profile/
├─ index.js
├─ Profile.js
├─ ProfileHeader.js
├─ ProfileHeader.css
└─ ProfileAPI.js
گروهبندی بر اساس ماهیت فایل:
یک سبک مشهور دیگر گروهبندی فایلها براساس ماهیت اونهاست که حالا همین روش هم میتونه به شکلهای مختلف اجرا بشه ولی ساختار پایین میتونه یه مثال برای این روش باشه:
api/
├─ APIUtils.js
├─ APIUtils.test.js
├─ ProfileAPI.js
└─ UserAPI.js
components/
├─ Avatar.js
├─ Avatar.css
├─ Feed.js
├─ Feed.css
├─ FeedStory.js
├─ FeedStory.test.js
├─ Profile.js
├─ ProfileHeader.js
└─ ProfileHeader.css
React Transition Group، React Spring و React Motion پکیجهای مشهور برای انیمیشن برای ریاکت هستن.
خیلی توصیه میشه که از استایلدهیهای سخت و مستقیم برای کامپوننتها پرهیز کنیم. هرمقداری که فقط در یک کامپوننت خاصی مورد استفاده قرار میگیره، بهتره که درون همون فایل لود بشه.
برای مثال، این استایلها میتونن تو یه فایل دیگه انتقال پیدا کنن:
export const colors = { white, black, blue, }; export const space = [0, 8, 16, 32, 64];
و توی موقعی که نیاز داریم از اون فایل مشخص لود کنیمشون:
import { space, colors } from "./styles";
ESLint یه linter برای JavaScript هستش. یه سری کتابخونه برای کمک به کدنویسی تو سبکهای مشخص و استاندارد برای eslint وجود داره. یکی از معروفترین پلاگینهای موجود eslint-plugin-react
هست.
به صورت پیشفرض این پلاگین یه سری از best practiceها رو برای کدهای نوشته شده بررسی میکنه و یه مجموعه از قوانین رو برای کدنویسی الزام میکنه. پلاگین مشهور دیگه eslint-plugin-jsx-a11y
هستش، که برای بررسی نکات و ملزومات معروف در زمینه accessibility کمک میکنه. چرا که JSX یه سینتکس متفاوتتری از HTML ارائه میکنه، مشکلاتی که ممکنه مثلا با alt
و tabindex
پیش میاد رو با این پلاگین میشه متوجه شد.
میتونیم از کتابخونههای AJAX مثل Axios یا حتی از fetch
که به صورت پیشفرض تو مرورگر وجود داره استفاده کنیم. لازمه که توی Mount
درخواست API رو انجام بدیم و برای به روز کردن کامپوننت میتونیم از setState
استفاده کنیم تا داده بدست اومده رو توی کامپوننت نشون بدیم.
برای مثال، لیست کارمندان از API گرفته میشه و توی state نگهداری میشه:
const MyComponent = () => { const [employees, setEmployees] = useState([]); const [error, setError] = useState(null); useEffect(() => { fetch("https://api.example.com/items") .then((res) => res.json()) .then( (result) => { setEmployees(result.employees); }, (error) => { setError(error); } ); }, []); return error ? ( <div>Error: {error.message}</div> ): ( <ul> {employees.map((employee) => ( <li key={employee.name}> {employee.name}-{employee.experience} </li> ))} </ul> ); };
همین کد روی کلاس کامپوننت به شکل زیر اجرا میشد:
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { employees: [], error: null, }; } componentDidMount() { fetch("https://api.example.com/items") .then((res) => res.json()) .then( (result) => { this.setState({ employees: result.employees, }); }, (error) => { this.setState({ error }); } ); } render() { const { error, employees } = this.state; if (error) { return <div>Error: {error.message}</div>; } else { return ( <ul> {employees.map((employee) => ( <li key={employee.name}> {employee.name}-{employee.experience} </li> ))} </ul> ); } } }
Render Props یه تکنیک ساده برای به اشتراک گذاری کامپوننت بین کامپوننتهای دیگهـست که با استفاده از یه prop که یه تابع یا یه کامپوننت رو بهش دادیم انجام میشه. کامپوننت زیر از همین روش برای پاس دادن یه React element استفاده میکنه و توی کامپوننت پایین این prop رو به شکل یه تابع فراخوانی میکنیم و چون یه تابع هست، میتونیم بهش هر مقداری که میخواییم بیاریم این سمت رو پاس بدیم.
<DataProvider render={(data) => <h1>{`Hello ${data.target}`}</h1>} />
کتابخونههایی مثل React Router و DownShift از این پترن استفاده میکنن.
React Router یه کتابخونه قدرتمند برای جابجایی سریع بین صفحات و flowهای مختلفه که برپایه ریاکت نوشته شده و امکان sync کردن آدرس وارد شده با صفحات رو توی محیطهای مختلف فراهم میکنه.
React Router یه wrapper روی کتابخونه history
هست که اعمال اجرایی بر روی window.history
رو با استفاده از ابجکتهای hash و browser مدیریت میکنه. البته این کتابخونه یک نوع دیگه از historyها به اسم memory history رو هم معرفی میکنه که برای محیطهایی که به صورت عمومی از history پشتیبانی نمیکنن کاربرد داره. مثل محیط توسعه برنامه موبایل با (React Native) یا محیطهای unit test و Nodejs.
React Router v4 سه نوع مختلف از کامپوننت رووتر(<Router>
) رو معرفیمیکنه:
<BrowserRouter>
<HashRouter>
<MemoryRouter>
کامپوننتهای فوق به ترتیب browser، hash، و memory history درست میکنن. React Router v4 ساخت history
رو براساس context ارائه شده به آبجکت router انجام میده و همین موضوعه که باعث میشه بتونیم از این کتابخونه توی محیطهای مختلف استفاده کنیم.
هر شئ از آبجکت history دو تا متد برای کار با state مرورگر ارائه میده.
push
replace
اگه به history به شکل یک آرایه از مسیرهای بازدید شده نگاه کنیم، push
یک جابجایی جدید به مسیر اضافه میکنه و replace
مسیر فعلی رو با یه مسیر جدید جایگزین میکنه.
روشهای مختلفی برای جابجایی در برنامه و توسط کد وجود داره که پایین لیست میکنیم، ولی روش آخر(استفاده از هوکها) بهترین و سادهترین روش توی کامپوننتهای تابعی هست.
استفاده از تابع مرتبه بالاتر(higher-order) withRouter
:
متد withRouter
آبجکت history رو به عنوان یه prop به کامپوننت اضافه میکنه. روی این prop به متدهای push
و replace
دسترسی داریم که بهسادگی میتونه مسیریابی بین routeها رو فراهم کنه و نیاز به context رو رفع کنه.
import { withRouter } from "react-router-dom"; // this also works with 'react-router-native' const Button = withRouter(({ history }) => ( <button type="button" onClick={() => { history.push("/new-location"); }} > {"Click Me!"} </button> ));
استفاده از کامپوننت <Route>
و پترن render props:
کامپوننت <Route>
همون prop که متد withRouter
به کامپوننت میده رو به کامپوننت میده.
import { Route } from "react-router-dom"; const Button = () => ( <Route render={({ history }) => ( <button type="button" onClick={() => { history.push("/new-location"); }} > {"Click Me!"} </button> )} /> );
استفاده از context:
استفاده از این مورد توصیه نمیشه و ممکنه به زودی deprecate شود.
const Button = (props, context) => ( <button type="button" onClick={() => { context.history.push("/new-location"); }} > {"Click Me!"} </button> ); Button.contextTypes = { history: React.PropTypes.shape({ push: React.PropTypes.func.isRequired, }), };
استفاده از هوکهای موجود:
هوکهایی برای دسترسی به history و params در این کتابخونه وجود داره مثل useHistory یا حتی توی نسخه ۶ به بعد هوک useNavigate که راحتتر میتونه امکان navigate بین صفحات رو فراهم کنه:
const Page = (props, context) => { const history = useHistory(); const location = useLocation(); const { slug } = useParams(); return ( <button type="button" onClick={() => { history.push("/new-location"); }} > {"Click Me!"} </button> ); };
نسخه ۶:
const Page = (props, context) => { const navigate = useNavigate(); return ( <button type="button" onClick={() => { navigate("/new-location"); }} > {"Click Me!"} </button> ); };
سادهترین راه برای دسترسی به paramهای آدرس استفاده از هوک useParams هست.
const { slug } = useParams(); console.log(`slug query param`, slug);
باید کامپوننت Route رو توی بلاک <Switch>
قرار بدیم چون همین کامپوننت <Switch>
چون Switch هست که باعث میشه منحصرا فقط یه route با مسیر فعلی تطابق پیدا کنه و کامپوننت اون route توی صفحه رندر بشه. اولش لازمه که Switch
رو import کنیم:
import { Switch, Router, Route } from "react-router";
بعدش routeها رو <Switch>
تعریف میکنیم:
<Router> <Switch> <Route {/*... */} /> <Route {/*... */} /> </Switch> </Router>
همونطوری که میدونیم موقع جابجایی میشه یه object به history
پاس بدیم که یه سری گزینهها رو برامون قابل کانفیگ میکنه:
this.props.history.push({ pathname: "/template", search: "?name=sudheer", state: { detail: response.data }, });
این کانفیگها یکیش search هست که میتونه پارامتر موردنظر ما رو به مسیر مورد نظر بفرسته.
کامپوننت <Switch>
اولین فرزند <Route>
ای که با درخواست موجود تطابق داشته باشه رو رندر میکنه. از اونجایی که یه <Route>
بدون path یا با path * همیشه مطابق با درخواستهاست، پس هنگام خطای ۴۰۴ این مورد برای رندر استفاده میشه.
<Switch> <Route exact path="/" component={Home} /> <Route path="/user" component={User} /> <Route component={NotFound} /> </Switch>
میتونیم یه ماژول درست کنیم که object history
رو میده و هرجایی خواستیم از این فایل استفاده کنیم.
برای مثال فایل history.js
رو ایجاد کنید:
import { createBrowserHistory } from "history"; export default createBrowserHistory({ /* pass a configuration object here if needed */ });
میتونیم از کامپوننت <Router>
بجای رووترهای پیشفرض استفاده کنیم. فایل history.js
بالا رو توی فایل index.js
لود میکنیم:
import { Router } from "react-router-dom"; import history from "./history"; import App from "./App"; ReactDOM.render( <Router history={history}> <App /> </Router>, holder );
البته میشه از متد push مثل آبجکت پیشفرض history استفاده کنیم:
// some-other-file.js import history from "./history"; history.push("/go-here");
نکته: روی نسخه ۶ دسترسی مستقیم به history حذف شده و برای هر کار یه هوک مختص به اون کار مهیا شده.
پکیج react-router
امکان استفاده از کامپوننت <Redirect>
رو توی React Router فراهم میکنه. رندر کردن <Redirect>
باعث جابجایی به مسیر پاس داده شده میشه. دقیقا مثل ریدایرکت سمت سرور، path مسیر جدید با path فعلی جایگزین میشه.
import React, { Component } from "react"; import { Redirect } from "react-router"; const Component = () => { if (isLoggedIn === true) { return <Redirect to="/your/redirect/page" />; } else { return <div>{"Login Please"}</div>; } }
React Intl یه کتابخونه برای آسان نمودن توسعه برنامههای چند زبانهـست. این کتابخونه از مجموعهای از کامپوننتها و APIها برای فرمتبندی رشتهها، تاریخ و اعداد رو برای سادهسازی فرآیند چندزبانگی فراهم میکنه. React Intl بخشی از FormatJS هست که امکان اتصال به ریاکت رو با کامپوننتهای خودش فراهم میکنه.
نمایش اعداد با جداکنندههای مشخص
نمایش تاریخ و ساعت با فرمت درست
نمایش تاریخ بر اساس زمان حال
امکان استفاده از لیبلها توی string
پشتیبانی از بیش از ۱۵۰ زبان
اجرا توی محیط مرورگر و node
دارا بودن استانداردهای داخلی
این کتابخونه از دو روش برای فرمتبندی رشتهها، اعداد و تاریخ استفاده میکنه: کامپوننتهای ریاکتی و API.
<FormattedMessage
id={"account"}
defaultMessage={"The amount is less than minimum balance."}
/>
const messages = defineMessages({ accountMessage: { id: "account", defaultMessage: "The amount is less than minimum balance.", }, }); formatMessage(messages.accountMessage);
کامپوننت <Formatted... />
از react-intl
بجای بازگرداندن string یه المنت برگشت میده و به همین دلیل نمیشه ازش به عنوان placeholder یا alt و... استفاده کرد. اگه جایی لازم شد یه پیامی رو اینجور جاها استفاده کنیم باید از formatMessage
استفاده کنیم. میتونیم شي intl
رو با استفاده از HOC injectIntl
به کامپوننت موردنظر inject کنیم و بعدشم میتونیم از متد formatMessage
روی این شي استفاده کنید.
import React from "react"; import { injectIntl, intlShape } from "react-intl"; const MyComponent = ({ intl }) => { const placeholder = intl.formatMessage({ id: "messageId" }); return <input placeholder={placeholder} />; }; MyComponent.propTypes = { intl: intlShape.isRequired, }; export default injectIntl(MyComponent);
میتونیم با استفاده از injectIntl
به locale فعلی رو دسترسی داشته باشیم:
import { injectIntl, intlShape } from "react-intl"; const MyComponent = ({ intl }) => ( <div>{`The current locale is ${intl.locale}`}</div> ); MyComponent.propTypes = { intl: intlShape.isRequired, }; export default injectIntl(MyComponent);
میتونیم با استفاده از HOC injectIntl
به متد formatDate
توی کامپوننت خودمون دسترسی داشته باشیم. این متد به صورت داخلی توسط FormattedDate
استفاده میشه و مقدار string تاریخ فرمت بندی شده رو برمیگردونه.
import { injectIntl, intlShape } from "react-intl"; const stringDate = this.props.intl.formatDate(date, { year: "numeric", month: "numeric", day: "numeric", }); const MyComponent = ({ intl }) => ( <div>{`The formatted date is ${stringDate}`}</div> ); MyComponent.propTypes = { intl: intlShape.isRequired, }; export default injectIntl(MyComponent);
Shallow rendering برای نوشتن یونیت تست توی ریاکت کاربرد داره. این روش بهمون این امکان رو میده که به عمق یک مرتبه کامپوننت موردنظرمون رو رندر کنیم و مقدار بازگردانی شده رو بدون اینکه نگران عملکرد کامپوننتهای فرزند باشیم، ارزیابی کنیم.
برای مثال، اگه کامپوننتی به شکل زیر داشته باشیم:
function MyComponent() { return ( <div> <span className={"heading"}>{"Title"}</span> <span className={"description"}>{"Description"}</span> </div> ); }
میتونیم انتظار اجرا به شکل پایین رو داشته باشیم:
import ShallowRenderer from "react-test-renderer/shallow"; // in your test const renderer = new ShallowRenderer(); renderer.render(<MyComponent />); const result = renderer.getRenderOutput(); expect(result.type).toBe("div"); expect(result.props.children).toEqual([ <span className={"heading"}>{"Title"}</span>, <span className={"description"}>{"Description"}</span>, ]);
این پکیج یه renderer معرفی میکنه که میتونیم ازش برای رندر کردن کامپوننتها و تبدیل اونا به یه آبجکت pure JavaScript استفاده کنیم بدون اینکه وابستگی به DOM یا محیط اجرایی موبایلی داشته باشیم. این پکیج برای گرفتن snapshot از سلسله مرتب view(یه چیزی شبیه به درخت DOM) که توسط ReactDOM یا React Native درست میشه رو بدون نیاز به مرورگر یا jsdom
فراهم میکنه.
import TestRenderer from "react-test-renderer"; const Link = ({ page, children }) => <a href={page}>{children}</a>; const testRenderer = TestRenderer.create( <Link page={"https://www.facebook.com/"}>{"Facebook"}</Link> ); console.log(testRenderer.toJSON()); // { // type: 'a', // props: { href: 'https://www.facebook.com/' }, // children: [ 'Facebook' ] // }
ReactTestUtils توی پکیج with-addons
ارائه شده و اجازه اجرای یه سری عملیات روی DOMهای شبیهسازی شده رو برای انجام یونیت تستها ارائه میده.
Jest یه فریمورک برای یونیت تست کردن جاواسکریپت هستش که توسط فیس بوک و براساس Jasmine ساخته شده. Jest امکان ایجاد اتوماتیک mock(دیتا یا مقدار ثابت برای تست) و محیط jsdom
رو فراهم میکنه.
یه سری برتریهایی نسبت بهJasmine داره:
میتونه به صورت اتوماتیک تستها رو توی سورس کد پیدا و اجرا کنه
به صورت اتوماتیک میتونه وابستگیهایی که داریم رو mock کنه
امکان تست کد asynchronous رو به شکل synchronously فراهم میکنه
تستها رو با استفاده از یه پیادهسازی مصنوعی از DOM(jsdom) اجرا میکنه و بواسطه اونه که تستها قابلیت اجرا روی cli رو دارن
تستها به شکل موازی و همزمان اجرا میشن و میتونن توی مدت زمان زودتری تموم شن
خب بیایین یه تست برای تابعی که جمع دو عدد رو توی فایل sum.js
برامون انجام میده بنویسیم:
const sum = (a, b) => a + b; export default sum;
یه فایل به اسم sum.test.js
ایحاد میکنیم که تستهامون رو توش بنویسیم:
import sum from "./sum"; test("adds 1 + 2 to equal 3", () => { expect(sum(1, 2)).toBe(3); });
و بعدش به فایل package.json
بخش پایین رو اضافه میکنیم:
{ "scripts": { "test": "jest" } }
در آخر، دستور yarn test
یا npm test
اجرا میکنیم و Jest نتیجه تست رو برامون چاپ میکنه:
$ yarn test
PASS./sum.test.js
✓ adds 1 + 2 to equal 3 (2ms)
Flux یه الگوی طراحی برنامهـست که به عنوان جایگزینی برای اکثر پترنهای MVC سنتی به کار میره. در حقیقت یه کتابخونه یا فریمورک نیست و یه معماری برای تکمیل کارکرد ریاکت با مفهوم جریان داده یک طرفه(Unidirectional Data Flow) به کار میره. فیسبوک از این پترن به شکل داخلی برای توسعه ریاکت بهره میگیره.
جریان کار بین dispatcher، storeها و viewهای کامپوننتها با ورودی و خروجی مشخص به شکل صفحه بعد خواهد بود:
Redux یه state manager(مدیریت کننده حالت) قابل پیشبینی برای برنامههای جاواسکریپتـه که برپایه دیزاین پترن Flux ایجاد شده. Redux میتونه با ریاکت یا هر کتابخونه دیگهای استفاده بشه. کم حجمه(حدود 2 کیلوبایت) و هیچ وابستگی به کتابخونه دیگهای نداره.
Redux از سه اصل بنیادی پیروی میکنه:
یک مرجع کامل و همواره درست: حالت موجود برا کل برنامه در یک درخت object و توی یه store نگهداری میشه. همین یکی بودن store باعث میشه دنبال کردن تغییرات در زمان توسعه و حتی دیباگ کردن برنامه سادهتر باشه.
State فقط قابل خواندن است: تنها روش ایجاد تغییر در store استفاده از action هستش و نتیجه اجرای این action یک object خواهد بود که رخداد پیش اومده رو توصیف میکنه. به این ترتیب مطمئن میشیم که تغییرات فقط با action انجام میشن و هر دیتایی توی store باشه توسط خودمون پر شده.
تغییرات با یه سری تابع pure انجام میشن: برای مشخص کردن نحوه انجام تغییرات در store باید reducer بنویسیم. Reducerها فقط یه سری توابع pure هستن که حالت قبلی و action رو به عنوان پارامتر میگیرن و حالت بعدی رو برگشت میدن.
بجای گفتن کاستیها بیایین مواردی که میدونیم موقع استفاده از Redux بجای Flux داریم رو بگیم:
باید یاد بگیریم که mutation انجام ندیم: Flux در مورد mutate کردن داده نظری نمیدهد، ولی Redux از mutate کردن داده جلوگیری میکنه و پکیجهای مکمل زیادی برای مطمئن شدن از mutate نشدن state توسط برنامهنویس ایجاد شدهاند. این مورد رو میشه فقط برای محیط توسعه با پکیجی مثل redux-immutable-state-invariant
، Immutable.js یا آموزش تیم برای نوشتن کد بدون mutate دیتا محقق کرد.
باید توی انتخاب پکیجها محتاطانه عمل کنید: Flux به شکل خاص کاری برای حل مشکلاتی مثل undo/redo، persist کردن داده یا مدیریت فرمها انجام نداده است. در عوض Redux کلی middleware و مکمل store برای محقق ساختن همچین نیازهای داره.
شاید هنوز یه جریان داده خوشگل نداشته باشه در حال حاضر Flux بهمون اجازه یه type check استاتیک خوب رو میده ولی Redux هنوز پشتیبانی خوبی نداره براش.
mapStateToProps
یه ابزار برای دریافت به روزشدنهای stateها توی کامپوننت هستش (که توسط یه کامپوننت دیگه به روز شده):
const mapStateToProps = (state) => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter), }; };
mapDispatchToProps
یه ابزار برای آوردن action برای فراخوانی تو کامپوننت ارائه میده (actionای که میخواییم dispatch کنیم و ممکنه state رو عوض کنه):
const mapDispatchToProps = (dispatch) => { return { onTodoClick: (id) => { dispatch(toggleTodo(id)); }, }; };
توصیه میشه که همیشه از روش “object shorthand” برای دسترسی به mapDispatchToProps
استفاده بشه
Redux این action رو توی یه تابع دیگه قرار میده که تقریبا میشه یه چیزی مثل (…args) => dispatch(onTodoClick(…args)) و تابعی که خودش به عنوان wrapper ساخته رو به کامپوننت مورد نظر ما میده.
const mapDispatchToProps = { onTodoClick, };
و البته هوکهای ریداکس برای دسترسی به state و انجام action مورد نظر هم خیلی کاربرد داره.
Dispatch کردن action توی reducer یه آنتی پترن محسوب میشه. reducer نباید هیچ سایدافکتی داشته باشه، فقط باید خیلی ساده state قبلی و action فعلی رو بگیره و state جدید رو بده. اینکار رو اگه با افزودن یه سری listeners و dispatch کردن با تغییرات reducer هم انجام بدیم باز باعث ایجاد actionهای تودرتو میشه و میتونه ساید افکت داشته باشه،
لازمه که store رو از یه ماژول که با createStore
ایجاد شده بارگذاری کنیم. البته حواسمون باشه برای انجام این مورد نباید اثری روی window به شکل global ایجاد کنیم.
const store = createStore(myReducer); export default store;
مدیریت DOM خیلی هزینهبر هست و میتونه باعث کندی و ناکارآمد شدن برنامه بشه.
بخاطر circular dependencies(وابستگی چرخشی) یه مدل پیچیده بین modelها و viewها ایجاد میشه.
بخاطر تعامل زیاد برنامه تغییرات خیلی زیادی رخ میده(مثل Google Docs).
روش ساده و بدون دردسری برای undo کردن(برگشت به عقب) نیست.
این دو کتابخونه خیلی متفاوتن و برای اهداف متفاوتی استفاده میشن، ولی یه سری تشابههای ریزی دارن.
Redux یه ابزار برای مدیریت state توی کل برنامهست. اکثرا هم به عنوان یه معماری برای ایجاد رابط کاربری استفاده میشه. RxJS یه کتابخونه برای برنامهنویسی reactive(کنش گرا) هستش. اکثرا هم برای انجام تسکهای asynchronous توی جاواسکریپت به کار میره. میتونیم بهش به عنوان یه معماری بجای Promise نگاه کنیم. Redux هم از الگوی Reactive استفاده میکنه چون Store ریداکس reactive هستش. Store میاد actionها رو از دور میبینه و تغییرات لازم رو توی خودش ایجاد میکنه. RxJS هم از الگوی Reactive پیروی میکنه، ولی بجای اینکه خودش این architecture رو بسازه میاد به شما یه سری بلاکهای سازنده به اسمObservable میده که باهاش بتونید الگوی reactive رو اجرا کنید.
فهرست
خیلی ساده میشه اون action رو موقع mount
اجرا کرد و موقع render
دیتای مورد نیاز رو داشت.
const App = (props) => { useEffect(() => { props.fetchData(); }, []); return props.isLoaded ? ( <div>{"Loaded"}</div> ): ( <div>{"Not Loaded"}</div> ); }; const mapStateToProps = (state) => ({ isLoaded: state.isLoaded, }); const mapDispatchToProps = { fetchData }; export default connect(mapStateToProps, mapDispatchToProps)(App);
برای دسترسی به دیتای نگهداری شده توی ریداکس باید دو گام زیر رو طی کنیم:
از متد mapStateToProps
استفاده میکنیم و متغیرهای state که از store میخواییم لود کنیم رو مشخص میکنیم.
با استفاده از متد connect دیتا رو به props میدیم، چون دیتایی که این HOC میاره به عنوان props به کامپوننت داده میشه. متد connect
رو هم از پکیج react-redux
باید بارگذاری کنیم.
import React from 'react'; import { connect } from 'react-redux'; const App = props => { render() { return <div>{props.containerData}</div> } }; const mapStateToProps = state => { return { containerData: state.data } }; export default connect(mapStateToProps)(App);
لازمه که توی برنامه یه root reducer تعریف کنیم که وظیفه معرفی ریدیوسرهای ایجاد شده با combineReducers
را دارد.
مثلا بیایین rootReducer
رو برای ست کردن state اولیه با فراخوانی عمل USER_LOGOUT
تنظیم کنیم. همونطوری که میدونیم، به صورت پیشفرض ما بنا رو براین میزاریم که reducerها با اجرای مقدار undefined
به عنوان پارامتر اول initialState رو برمیگردونن و حتی actionش هم مهم نیست.
const appReducer = combineReducers({ /* your app's top-level reducers */ }); const rootReducer = (state, action) => { if (action.type === "USER_LOGOUT") { state = undefined; } return appReducer(state, action); };
اگه از پکیج redux-persist
استفاده میکنین، احتمالا لازمه که storage رو هم خالی کنین. redux-persist
یه کپی از دیتای موجود در store رو توی localstorage نگهداری میکنه. اولش، لازمه که یه موتور مناسب برای storage بارگذاری کنیم که برای تجزیه state قبل مقداردهی اون با undefined و پاک کردن مقدارشون مورد استفاده قرار میگیره.
const appReducer = combineReducers({ /* your app's top-level reducers */ }); const rootReducer = (state, action) => { if (action.type === "USER_LOGOUT") { Object.keys(state).forEach((key) => { storage.removeItem(`persist:${key}`); }); state = undefined; } return appReducer(state, action); };
کاراکتر(symbol) @ در حقیقت یه نماد از جاواسکریپت برای مشخص کردن decoratorهاست. _Decorator_ها این امکان رو بهمون میده که بتونیم برای کلاس و ویژگیهای(properties) اون یادداشتها و مدیریتکنندههایی رو توی زمان طراحی اضافه کنیم.
بزارین یه مثال رو برای Redux بزنیم که یه بار از decorator استفاده کنیم و یه بار بدون اون انجامش بدیم.
بدون decorator:
import React from "react"; import * as actionCreators from "./actionCreators"; import { bindActionCreators } from "redux"; import { connect } from "react-redux"; function mapStateToProps(state) { return { todos: state.todos }; } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators(actionCreators, dispatch) }; } class MyApp extends React.Component { //...define your main app here } export default connect(mapStateToProps, mapDispatchToProps)(MyApp);
با decorator:
import React from "react"; import * as actionCreators from "./actionCreators"; import { bindActionCreators } from "redux"; import { connect } from "react-redux"; function mapStateToProps(state) { return { todos: state.todos }; } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators(actionCreators, dispatch) }; } @connect(mapStateToProps, mapDispatchToProps) export default class MyApp extends React.Component { //...define your main app here }
مثالهای بالا تقریبا شبیه به هم هستن فقط یکیشون از decoratorها استفاده میکنه و اون یکی حالت عادیه. سینتکس decorator هنوز به صورت پیشفرض توی هیچکدوم از runtimeهای جاواسکریپت فعلا وجود نداره و هنوز به شکل آزمایشی مورد استفاده قرار میگیره ولی پروپوزال افزوده شدنش به زبان در دست بررسیه. خوشبختانه فعلا میتونیم از babel برای استفاده از اون استفاده کنیم.
میتونیم از Context برای استفاده از state توی مراحل داخلی کامپوننتهای nested استفاده کنیم و پارامترهای مورد نظرمون رو تا هر عمقی که دلخواهمون هست ببریم و استفاده کنیم، که البته context برای همین امر به وجود اومده. این درحالیه که Redux خیلی قدرتمندتره، پلاگینهای مختلفی داره و یه سری قابلیتهای حرفهایتری رو بهمون میده. بعلاوه، خود React Redux به شکل داخلی از context استفاده میکنه ولی به شکل عمومی این موضوع دیده نمیشه.
Reducerها همیشه یه مجموعه از stateها رو جمعآوری و تحویل میدن(براساس همه actionهای قبلی). برای همین، اونا به عنوان یه سری کاهندههای state عمل میکنن. هر وقت یه reducer از Redux فراخوانی میشه، state و action به عنوان پارامتر پاس داده میشن و بعدش این state بر اساس actionجاری مقادیرش کاهش یا افزایش داده میشوند و بعدش state بعدی برگشت داده میشه. یعنی شما میتونین یه مجموعه از دادهها رو reduce کنین و به state نهایی که دلخواهتون هست برسین.
میشه از middleware(میانافزار) redux-thunk
استفاده کرد که اجازه میده بتونیم actionهای async داشته باشیم.
بزارین یه مثال از دریافت اطلاعات یه حساب خاص با استفاده از فراخوانی AJAX با استفاده از fetch API بزنیم:
export function fetchAccount(id) { return (dispatch) => { dispatch(setLoadingAccountState()); // Show a loading spinner fetch(`/account/${id}`, (response) => { dispatch(doneFetchingAccount()); // Hide loading spinner if (response.status === 200) { dispatch(setAccount(response.json)); // Use a normal function to set the received state } else { dispatch(someError); } }); }; } function setAccount(data) { return { type: "SET_Account", data: data }; }
نه لزومی نداره، دیتاهای عمومی برنامه رو میشه توی store ریداکس نگهداری کرد و مسائل مربوط به UI به شکل داخلی توی state کامپوننتها نگهداری بشن.
بهترین روش، بسته به هر پروژه و هر فرد میتونه متفاوت باشه، ترجیح من استفاده از هوکهای useSelector و useDispatch هستن، برای دسترسی به store و انجام عملیات روی اون استفاده از تابع connect
هم میتونیم استفاده کنیم که یه کامپوننت جدید ایجاد میکنه که کامپوننت جاری توی اون قرار داره و دیتای لازم رو بهش پاس میده. این پترن با عنوان Higher-Order Components یا کامپوننتهای مرتبه بالاتر شناخته میشه و یه روش مورد استفاده برای extend کردن کارکرد کامپوننتهای ریاکتی محسوب میشه. این تابع بهمون این امکان رو میده که state و actionهای مورد نظرمون رو به داخل کامپوننت بیاریم و البته به شکل پیوسته با تغییرات اونا کامپوننتمون رو به روز کنیم.
بیایین یه مثال از کامپوننت <FilterLink>
با استفاده از تابع connect بزنیم:
import { connect } from "react-redux"; import { setVisibilityFilter } from "../actions"; import Link from "../components/Link"; const mapStateToProps = (state, ownProps) => ({ active: ownProps.filter === state.visibilityFilter, }); const mapDispatchToProps = (dispatch, ownProps) => ({ onClick: () => dispatch(setVisibilityFilter(ownProps.filter)), }); const FilterLink = connect(mapStateToProps, mapDispatchToProps)(Link); export default FilterLink;
Component یه کامپوننت class یا function هست که لایه ظاهری و مربوط به UI برنامهمون توی اون قرار میگیره.
Container یه اصطلاح غیررسمی برای کامپوننتهاییـه که به store ریداکس وصل شدن. Containerها به state subscribe میکنن یا actionها رو dispatch میکنن و هیچ DOM elementای رو رندر نمیکنن بلکه کامپوننتهای UI رو به عنوان child به روز میکنن.
نکته مهم: استفاده از این روش تقریبا توی سال ۲۰۱۹ دیگه منقضی محسوب میشه و چون هوکهای ریاکت خیلی راحت میتونن دیتا رو توی هر سطح از کامپوننت برامون لود کنن، پس جدا نشدن این دولایه تاثیر چشمگیری توی ساده بودن کدها نخواهد داشت و بعضا حتی میتونه کار رو سختتر کنه، پس به عنوان مترجم توصیه میکنم این کار رو انجام ندین:)
Constantها یا موارد ثابت بهتون این اجازه رو میدن که کارکرد یه عملکرد مشخص رو به سادگی توی پروژه پیدا کنید. البته از خطاهای سادهای که ممکنه براتون پیش بیاد هم جلوگیری میکنه. مثل خطاهای مربوط به type یا ReferenceError
ها که ممکنه خیلی راحت رخ بدن.
اکثرا مقادیر ثابت constant رو توی یه فایل مثل (constants.js
یا actionTypes.js
) قرار میدیم.
export const ADD_TODO = "ADD_TODO"; export const DELETE_TODO = "DELETE_TODO"; export const EDIT_TODO = "EDIT_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; export const COMPLETE_ALL = "COMPLETE_ALL"; export const CLEAR_COMPLETED = "CLEAR_COMPLETED";
توی ریداکس از این مقادیر دوتا جا استفاده میشه:
موقع ساخت action:
مثلا فرض میکنیم actions.js
:
import { ADD_TODO } from "./actionTypes"; export function addTodo(text) { return { type: ADD_TODO, text }; }
توی reducerها:
مثلا یه فایل به اسم reducer.js
رو در نظر بگیرین:
import { ADD_TODO } from "./actionTypes"; export default (state = [], action) => { switch (action.type) { case ADD_TODO: return [ ...state, { text: action.text, completed: false, }, ]; default: return state; } };
چندین روش برای bind کردن action به متد dispatch
توی mapDispatchToProps
هستش که پایین بررسیشون میکنیم:
const mapDispatchToProps = (dispatch) => ({ action: () => dispatch(action()), }); export default connect(mapStateToProps, mapDispatchToProps)(App);
const mapDispatchToProps = (dispatch) => ({ action: bindActionCreators(action, dispatch), }); export default connect(mapStateToProps, mapDispatchToProps)(App);
const mapDispatchToProps = { action }; export default connect(mapStateToProps, mapDispatchToProps)(App);
روش سوم خلاصه شده روش اوله که معمولا توصیه میشه.
اگه پارامتر ownProps
ارائه شده باشه، ReactRedux پارامترهایی که به کامپوننت پاس داده شدن رو به تابع connect پاس میده. پس اگه یه کامپوننت connect شده مثل کد زیر داشته باشین:
import ConnectedComponent from "./containers/ConnectedComponent"; <ConnectedComponent user={"john"} />;
پارامتر ownProps
توی mapStateToProps
و mapDispatchToProps
یه object رو خواهد داشت که مقدار زیر رو داره:
{ user: "john"; }
میتونیم از این مقدار استفاده کنیم تا در مورد مقدار بازگشتی تصمیم بگیریم.
اکثر برنامههای ریداکسی یه ساختاری مثل این دارند:
Components: که برای کامپوننتهای dumb یا فقط نمایشی که به ریداکس وصل نیستند استفاده میشود.
Containers: که برای کامپوننتهای smart که به ریداکس وصل هستن.
Actions: که برای همه actionها استفاده میشه و هر فایل به بخشی از عملکرد برنامه تعلق داره.
Reducers: که برای همه reducerها استفاده میشه و هر فایل به یه state توی store تعلق داره.
Store: که برای ساختن store استفاده میشه.
این ساختار برای یه برنامه کوچک تا بزرگ کاربرد داره. البته اون بخشی ازش که کامپوننتهای dumb و smart یا همون container و component رو بر طبق وصل شدنشون به ریداکس جدا میکردیم تقریبا منقصی محسوب میشه.
redux-saga
یه کتابخونه هست که تمرکز اصلیش برای ایجاد side-effectهاست (چیزهای asynchronous مثل fetch کردن داده و غیرشفاف مثل دسترسی به کش مرورگر) که توی برنامههای React/Redux با این روش سادهتر و بهتر انجام میشه.
پکیج ریداکس ساگا روی NPM هست:
$ npm install --save redux-saga
Saga مثل یه thread جداگانه برای برنامه عمل میکنه و فقط برای مدیریت ساید افکت کارایی داره. redux-saga
یه میانافزار(middlewaer) برای ریداکسـه، که به معنی اینه که میتونه به صورت اتوماتیک توسط actionهای ریداکس شروع بشه، متوقف بشه و یا کار خاصی انجام بده. این میانافزار به کل store ریداکس و actionهایی که کار میکنن دسترسی داره و میتونه هر action دیگهای رو dispatch کنه.
هر دوی افکتهای call
و put
سازندههای افکت هستن. تابع call
برای ایجاد توضیح افکت استفاده میشه که به میانافزار دستور میده منتظر call بمونه. تابع put
یه افکت ایجاد میکنه، که به store میگه یه action خاص رو فقط اجرا کنه.
بزارین یه مثال در مورد عملکرد این دوتا افکت برای دریافت داده یه کاربر بزنیم.
function* fetchUserSaga(action) { // `call` function accepts rest arguments, which will be passed to `api.fetchUser` function. // Instructing middleware to call promise, it resolved value will be assigned to `userData` variable const userData = yield call(api.fetchUser, action.userId); // Instructing middleware to dispatch corresponding action. yield put({ type: "FETCH_USER_SUCCESS", userData, }); }
میان افزار Redux Thunk بهمون این اجازه رو میده که actionهایی رو بسازیم که بهجای action عادی تابع برگردونن thunk میتونه به عنوان یه ایجاد کننده delay برای dispatch کردن یه action استفاده کنیم، یا حتی با بررسی یه شرط خاص یه action رو dispatch کنیم. تابعی که توی action استفاده میشه و dispatch
و getState
رو به عنوان پارامتر ورودی میگیره.
هر دوی ReduxThunk و ReduxSaga میتونن مدیریت ساید افکتها رو به دست بگیرن. توی اکثر سناریوها، Thunk از Promise استفاده میکنه، درحالیکه Saga از Generatorها استفادهمیکنه. Thunk تقریبا سادهتره و promise رو تقریبا همه دولوپرها باهاش آشنا هستن، در حالیکه Sagas/Generatorها خیلی قویتر هستن و میتونن کاربردیتر باشن ولی خب لازمه که یاد بگیرینش. هردوی میانافزارها میتونن خیلی مفید باشن و شما میتونین با Thunks شروع کنین و اگه جایی دیدین نیازمندیتون رو برآورده نمیکنه سراغ Sagas برید.
ReduxDevTools یه محیط برای مشاهده در لحظه تغییرات ریداکس فراهم میکنه و قابلیت اجرای مجدد action و یه رابط کاربری قابل شخصیسازی رو فراهم میکنه. اگه نمیخوایین پکیج ReduxDevTools رو نصب کنید میتونین از افزونه ReduxDevTools برای Chrome و Firefox استفاده کنین.
بهتون اجازه میده که اطلاعات هر state و payload پاس داده شده به action رو مشاهده کنین.
بهتون اجازه میده که actionهای اجرا شده رو لغو کنید.
اگه یه تغییری روی کدهای reducer بدین، هر actionای که stage شده رو مجدد ارزیابی میکنه.
اگه یه reducers یه خطایی بده، میشه متوجه شد که در طی انجام شدن کدوم action این اتفاق افتاده و خطا چی بوده.
با persistState
میتونین دیباگ روی موقع reloadهای مختلف ذخیره کنید.
Selectorها یه سری تابع هستن که state ریداکس رو به عنوان یه پارامتر دریافت میکنه و یه بخش از اون state که میخواییم رو برگشت میده.
برای مثال، دریافت اطلاعات کاربر از ریداکس با یه selector مث این میتونه فراهم شده باشه:
const getUserData = (state) => state.user.data;
ReduxForm در کنار ریاکت و ریداکس کار میکنه تا اطلاعات فرمها رو توی state ریداکس مدیریت کنیم. ReduxForm میتونه با inputهای خام HTML5 هم کار کنه، ولی با فریمورکهای معروف UI مثل Material، ReactWidgets و ReactBootstrap کار کنه.
ماندگاری مقادیر فیلدهای فرم توی ریداکس.
اعتبارسنجی (sync/async) و ثبت فرم.
فرمت کردن، تجزینه و نرمالسازی مقادیر فیلدها.
میتونیم از applyMiddleware
استفاده کنیم.
برای مثال میشه از redux-thunk
و logger
به عنوان پارامترهای applyMiddleware
استفاده کنیم:
import { createStore, applyMiddleware } from "redux"; const createStoreWithMiddleware = applyMiddleware( ReduxThunk, logger )(createStore);
لازم داریم که state اولیه رو به عنوان پارامتر دوم به createStore پاس بدیم:
const rootReducer = combineReducers({ todos: todos, visibilityFilter: visibilityFilter, }); const initialState = { todos: [{ id: 123, name: "example", completed: false }], }; const store = createStore(rootReducer, initialState);
Relay و Redux توی این مورد که دوتاشونم از یه store استفاده میکنن شبیه بهم هستن. تفاوت اصلی این دو اینه که relay فقط stateهایی رو مدیریت میکنه که از سرور تاثیر گرفتن و همه دسترسیهایی که به state مربوطه رو با کوئریهای GraphQL(برای خوندن دادهها) و mutationها (برای تغییرات داده) انجام میده. Relay دادهها برای شما رو cache میکنه و گرفتن داده از سرور رو برای شما بهینه میکنه. چون فقط تغییرات رو دریافت میکرد و نه چیز دیگهای.
React یه کتابخونه جاواسکریپتی هست که از اجرای اون روی frontend و اجرای اون روی سرور برای تولید رابط کاربری و برنامههای تحت وب پشتیبانی میکنه.
React Native یه فریمورک موبایل هست که کدها رو به کامپوننتهای native روی موبایل compile میکنه و بهمون این اجازه رو میده که برنامههای موبایلی(iOS, Android, and Windows) رو با استفاده از جاواسکریپت بسازیم که از ریاکت برای تولید کامپوننت استفاده میکنه.
ReactNative میتونه توی شبیهسازهای سیستمعاملهای موبایلی مثل iOS و Android تست کرد. میتونیم برنامههای خودمون رو توی برنامه expo(https://expo.io) توی گوشی خودمون هم ببینیم که با استفاده از QR-code میتونه یه برنامه روی کامپیوتر و گوشی sync کنه، البته باید هر دوی این دستگاهها تو یه شبکه وایرلس باشه.
میتونیم از console.log
، console.warn
و غیره استفاده کرد. از نسخه ReactNative 0.29 میتونیم خیلی ساده کدهای زیر رو اجرا کنیم که لاگ رو توی خروجی ببینیم:
$ react-native log-ios
$ react-native log-android
برای دیباگ کردن برنامه ریاکت native گامهای زیر رو طی میکنیم:
برنامه رو توی شبیهساز iOS اجرا میکنیم.
دکمههای Command + D
رو فشار میدیم و یه صفحه وب توی آدرس http://localhost:8081/debugger-ui
اجرا میشه.
چکباکس On Caught Exceptions_ رو برای یه دیباگ بهتر فعال میکنیم.
دکمههای Command + Option + I
رو برای اجرای developer-tools کروم فشار میدیم یا از طریق منوهای View
و Developer
و DeveloperTools
باز میکنیمـش.
حالا میتونیم برنامه مورد نظر خودمون رو به راحتی تست کنیم.
Reselect یه کتابخونه کمکی برای selectorهای ریداکسـه که از مفهوم memoization استفاده میکنه. این کتابخونه به شکلی نوشته شده بوده که دادههای هر برنامه Redux-like یا شبیه ریداکس رو پردازش کنه، ولی نتونسته با هیچ برنامه یا کتابخونه دیگهای گره بخوره.
Reselect یه کپی از آخرین inputs/outputs از هر فراخوانی رو نگهداری میکنه و فقط زمانی اونو دوباره محاسبه میکنه که تغییراتی توی ورودی رخ داده باشه. اگه همون ورودیها دوبار استفاده بشن، Reselect مقدار cache شده رو برمیگردونه. memoization و cacheای که استفاده میشه تا حد زیادی قابل شخصی سازیه.
Flow یه static type checker هستش که طراحی شده تا خطاهای مربوط به نوع دادهها رو توی جاواسکریپت پیدا کنیم. نوعهای flow میتونه خیلی ریزبینانهتر از رویکردهای سنتی بررسی نوع عمل کنه. برای مثال، Flow بهمون کمک میکنه که خطاهای مربوط به دریافت null
توی برنامه رو کنترل کنیم که توی روشهای سنتی غیرممکنه تقریبا.
Flow یه ابزار تجزیه و تحلیل استاتیک(static-checker) هستش که از یه سری ویژگیهای بیشتر از زبان جاواسکریپت رو پشتیبانی میکنه و بهمون کمک میکنه که در بخشهای مختلف برنامه نوع دادهها رو اضافه کنیم و خطاهایی که مرتبط با بررسی نوعها هست رو موقع compile ازشون جلوگیری کنیم. PropTypeها یه روش بررسی نوع داده ورودی کامپوننتهای ساده (موقع runtime) هست که روی ریاکت اضافه شدن. PropType به غیر از نوع دادههایی که به کامپوننت موردنظر به عنوان prop داده شده رو نمیتونه بررسی کنه. پس اگه دنبال یه روش برای بررسی نوع داده به شکل منعطف هستیم که توی کل پروژه عمل کنه Flow یا TypeScript روشهای بهتری هستن.
به شکل کلی، باید css و فونت آیکون مربوط به font-awesome به پروژه اضافه بشه، میتونیم از پکیج این کتابخونه روی npm استفاده کنیم و بگیم که باید گامهای زیر برای استفاده از font-awesome توی ریاکت باید طی بشه:
پکیج font-awesome
رو نصب میکنیم:
npm install --save font-awesome
font-awesome
رو توی فایل index.js
بارگذاری میکنیم:
import "font-awesome/css/font-awesome.min.css";
از کلاس این فونت توی className
های موردنظر استفاده میکنیم:
render() { return <div><i className={'fa fa-spinner'} /></div> }
ReactDeveloperTools بهمون اجازه اینو میده که سلسله مراتب کامپوننتهای برنامه رو بررسی کنیم که شامل prop و state هم میشه. این مورد به دو روش افزونه (برای Chrome و Firefox) و یه برنامه جانبی مستقل (که با سافاری و مرورگرهای دیگه هم کار میکنه) در دسترسه.
پس سه مورد رو میتونیم در نظر بگیریم:
افزونه Chrome
افزونه Firefox
برنامه مستقل (Safari ،ReactNative و...)
اگه یه فایل محلی HTML رو توی مرورگر باز کنیم (file://...
) بعدش لازمه که ChromeExtensions یا همون افزونههای کروم رو باز کنیم و چکباکس Allow access to file URLs
رو فعال کنیم.
یه element برای Polymer ایجاد میکنیم:
<link rel="import" href="../../bower_components/polymer/polymer.html" />; Polymer({ is: "calender-element", ready: function () { this.textContent = "I am a calender"; }, });
کامپوننت Polymer رو با تگهای HTML ایجاد میکنیم و توی داکیومنت html بارگذاری میکنیم، برای مثال اونو توی index.html
برنامه بارگذاری کنیم:
<link
rel="import"
href="./src/polymer-components/calender-element.html"
/>
از اون element توی فایل JSX استفاده میکنیم:
import React from "react"; class MyComponent extends React.Component { render() { return <calender-element />; } } export default MyComponent;
ریاکت مزایای زیر رو نسبت به Vue.js داره:
انعطاف پذیری بیشتری رو توی توسعه برنامههای بزرگ بهمون میده.
تست کردنش راحتتره.
برای تولید برنامههای موبایلی هم مناسبه.
اطلاعات و راهکارهای مختلفی براش توی دسترسه.
نکته: لیست موارد فوق صرفاً اظهار نظر شخصی بوده و براساس تجربه حرفهای ممکن است متفاوت باشد. اما به عنوان پارامترهای پایه مفید هستن
React | Angular |
---|---|
ریاکت یه کتابخونهست و فقط یه لایه view داره | Angular یه فریم ورکه و عملکردش کاملا MVC هستش |
در ریاکت جریان دادهها فقط از یه طریق(one-directional) هستش و به همین خاطر اشکال زدایی(debug) راحت تره | در Angular جریان دادهها از دو جهته، یعنی اتصال دادههای دوطرفه بین والدین و فرزندان رو داره و به خاطر همین اشکال زدایی سخت تره |
نکته: لیست موارد فوق صرفاً اظهار نظر شخصی بوده و براساس تجربه حرفهای ممکن است متفاوت باشد. اما به عنوان پارامترهای پایه مفید هستند.
فهرست
موقع لود صفحه، React DevTools یه گلوبال به اسم __REACT_DEVTOOLS_GLOBAL_HOOK__
تنظیم میکنه، بعدش ریاکت موقع مقداردهی اولیه با اون هوک ارتباط برقرار میکنه. اگه وب سایت از ریاکت استفاده نکنه یا ریاکت نتونه با DevTools ارتباط برقرار کنه اون تب رو نشون نمیده.
styled-components
یه کتابخونه جاواسکریپتـه برای طراحی ظاهر برنامههای ریاکت، پیچیدگی بین استایلها و کامپوننتها رو حذف میکنه و بهمون این امکان رو میده که کامپوننتهایی رو تولید کنیم که نگران استایلشون نیستیم و خیالمون راحته که استایلشون کنار خودشون منتقل میشن و css واقعی رو با جاواسکریپت بنویسیم.
بیاین کامپوننتهای <Title>
و <Wrapper>
رو با استایلهای خاص برای هر کدوم بسازیم.
import React from 'react' import styled from 'styled-components' // Create a <Title> component that renders an <h1> which is centered, red and sized at 1.5em const Title = styled.h1`` // Create a <Wrapper> component that renders a <section> with some padding and a papayawhip background const Wrapper = styled.section``
این دو تا متغیر، Title
و Wrapper
، کامپوننتهایی هستن که میتونیم مثل هر کامپوننت دیگه ای رندرشون کنیم.
<Wrapper> <Title>Lets start first styled component!</Title> </Wrapper>
Relay یه فریم ورک جاواسکریپتـه که برای ارائه یک لایه داده و ارتباط client-server به برنامههای وب با استفاده از لایه view ریاکت استفاده میشه.
از نسخه react-scripts@2.1.0 به بالاتر، پشتیبانی به شکل داخلی برای typescript وجود داره. میتونیم پارامتر --typescript
رو به صورت زیر به این اسکریپت پاس بدیم:
npx create-react-app my-app --typescript # or yarn create react-app my-app --typescript
ولی برای ورژنهای پایینتر وقتی داریم یه پروژه جدید می سازیم react scripts، گزینه --scripts-version
رو به عنوان react-scripts-ts
تنظیم میکنیم. react-scripts-ts
مجموعه ای از تنظیمات برای گرفتن پروژه create-react-app
و آوردن typeScript داخلش هست.
حالا ساختار پروژه باید این شکلی باشه:
my-app/
├─.gitignore
├─ images.d.ts
├─ node_modules/
├─ public/
├─ src/
│ └─...
├─ package.json
├─ tsconfig.json
├─ tsconfig.prod.json
├─ tsconfig.test.json
└─ tslint.json
Selectorها دادههای مشتق شده رو محاسبه میکنه و به ریداکس اجازه میدن حداقل stateهای ممکن رو ذخیره کنه.
Selectorها memoize شده هستن و یه selector تا وقتی که یکی از آرگومانهاش تغییر نکرده معتبر نیست.
Selectorها قابل ترکیب هستن یعنی میتونن به عنوان ورودی برای بقیه Selectorها استفاده بشن.
بیاین محاسبات و مقادیر مختلف یه سفارش حمل و نقل رو با استفاده ساده از Reselect انجام بدیم:
import { createSelector } from 'reselect' const shopItemsSelector = state => state.shop.items const taxPercentSelector = state => state.shop.taxPercent const subtotalSelector = createSelector( shopItemsSelector, items => items.reduce((acc, item) => acc + item.value, 0) ) const taxSelector = createSelector( subtotalSelector, taxPercentSelector, (subtotal, taxPercent) => subtotal * (taxPercent / 100) );
export const totalSelector = createSelector( subtotalSelector, taxSelector, (subtotal, tax) => ({ total: subtotal + tax }) ) let exampleState = { shop: { taxPercent: 8, items: [ { name: 'apple', value: 1.20 }, { name: 'orange', value: 0.95 }, ] } } console.log(subtotalSelector(exampleState)) // 2.15 console.log(taxSelector(exampleState)) // 0.172 console.log(totalSelector(exampleState)) // { total: 2.322 }
اکشنها آبجکتهای ساده جاواسکریپت یا اطلاعاتی هستن که دادهها رو از برنامه به store میفرستن. اونا تنها منابع اطلاعاتی برای store هستن. اکشن باید یه ویژگی type داشته باشه که نوع اکشنای که انجام میشه رو نشون بده.
برای مثال اکشنای که نشون میده یه آیتم todo جدید اضافه شده، میتونه این شکلی باشه:
{ type: 'ADD_TODO', text: 'Add todo item' }
البته یه استانداردی هست که برای دادهای که میخواییم منتقل کنیم اسم متغیر انتخاب نکنیم و از ویژگی payload براش استفاده کنیم، مثال فوق با این استاندارد به شکل زیر میتونه پیادهسازی بشه:
{ type: 'ADD_TODO', payload: 'Add todo item' }
خیر، استاتیکها فقط با React.createClass
کار میکنن:
someComponent= React.createClass({ statics: { someMethod: function() { //.. } } })
اما میتونیم استاتیکها رو داخل کلاسهای ES6 یا خارج از کلاس مثل زیر بنویسیم،
class Component extends React.Component { static propTypes = { //... } static someMethod() { //... } }
class Component extends React.Component { .... } Component.propTypes = {...} Component.someMethod = function(){....}
ریداکس میتونه به عنوان یه محل برای ذخیره داده برای لایه UI استفاده بشه. رایج ترین کاربرد ریداکس برای ریاکت و ریاکت نیتیو هستش، ولی یه سری کارهایی هم برای هماهنگ کردنش با Angular، Angular 2، Vue، Mithril و موارد دیگه موجوده. ریداکس به راحتی یه مکانیسم اشتراکی ارائه میده که میتونه برای کدهای دیگه هم استفاده بشه.
ریداکس در اصل توی ES6 نوشته شده و برای build روی ES5 با Webpack و Babel کار کردن، در حقیقت ما باید بتونیم بدون توجه به مراحل و نسخه جاواسکریپت ازش استفاده کنیم. ریداکس همینطور یه ساختار UMD ارائه میده که میتونه مستقیم و بدون هیچگونه وابستگی به شکل مستقیم روی مرورگر مورد استفاده قرار بگیره.
باید تنظیمات enableReinitialize: true
رو اضافه کنیم.
const InitializeFromStateForm = reduxForm({ form: 'initializeFromState', enableReinitialize: true })(UserEdit)
اگه prop initialValues
به روز بشه، فرممون هم به روز میشه.
میتونیم از یکی از متدهای PropTypes
به اسم oneOfType
استفاده کنیم.
برای مثال، ویژگی height رو میتونیم با دو نوع string
یا number
مثل زیر تعریف کنیم:
Component.PropTypes = { size: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]) }
میتونیم SVG رو مستقیما به عنوان یه کامپوننت به جای لود کردنش به عنوان یه فایل ایمپورت کنیم. این ویژگی توی react-scripts@2.0.0
و ورژنهای بالاتر در دسترسه.
import { ReactComponent as Logo } from './logo.svg' const App = () => ( <div> {/* Logo is an actual react component */} <Logo /> </div> )
نکته فراموش نکنیم که موقع ایمپورت کردن از آکولاد استفاده کنیم.
اگه ref callback به عنوان یه تابع درون خطی تعریف بشه، در طول به روزرسانی دو بار فراخوانی میشه، یه بار با مقدار null و بعد دوباره با عنصر DOM. این موضوع به خاطر اینه که یه نمونه جدیدی از تابع با هر بار رندر ساخته میشه، پس ریاکت باید ref قبلی رو پاک کنه و یه نمونه جدید ایجاد کنه.
class UserForm extends Component { handleSubmit = () => { console.log("Input Value is: ", this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' ref={(input) => this.input = input} /> // Access DOM input in handle submit <button type='submit'>Submit</button> </form> ) } }
اما انتظار ما اینه که وقتی کامپوننت mount شد، ref callback یه بار صدا زده بشه. یه راه حل سریع استفاده از class property syntax ES6 برای تعریف تابع هستش.
class UserForm extends Component { handleSubmit = () => { console.log("Input Value is: ", this.input.value) } setSearchInput = (input) => { this.input = input } render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' ref={this.setSearchInput} /> // Access DOM input in handle submit <button type='submit'>Submit</button> </form> ) } }
مفهوم render hijacking به معنی توانایی کنترل اینکه چه کامپوننتی خروجی بقیه رندر شدن یه کامپوننت دیگه باشه هست. در واقع ما میتونیم با قرار دادن کامپوننت خودمون توی یه کامپوننت با اولویت بالا(HOC) یه تغییراتی بهش بدیم، مثلا یه سری prop بهش اضافه کنیم یا تغییرات دیگهای که باعث تغییر منطق رندر بشه. HOC در واقع hijacking رو فعال نمیکنه اما با استفاده از HOC این امکان رو فراهم میکنیم که کامپوننت بتونه رفتار متفاوتی رو موقع رندر داشته باشه.
دو روش اصلی برای اجرای HOCها توی ریاکت وجود داره:
Props Proxy (PP)
Inheritance Inversion (II).
این دو روش امکان مدیریت و کنترل WrappedComponent به شکلهای مختلف رو فراهم می کنن.
Props Proxy
تو این روش، متد رندر HOC یه عنصر ریاکت از نوع WrappedComponent رو برمی گردونه که در واقع همون کامپوننت اصلی هست که از پارامتر ورودی تابع گرفتیم. با رندر کردن اون کامپوننت توسط این تابع، propهایی که HOC دریافت میکنه رو به کامپوننت انتقال میدیم و میتونیم propهای دیگهای هم بهش اضافه کنیم، به خاطر همین بهش Props Proxy گفته میشه.
function ppHOC(WrappedComponent) { return class PP extends React.Component { render() { return <WrappedComponent {...this.props}/> } } }
Inheritance Inversion
توی این روش، کلاس HOC برگشت داده شده(Enhancer) از WrappedComponent دریافت شده extend میشه و به همین دلیل میتونیم به متدهای اون کامپوننت دسترسی داشته باشیم و با این دسترسی خیلی راحت میتونیم متد render رو هم فراخوانی کنیم.
function iiHOC(WrappedComponent) { return class Enhancer extends WrappedComponent { render() { return super.render() } } }
اعداد رو باید از طریق آکولاد همونطور که رشته رو داخل کوتیشن قرار میدیم، انتقال بدیم.
React.render(<User age={30} department={"IT"} />, document.getElementById('container'));
این به تصمیم توسعه دهنده بستگی داره ولی بهتره که از عمومی سازی دادههای غیرضروری خودداری کنین. این وظیفه توسعه دهندهست که بررسی کنه چه نوعی از stateها برنامه رو تشکیل بده و هر state کجا باید قرار بگیره. به شکل کلی این شرطها رو قبل از انتقال state لوکال به state عمومی بررسی کنین:
اینا قوانینی هستن که تعیین می کنن چه نوع داده ای باید توی ریداکس قرار بگیره
آیا بقیه قسمتای برنامه به این دادهها اهمیت میدن؟
آیا نیازه که بتونیم یه سری دادهها رو از روی این دادههای اصلی به دست بیاریم؟
آیا از این دادهها توی چندین کامپوننت استفاده میشه؟
آیا نیازه که بتونیم یه state رو به یه بازه زمانی خاصی برگردونیم؟
آیا میخوایم داده رو توی حافظه نگه داریم؟ (یعنی به جای درخواست مجدد، از اطلاعات موجود توی state استفاده کنیم)
ریاکت به صورت پیش فرض و بدون هیچگونه پیکربندی، یه ServiceWorker برامون ایجاد میکنه. ServiceWorker یه API وب هستش که توی ذخیره کردن assetها و فایلهای دیگه بهمون کمک میکنه تا وقتی کاربر آفلاینه یا سرعت اینترنتش پایینه، بازم بتونه نتایج رو روی صفحه ببینه. به این ترتیب بهمون کمک میکنه تا تجربه کاربری بهتری ایجاد کنیم. با استفاده از متد registerServiceWorker که ریاکت فراهم میکنه، سرویس خودمون رو روی مرورگر کاربر نصب میکنیم و میتونیم از مزایایی که گفتیم بهرهمند بشیم.
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; ReactDOM.render(<App />, document.getElementById('root')); registerServiceWorker();
میتونیم مقدار فعلی یه state رو با مقدار موجود مقایسه کنیم و تصمیم بگیریم که state رو تغییر بدیم یا نه. میدونیم اگه یه setState غیر ضروری انجام بدیم، کامپوننتمون ریرندر میشه پس اگه مقادیر یکسان بود برای جلوگیری از رندر مجدد نباید استیت رو مجددا ست کنیم. برای مثال، اطلاعات پروفایل کاربر توی مثال زیر به صورت شرطی رندر شده:
const getUserAddress = (user) => { const latestAddress = user.address; if (address !== latestAddress) { setAddress(address); } };
آرایهها: از نسخه ۱۶ به بالای ریاکت، بر خلاف نسخههای قدیمی، نیازی نیست مطمئن بشیم که کامپوننتمون یه المنت یا کامپوننت ریاکت برمیگردونه.
میتونیم عناصر شبیه هم رو بدون نیاز به عنصر بسته بندی به عنوان یه آرایه برگردونیم. به عنوان مثال، بیاین لیست توسعه دهندگان زیر رو در نظر بگیریم:
const ReactJSDevs = () => { return [ <li key="1">John</li>, <li key="2">Jackie</li>, <li key="3">Jordan</li>, ]; };
به همین شکل میتونیم آیتمهای این آرایه رو توی یه کامپوننت دیگه ادغام کنیم:
const JSDevs = () => { return ( <ul> <li>Brad</li> <li>Brodge</li> <ReactJSDevs /> <li>Brandon</li> </ul> ); };
رشتهها و اعداد: میتونیم انواع رشتهها و اعداد رو با توی کامپوننتمون رندر کنیم:
// String const StringComponent = () => { return 'Welcome to ReactJS questions'; } // Number const NumberComponent = () => { return 2018; }
تا اینجای مثالها بارها از هوکها استفاده کردیم، هوکها بهمون این امکان رو میدن که بدون نوشتن کلاس از state و ویژگیهای دیگه ری اکت استفاده کنیم.
بیاین یه مثال از هوک useState ببینیم:
import { useState } from "react"; function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); }
برای استفاده از هوکها باید از دو قانون پیروی کنیم:
هوکها رو فقط در ابتدای کامپوننتها صدا کنیم. یعنی نباید هوکها رو توی حلقهها، داخل یا بعد ازشرطها یا توابع تودرتو استفاده کنیم. با این کار اطمینان حاصل میشه که هوکها با هر بار رندر کامپوننت به همون ترتیب صدا زده میشن و state هوکها بین رندرهای مختلف از useState ،useEffect حفظ میشه.
هوکها رو فقط داخل کامپوننت ریاکت میتونیم استفاده کنیم. توی توابع جاواسکریپت و خارج از درخت کامپوننتها نباید هوکها رو صدا بزنیم.
تیم ری اکت یه پلاگین ESLinst به اسم eslint-plugin-react-hooks منتشر کرده که این دو قانون رو اجرا میکنه. با استفاده از دستور زیر میتونیم این پلاگین رو به پروژه مون اضافه کنیم.
npm install eslint-plugin-react-hooks@next
و تنظیمات زیر رو توی فایل ESLint config اعمال کنیم
// Your ESLint configuration { "plugins": [ //... "react-hooks" ], "rules": { //... "react-hooks/rules-of-hooks": "error" } }
نکته این پلاگین به صورت پیش فرض در نظر گرفته شده تا در ساخت React App ازش استفاده کنیم.
اینجا تفاوت عمده Flux و Redux گفته شده
Flux | Redux |
---|---|
State قابل تغییره | State غیر قابل تغییره |
Store شامل منطق تغییر و State هستش | Store و منطق تغییر از هم جدا هستن |
Storeهای مختلفی وجود داره | فقط یه Store وجود داره |
تمام Storeها جدا از هم هستن | یه Store با Reducerهای سلسله مراتبی |
یه dispatcher تکی داره | مفهومی به اسم dispatcher وجود نداره |
کامپوننت ریاکت به store میاد و subscribe میکنه | کامپوننتهای Container از تابع connect استفاده می کنن. |
اینجا مزایای اصلی ماژول React Router V4 گفته شده:
توی React Router ورژن ۴، API کلا در مورد کامپوننت هاست. یه Router میتونیم به عنوان یه کامپوننت تکی (<BrowserRouter>
) تجسم کنیم که کامپوننتهای روتر فرزند (<Route>
) رو دسته بندی میکنه.
نیازی به تنظیم دستی history نداریم. روتر از طریق بسته بندی routeها با کامپوننت
اندازه برنامه فقط به یه ماژول روتر خاص (Web, core یا native) کاهش پیدا میکنه.
بعد از اینکه یه خطا داخل یه کامپوننت با سلسله مراتب پایینتر رخ داد، متد componentDidCatch صدا زده میشه. این متد دو تا پارامتر دریافت میکنه:
error: آبجکت error
info: یه آبجکت با کلید componentStack که شامل اطلاعاتیه در مورد اینکه کدوم کامپوننت خطا ایجاد کرده.
ساختار متد به صورت زیر هستش:
componentDidCatch(error, info)
اینها مواردی هستن که error boundaryها اونجا کار نمی کنن
داخل Event handlerها.
کد ناهمزمان با استفاده از callbackهای setTimeout یا requestAnimationFrame.
موقع ارائه سمت سرور (Server side rendering).
وقتی خطاها در خود کد error boundaryها رخ میده.
Error boundaryها خطاها رو توی event handlerها نمی گیرن. Event handlerها بر خلاف متد رندر یا lifecycle موقع رندر کردن اتفاق نمیافته یا فراخوانی نمیشه. بنابراین ری اکت میدونه که این مدل خطاها رو توی event handlerها چطوری بازیابی کنه.
اگه هنوز نیاز داریم خطا رو توی event handler بگیریم، میتونیم از دستور try / catch جاواسکریپت مثل زیر استفاده کنیم:
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { error: null }; } handleClick = () => { try { // Do something that could throw } catch (error) { this.setState({ error }); } }; render() { if (this.state.error) { return <h1>Caught an error.</h1>; } return <div onClick={this.handleClick}>Click Me</div>; } }
کد بالا خطا رو با استفاده از try/catch جاواسکریپت به جای error boundaryها میگیره.
بلوک try..catch با نوشتن کد دستوری دور هر بخش از برنامه کار میکنه در حالی که error boundaryها برای رندر کردن یه رابط کاربری پشتیبان روی صفحه در نظر گرفته شدن.
برای مثال، بلوک try catch به شکل کد دستوری زیر استفاده میشه:
try { showButton(); } catch (error) { //... }
در حالی که error boundaryها رابط کاربری رو به شکل پایین مدیریت می کنه:
<ErrorBoundary> <MyComponent /> </ErrorBoundary>
پس اگه خطایی توی متد componentDidUpdate توسط setState جایی در عمق درخت، رخ بده بازم به درستی به نزدیکترین error boundary گسترش پیدا میکنه.
توی ری اکت ورژن ۱۶، خطاهایی که توسط هیچ error boundary گرفته نشن، منجر به unmount شدن کل درخت کامپوننت ری اکت میشن.دلیل این تصمیم اینه که رابط کاربری خراب بهتره که کامل حذف بشه تا اینکه سر جای خودش باقی بمونه.به عنوان مثال، برای یه برنامه پرداخت بهتره که هیچی رندر نکنیم تا اینکه بخوایم یه مقدار اشتباه رو نشون بدیم.
میزان و محل استفاده از error boundaryها بر اساس نیاز پروژه به عهده توسعه دهندهست. میتونیم از هر کدوم از روشهای زیر استفاده کنیم:
میتونیم روت کامپوننتهای سطح بالا رو برای نمایش یه پیغام خطای عمومی واسه کل برنامه بسته بندی کنیم.
همین طور میتونیم کامپوننتهای تکی رو توی یه error boundary قرار بدیم تا از خراب شدن کل برنامه محافظت بشه.
به غیر از پیامهای خطا و پشته جاواسکریپت، ری اکت ورژن ۱۶ پشته کامپوننت رو با نام فایل و شماره خط با استفاده از مفهوم error boundary نمایش میده. برای مثال، کامپوننت BuggyCounter پشته کامپوننت رو به صورت زیر نشون میده:
متد render تنها متد مورد نیاز توی class کامپوننت هستش. به عنوان مثال، همه متدها غیر از متد render توی class کامپوننت اختیاری هستش.
اینجا لیستی از انواع typeهای استفاده شده و برگشت داده شده توسط متد رندر نوشته شده:
عناصر ری اکت: عناصری که به ری اکت دستور میدن تا یه گره DOM رو رندر کنه. این عناصر شامل عناصر html مثل <div/>
و عناصر تعریف شده توسط کاربر هستش.
آرایهها و fragmentها: میتونیم بجای بازگرداندن چندین عنصر، یه آرایه از اونا برگردونیم و اگه چندتا عنصر برمیگردونیم میشه اونا رو داخل فرگمنت گذاشت.
Portalها فرزندها رو داخل یه زیرشاخه DOM متفاوت رندر میکنه.
رشتهها و اعداد: رشتهها و اعداد رو به عنوان گره متنی توی DOM رندر میکنه.
Boolean یا null: چیزی رندر نمیکنه اما از این type برای رندر کردن محتوای شرطی استفاده میشه.
constructor به طور عمده برای دو منظور استفاده میشه:
برای مقدار دهی اولیه local state با تخصیص ابجکت به this.state
برای اتصال متدهای event handler به نمونه
به عنوان مثال کد زیر هر دو مورد بالا رو پوشش میده:
constructor(props) { super(props); // Don't call this.setState() here! this.state = { counter: 0 }; this.handleClick = this.handleClick.bind(this); }
نه، اجباری نیست. به عنوان مثال، اگه ما state رو مقدار دهی اولیه نکنیم و متدها رو متصل نکنیم، نیازی به پیاده سازی constructor برای کلاس کاموننتمون نداریم.
defaultProp ها به عنوان یه ویژگی روی کلاس کامپوننت تعریف شده تاpropهای پیش فرض رو برای کلاس تنظیم کنه. این مورد برای propهای undefined استفاده میشه نه برای propهای null. به عنوان مثال بیاین یه prop پیش فرض رنگ برای کامپوننت button بسازیم.
class MyButton extends React.Component { //... } MyButton.defaultProps = { color: "red", };
اگه props.color ارائه نشه مقدار پیش فرض روی red
تنطیم میشه. به عنوان مثال هر جا بخوایم به prop color دسترسی پیدا کنیم از مقدار پیش فرض استفاده میکنه.
render() { return <MyButton style={{color: this.props.color}} /> ; // props.color will be set to red }
نکته: اگه مقدار null رو ارائه بدیم مقدار null باقی می مونه.
setState رو نباید توی componentWillUnmount فراخوانی کنیم چون وقتی یه کامپوننت unmount میشه، دیگه هیچوقت دوباره mount نمیشه.
این متد زمانی فراخوانی میشه که یه خطا توی کامپوننتهای فرزندان این کامپوننت رخ بده. این متد errorای که رخ داده رو به عنوان ورودی دریافت میکنه و باید یه مقداری رو برای به روز کردن state برگردونه. ساختار کلی این متد به شکل پایینـه:
static getDerivedStateFromError(error)
بیایین یه مثال از ErrorBoundaryها با استفاده از این متد ببینیم:
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // Update state, next render will show the fallback UI return { hasError: true }; } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } }
تغییر در propها یا state میتونه باعث به روزرسانی بشه. متدهای زیر به ترتیب، وقتی یه کامپوننت مجددا رندر میشه صدا زده میشه.
static getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
وقتی یه خطایی موقع رندر کردن وجود داشته باشه، توی متد lifecycle، یا توی constructor هر کامپوننت فرزند، متدهای زیر فراخوانی میشه.
static getDerivedStateFromError
componentDidCatch
بیشتر برای نمایش اینکه کدوم کامپوننت رندر شده و یا debug(اشکال زدایی) راحتتر توی devTools استفاده میشه، به عنوان مثال، برای سهولت توی debug یه displayName انتخاب میکنیم که نشون میده این کامپوننت، نتیجه یه withSubscription HOC هستش.
function withSubscription(WrappedComponent) { class WithSubscription extends React.Component { /*... */ } WithSubscription.displayName = `WithSubscription(${getDisplayName( WrappedComponent )})`; return WithSubscription; } function getDisplayName(WrappedComponent) { return ( WrappedComponent.displayName || WrappedComponent.name || "Component" ); }
ری اکت همه مرورگرهای معروف از جمله اینترنت اکسپلورر ۹ به بالا رو پشتیبانی می کنه، اگرچه برای مرورگرهای قدیمی تر مثل IE 9 و IE 10 یه سری polyfillها نیازه. اگه از polyfill es5-shim و es5-sham استفاده کنیم در اون صورت حتی مرورگرهای قدیمی رو هم پشتیبانی میکنه که متدهای ES5 رو پشتیبانی نمیکنن.
این متد از بسته react-dom در دسترس هستش و کامپوننت mount شده رو از DOM حذف میکنه و event handlerها و stateهای اون کامپوننت رو فیلتر میکنه. اگه هیچ کامپوننت mount شدهای توی container وجود نداشته باشه، فراخوانی این تابع هیچ کاری رو انجام نمیده. اگه کامپوننت unmount شدهای وجود داشت true رو بر می گردونه و اگه هیچ کامپوننتی برای unmount شدن وجود نداشت false رو برمی گردونه.
ساختار این متد به صورت زیر هستش:
ReactDOM.unmountComponentAtNode(container);
code-splitting ویژگی پشتیبانی شده توسط باندلرهایی مثل webpack و browserify هستش که میتونه بستههای مختلفی ایجاد کنه که میتونه به صورت پویا در زمان اجرا بارگیری بشه. ریاکت code-splitting رو از طریق ویژگی dynamic import پشتیبانی میکنه.
برای مثال، در قطعه کد زیر، moduleA.js و تمام وابستگیهای منحصر به فرد اون رو به عنوان یه قطعه جداگانه ایجاد میکنه که فقط بعد از کلیک کاربر روی دکمه Load بارگیری میشه.
moduleA.js
const moduleA = "Hello"; export { moduleA };
App.js
import React, { Component } from "react"; class App extends Component { handleClick = () => { import("./moduleA") .then(({ moduleA }) => { // Use moduleA }) .catch((err) => { // Handle failure }); }; render() { return ( <div> <button onClick={this.handleClick}>Load</button> </div> ); } } export default App;
<StrictMode>
توی موارد زیر به کار میاد:
شناسایی کامپوننتها با متد unsafe lifecycle.
هشدار در مورد استفاده از API مربوط به legacy string ref.
تشخیص side effect های غیرمنتظره.
شناسایی API legacy context.
هشدار در مورد استفاده منسوخ findDOMNode.
Fragmentهای اعلام شده با سینتکس <React.Fragment> ممکنه keyهایی داشته باشن. استفاده عمومی مپ کردن یه مجموعه به آرایهای از fragmentها به صورت زیر هستش:
function Glossary(props) { return ( <dl> {props.items.map((item) => ( // Without the `key`, React will fire a key warning <React.Fragment key={item.id}> <dt>{item.term}</dt> <dd>{item.description}</dd> </React.Fragment> ))} </dl> ); }
یادداشت key تنها اتریبیوتی هستش که میشه به Fragment پاس داد. در آینده، ممکنه از اتریبیوتهای اضافه ای هم مثل event handlerها پشتیبانی بشه.
از ریاکت 16، هر دو ویژگی استاندارد یا سفارشی DOM کاملا پشتیبانی میشن.از اونجایی که کامپوننتهای ریاکت اغلب هر دو نوع پراپهای DOM-related و custom رو استفاده میکنن، ریاکت دقیقا مانند APIهای DOM از قرارداد camelCase استفاده میکنه. بیاین با استفاده از ویژگیهای استاندارد HTML چند مورد رو انتخاب کنیم.
<div tabIndex="-1" /> // Just like node.tabIndex DOM API <div className="Button" /> // Just like node.className DOM API <input readOnly={true} /> // Just like node.readOnly DOM API
این propها به استثنای موارد خاص، مشابه ویژگیهای متناظر HTML کار میکنن. همچنین از تمام ویژگیهای svg پشتیبانی می کنه.
کامپوننتهای با اولویت بالا جدا از مزایایی که داره، چند تا نکته مهم هم داره. اینجا چند مورد به ترتیب گفته شده:
از HOCها توی متد render استفاده نکنیم: استفاده از HOC توی یه کامپوننت با متد رندر اون کامپوننت توصیه نمیشه.
render() { // A new version of EnhancedComponent is created on every render // EnhancedComponent1 !== EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // That causes the entire subtree to unmount/remount each time! return <EnhancedComponent />; }
کد بالا با remount کردن کامپوننتی که باعث از بین رفتن state اون کامپوننت و همه فرزندانش شده، روی عملکرد تاثیر میذاره. در عوض، HOCها رو بیرون از تعریف کامپوننت اعمال میکنیم تا کامپوننت بدست اومده فقط یه بار ساخته بشه.
متدهای static باید کپی بشن وقتی HOC رو روی یه کامپوننت اعمال می کنیم، کامپوننت جدید هیچ کدوم از متدهای استاتیک کامپوننت اصلی رو نداره
// Define a static method WrappedComponent.staticMethod = function () { /*...*/ }; // Now apply a HOC const EnhancedComponent = enhance(WrappedComponent); // The enhanced component has no static method typeof EnhancedComponent.staticMethod === "undefined"; // true
میتونیم با کپی کردن متدها توی container قبل از return کردنش رو این مشکل غلبه کنیم.
function enhance(WrappedComponent) { class Enhance extends React.Component { /*...*/ } // Must know exactly which method(s) to copy:( Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance; }
Refها رو نمیشه انتقال داد: برای HOCها نیاز داریم که همه propها رو به کامپوننت پاس بدیم اما در مورد refها این کار جواب نمیده. دلیلش هم اینه که ref در واقع یه prop شبیه key نیست. تو این مورد باید از React.forwardRef API استفاده کنیم.
ًReact.forwardRef یه تابع رندر رو به عنوان یه پارامتر میگیره و DevTools از این تابع برای تعیین اینکه چه چیزی باید برای ref forwarding component نمایش داده بشه، استفاده میکنه. برای مثال، اگه ما هیچ اسمی برای تابع رندر نذاریم یا از ویژگی diplayName استفاده نکنیم، توی DevTools به عنوان ForwardRef نمایش داده میشه.
const WrappedComponent = React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; });
اما اگه برای تابع رندر اسم گذاشته باشیم اونوقت به صورت ”ForwardRef(myFunction)” نمایش داده میشه
const WrappedComponent = React.forwardRef(function myFunction(props, ref) { return <LogProps {...props} forwardedRef={ref} />; });
به عنوان یه گزینه دیگه، میتونیم از ویژگی displayName برای تابع forwardRef استفاده کنیم.
function logProps(Component) { class LogProps extends React.Component { //... } function forwardRef(props, ref) { return <LogProps {...props} forwardedRef={ref} />; } // Give this component a more helpful display name in DevTools. // e.g. "ForwardRef(logProps(MyComponent))" const name = Component.displayName || Component.name; forwardRef.displayName = `logProps(${name})`; return React.forwardRef(forwardRef); }
اگه یه prop رو به یه کامپوننت پاس بدیم ولی هیچ مقداری رو برای اون prop نعیین نکنیم، به طور پیشفرض true در نظر گرفته میشه. به طور مثال:
<MyInput autocomplete /> <MyInput autocomplete={true} />
Next.js یه فریمورک محبوب و سبک برای برنامههای استاتیک و تحت سرور هستش که توسط ریاکت ساخته شده. همچنین استایل دهی و مسیریابی رو هم ارائه میده. اینجا ویژگیهای اصلی ارائه شده توسط Next.js آورده شده.
server rendering به طور پیش فرض پشتیبانی میشه
تقسیم خودکار کد برای بارگذاری سریعتر صفحه
مسیریابی(routing) ساده سمت کلاینت(مبتنی بر صفحه)
محیط توسعه زنده و سریع(HMR)
با Express یا هر سرور HTTP دیگهای روی nodejs قابل پیاده سازیه
با تنظیمات Babel و Webpack قابل تنظیمه
event handlerها و توابع دیگه رو میتونیم به عنوان prop به کامپوننتهای فرزند انتقال بدیم. به صورت زیر توی کامپوننت فرزند میتونه استفاده بشه،
<button onClick={this.handleClick}>
بله، میتونیم استفاده کنیم. این معمولا سادهترین راه برای انتقال پارامترها به توابع برگشتی هستش. اما موقع استفاده ازشون اگه خیلی پرفورمنس برامون مهمه یادداشت پایین رو باید مدنظر داشته باشیم.
class Foo extends Component { handleClick() { console.log("Click happened"); } render() { return <button onClick={() => this.handleClick()}>Click Me</button>; } }
یادداشت: استفاده از تابع arrow توی متد رندر، با هر بار اجرا، یه تابع جدید ایجاد میکنه که هر بار که کامپوننت رندر میشه، مجدد ایجاد شده و توی حافظه مصرفی و... تاثیر میزاره.
اگه از eventHandlerها مثل onClick یا onScroll استفاده میکنین و میخوایید از اجرا شدن بیش از حد تابع callback جلوگیری کنین، روشهای مختلفی برای انجام این کار وجود داره:
Throttling: تغییر براساس زمان و به شکل پیوسته. برای مثال میشه با استفاده از تابع _.throttle
روی کتابخونه lodash(روی npm موجوده) انجامش داد.
Debouncing: تغییر براساس یک مدت زمان بدون فعالیت. میشه با استفاده از تابع _.debounce
روی کتابخونه lodash انجامش داد.
Throttling با RequestAnimationFrame : تغییر بر اساس requestAnimationFrame. میشه از کتابخونه یا تابع raf-schd روی lodash استفاده کرد.
ReactDOM قبل از embedd کردن هر مقدار در JSX اون رو امن میکنه و اصطلاحا escape میکنه. به همین دلیل میشه اطمینان داشت که هیچ کدمخربی رو نمیشه به شکل مقداری تو jsx ادد کرد. هر مقداری قبل از رندر شدن به رشته تبدیل میشه و برای مثال اگه از ورودی input یه مقدار نا امن از کاربر دریافت کنیم مثل:
const name = response.potentiallyMaliciousInput; const element = <h1>{name}</h1>;
مقدار چاپ شده در مقابل حمله XSS یا همون (Cross-site-scripting) در امان خواهد بود.
میشه UI(تولید شده توسط رندرشدن elementها) رو با پاس دادن مقدار جدید به متد رندر از ReactDOM به روزرسانی کرد. برای مثال بیایین یه ساعت رو بررسی کنیم که با هر تیکتاک باید رندر بشه و ما این کار رو با ReactDom میخواییم انجام بدیم:
function tick() { const element = ( <div> <h1>Hello, world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render(element, document.getElementById("root")); } setInterval(tick, 1000);
وقتی یه کامپوننت میسازید، فرقی هم نمیکنه تابع یا کلاس، نباید روی propهای ورودیش تغییری انجام بده. به این کامپوننت نگاه کنید:
function Capital(amount, interest) { return amount + interest; }
این کامپوننت pure نامیده میشه چون با ورودیهای یکسان، همواره خروجی یکسانی تولید میکنه و تغییری روی اونا نمیده. به همین خاطر ریاکت یه قانون ساده داره که میگه: "همه کامپوننتها باید مثل یه pure کامپوننت رفتار کنن و در مورد propهای ورودی هیچ تغییری روشون ندن."
وقتی که متد setState() رو داخل یه کامپوننت فراخوانی میکنیم، ریاکت object پاس داده شده رو با state فعلی ترکیب میکنه. بیایین یه مثال رو درنظر بگیریم از کاربران، پستها و نظرات فیسبوک:
constructor(props) { super(props); this.state = { posts: [], comments: [] }; }
حالا میتونیم به شکل جداگانه هر کدوم از این دادهها رو با setState() جداگانه مثل زیر آپدیت کنیم:
componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); }
همونطوری که بالاتر هم گفته شد، this.setState({ comments }) فقط بخش مربوط به نظرات رو به روز میکنه و کاری با بقیه بخشها نداره.
موقع iterations یا داخل حلقهها، مرسوم هست که یه پارامتر اضافی دیگه به eventHandler پاس بدیم، مثلا آیدی کاربر و ... . این میتونه arrow-functionها یا متد bind انجام بشه. بیایین یه نگاهی به تابع آپدیت کاربر داخل یه جدول بندازیم:
<button onClick={(e) => this.updateUser(userId, e)}>Update User details</button> <button onClick={this.updateUser.bind(this, userId)}>Update User details</button>
در هر دو حالت پارامتر e به عنوان دومین پارامتر پاس داده میشود. در مورد تابع arrow باید به شکل مستقیم اون رو پاس بدیم و در مورد مثال bind به شکل اتوماتیک پاس داده میشه.
میتونیم با چک کردن یه شرط و برگردوندن null کامپوننتـمون رو رندر نکنیم. مثل این کامپوننت:
function Greeting(props) { if (!props.loggedIn) { return null; } return <div className="greeting">welcome, {props.name}</div>; }
class User extends React.Component { constructor(props) { super(props); this.state = {loggedIn: false, name: 'John'}; } render() { return ( <div> //Prevent component render if it is not loggedIn <Greeting loggedIn={this.state.loggedIn} /> <UserDetails name={this.state.name}> </div> ); } }
در کامپوننتهای فوق، کامپوننت greeting با بررسی مقدار prop مورد نظر برای لاگین بودن، به صورت شرطی رندر میشه.
سه تا شرط وجود داره که مطمئن بشیم میتونیم از index به عنوان key استفاده کنیم:
لیست و آیتمهاش ثابت هستن و کامپایل نمیشن و تغییر نمیکنن.
آیتمهای موجود توی لیست، فیلدی برای id ندارن.
لیست موردنظر هیچ وقت مجددا تولید و مقداردهی نمیشه، یا فیلتر نمیشه.
Keyهایی که در طول رندر کردن یک آرایه استفاده میشن، الزاما باید در همسایگی خودشون منحصر به فرد باشن ولی هیچ لزومی نداره که به شکل عمومی منحصر به فرد باشن. برای مثال میتونین یه کلید یکسان رو موقع رندر کردن دو تا آرایه متفاوت استفاده کنین، مثل:
function Book(props) { const index = ( <ul> {props.pages.map((page) => ( <li key={page.id}>{page.title}</li> ))} </ul> ); const content = props.pages.map((page) => ( <div key={page.id}> <h3>{page.title}</h3> <p>{page.content}</p> <p>{page.pageNumber}</p> </div> )); return ( <div> {index} <hr /> {content} </div> ); }
Formik یه کتابخونه مدیریت فرم برای ریاکتـه که به شکل پیشفرض امکان زیادی مثل اعتبارسنجی، نگهداری سابقه تغییر فیلدها و مدیریت ثبت شدن فرم رو فراهم میکنه. در حالت کلی میشه به صورت زیر دستهبندیشون کرد:
دریافت و فراهم کردن دادههای فیلدهای فرم
اعتبارسنجی و مدیریت پیامهای خطا
مدیریت ثبت شدن فرم
از فرمیک میشه برای فرمهای مقیاسپذیر، بهینه، بی دردسر و حتی پیچیده استفاده کرد. API فرمیک هم بسیار ساده و قابل فهمه.
دلایل استفاده از فرمیک بهجای ریداکسفرم اینا هستن:
State فرم به شکل محلی و کوتاه مدت ذخیره میشه و هیچ نیازی به نگهداری اون به شکل عمومی روی ریداکس یا هر کتابخونه شبیه flux نیست.
Redux-Form با هر کلیدی که فشرده میشه به شکل عمومی reducer موجود در redux رو فراخوانی میکنه. این مسئله توی برنامههای بزرگ باعث ایجاد کندی میشه.
سایز کتابخونه Redux-Form حدود 22.5 kB به شکل minify و gzip شده هستش که در مقابل Formik فقط 12.7 kB هست.
در ریاکت توصیه میشه که از composition(ترکیب) بجای inheritance(ارثبری) برای استفاده مجدد از کدها توی کامپوننتهای دیگه بهره ببریم. هر دو مورد به ما این امکان رو میدن که به شکل منعطف کدهای موجود رو بین کامپوننتها به اشتراک بزاریم.
ولی, اگه بخواییم یه کد غیر UI بین کامپوننتها به اشتراک بزاریم، توصیه میشه اون بخش رو به شکل یه ماژول جداگانه جاواسکریپت دربیاریم و بعدا بدون دغدغه از اینکه کامپوننتی که میخواییم از این کد استفاده کنیم، کلاس هست یا تابع، یا حتی اصلا ربطی به ui نداره، از ماژولمون استفاده کنیم.
بله، میشه از وب کامپوننتها توی برنامه ریاکتی استفاده کرد. اگرچه خیلی از توسعهدهندهها از این قابلیت استفاده نمیکنن، ممکنه بیشتر زمانهایی به کارمون بیاد که از کتابخونههای خارجی توی برنامهمون میخواییم استفاده کنیم. برای مثال وب کامپوننت انتخاب تاریخ Vaadin رو میشه به شکل زیر استفاده کرد:
import React, { Component } from "react"; import "./App.css"; import "@vaadin/vaadin-date-picker"; class App extends Component { render() { return ( <div className="App"> <vaadin-date-picker label="When were you born?"></vaadin-date-picker> </div> ); } } export default App;
ساختار import داینامیک در ابتدا به شکل یه پروپوزال روی ECMAScript ارائه شده بود که تایید شد. میتونیم با استفاده از این قابلیت به راحتی code-splitting رو توی برنامهمون داشته باشیم. به مثال پایین توجه کنین:
Import عادی
import { add } from "./math"; console.log(add(10, 20));
Import داینامیک
import("./math").then((math) => { console.log(math.add(10, 20)); });
اگه میخوایین code-splitting رو روی یه برنامهای که سمت سرور رندر میشه داشته باشین، توصیه میشه که از کتابخونه LoadableComponents استفاده کنین، چون در حال حاضر React.lazy و Suspense روی SSR به درستی کار نمیکنن. Loadable بهتون این اجازه رو میده که import داینامیک رو مثل یه کامپوننت عادی باهاش برخورد کنین. بزارین یه مثال بزنیم:
import loadable from "@loadable/component"; const OtherComponent = loadable(() => import("./OtherComponent")); function MyComponent() { return ( <div> <OtherComponent /> </div> ); }
حالا کامپوننت OtherComponent تو یه فایل جداگانه bundle میشه.
اگه یه ماژول شامل import داینامیک باشه و هنوز رندر نشده نباشه، توی کامپوننت والدش باید یه رابط کاربری loading براش نمایش داده بشه. این بخش میتونه با کامپوننت Suspense مدیریت بشه. برای مثال کامپوننتهای پایین رو ببینید که از Suspense در طول مدت بارگذاری کامپوننت دوم استفاده میکنن:
const OtherComponent = React.lazy(() => import("./OtherComponent")); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); }
همونطوری که اشاره کردیم، Suspense روی یه کامپوننت که به شکل lazy بارگذاری شده wrap میشه.
یکی از بهترین جاها برای انجام code-splitting، انجام اون به ازای routeهای برنامهست. وقتی میخواییم یه route جدید رو بارگذاری کنیم کاربر انتظار داره که صفحه عوض بشه، پس با نمایش fallback و استفاده از suspense تجربه کاربری خیلی بهتر میشه. بیایین یه مثال از روترها در کنار استفاده از lazy و suspense ببینیم:
import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import React, { Suspense, lazy } from "react"; const Home = lazy(() => import("./routes/Home")); const About = lazy(() => import("./routes/About")); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> </Switch> </Suspense> </Router> );
در مثال فوق به ازای هر route یک فایل bundle مجزا ساخته میشه و برنامه وقتی میخواد بارگذاری بشه، فقط فایل مربوط به اون route لود میشه که باعث افزایش سرعت لود و بهبود تجربه کاربری میشه.
Context برای به اشتراک گذاری دادههایی که به شکل عمومی روی درخت کامپوننتهای ریاکت مورد نیاز هستن طراحی شده. برای مثال در تکه کد زیر مقدار theme برای استفاده در کامپوننتهای پایینتر از طریق context منتقل شده.
//Lets create a context with a default theme value "luna" const ThemeContext = React.createContext("luna"); // Create App component where it uses provider to pass theme value in the tree class App extends React.Component { render() { return ( <ThemeContext.Provider value="nova"> <Toolbar /> </ThemeContext.Provider> ); } } // A middle component where you don't need to pass theme prop anymore const Toolbar = () => { return ( <div> <ThemedButton /> </div> ); } // Lets read theme value in the button component to use const ThemedButton = () => { const theme = useContext(ThemeContext); return <Button theme={theme} />; }
پارامتر پیشفرض برای context، زمانی استفاده میشه که بخواییم یه مقدار پیشفرض هست که اگه خواستیم از context یه جایی استفاده کنیم قبلش توی درخت کامپوننتهای والد از Provider استفاده نکردیم، این مقدار برگشت داده بشه، بیشتر برای محیطهای تست و... که لازم نباشه کامپوننت رو داخل Provider قرار بدیم استفاده میشه.
const MyContext = React.createContext(defaultValue);
اگر نخواییم از هوک useContext استفاده کنیم و داخل کلاس کامپوننت باشیم، ContextType برای دسترسی به context استفاده میشه. به دو روش میشه ازش استفاده کرد:
class MyClass extends React.Component { componentDidMount() { let value = this.context; /* perform a side-effect at mount using the value of MyContext */ } componentDidUpdate() { let value = this.context; /*... */ } componentWillUnmount() { let value = this.context; /*... */ } render() { let value = this.context; /* render something based on the value of MyContext */ } } MyClass.contextType = MyContext;
class MyClass extends React.Component { static contextType = MyContext; render() { let value = this.context; /* render something based on the value */ } }
یه Consumer، یه کامپوننت ریاکتی هست که به تغییرات یه context گوش میکنه. روی این کامپوننت الزاما باید یه تابع به عنوان فرزند پاس داده بشه. کامپوننت consumer تضمین میکنه که مقدار context رو به عنوان ورودی اون تابع بهمون خواهد داد و ما میتونیم از اون مقدار برای تولید UI خودمون استفاده کنیم. به مثال پایین توجه کنین:
<MyContext.Consumer> {value => /* render something based on the context value */} </MyContext.Consumer>
نکته: با استفاده از هوک useContext دیگه لازم نیست به این شکل از کامپوننت Consumer استفاده کنیم و به راحتی میشه به مقدار context دست پیدا کرد.
context از رفرنس برای متوجه شدن اینکه چه زمانی به رندر شدن مجدد نیاز داریم، استفاده میکنه., حالتهایی هست که میتونه باعث رندر شدن ناخواسته کامپوننت consumer زمانی که والد provider ریرندر شد بشه. برای مثال، کدپایین همهیl consumerها رو با هر ریرندر توی کامپوننت Provider ریرندر میکنه. چون object به عنوان ورودی provider داده شده که با هر بار رندر رفرنس اون object تغییر میکنه.
class App extends React.Component { render() { return ( <Provider value={{ something: "something" }}> <Toolbar /> </Provider> ); } }
این مورد میتونه با lift-up کردن state به کامپوننت والدش حل بشه:
class App extends React.Component { constructor(props) { super(props); this.state = { value: { something: "something" }, }; } render() { return ( <Provider value={this.state.value}> <Toolbar /> </Provider> ); } }
ref داخل کامپوننتها پاس داده نمیشه چون ref یه prop نیست. اون توسط ریاکت درست مثل key به طور متفاوتی هندل میشه. اگه ما ref رو توی HOC اضافه کنیم، ref به بیرونی ترین کامپوننت container اشاره میکنه، نه به کامپوننت wrapped شده. تو این مورد ما میتونیم از Forward Ref API استفاده کنیم. برای مثال با استفاده از React.forwardRef API میتونیم ref رو به کامپوننت FancyButton داخلی بفرستیم.
function logProps(Component) { class LogProps extends React.Component { componentDidUpdate(prevProps) { console.log("old props:", prevProps); console.log("new props:", this.props); } render() { const { forwardedRef,...rest } = this.props; // Assign the custom prop "forwardedRef" as a ref return <Component ref={forwardedRef} {...rest} />; } } return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; }); }
بیایید از این HOC برای لاگ کردن propهای ورودی به کامپوننتـمون استفاده کنیم:
class FancyButton extends React.Component { focus() { //... } //... } export default logProps(FancyButton);
حالا بیاین یه ref بسازیم و اونو به کامپوننت FancyButton بفرستیم. توی این مورد میتونیم focus رو روی عنصر دکمه تنظیم کنیم.
import FancyButton from "./FancyButton"; const ref = React.createRef(); ref.current.focus(); <FancyButton label="Click Me" handleClick={handleClick} ref={ref} />;
توابع منظم یا کلاس کامپوننتها آرگومان ref رو دریافت نمی کنن و ref توی propها هم در دسترس نیست. آرگومان دوم ref فقط زمانی وجود داره که ما کامپوننت رو با React.forwardRef تعریف کنیم.
وقتی ما در یک کامپوننت شروع به استفاده از forwardRef می کنیم، باید با اون به عنوان یه تغییر سریع رفتار کنیم و نسخه اصلی جدیدی از کتابخونه خودمون رو منتشر کنیم. این به این دلیله که کتابخونه ما رفتار متفاوتی داره مثل اینکه چه چیزی به ref اختصاص پیدا کرده و چه خروجیهایی داریم. این تغییرات میتونه برنامهها و بقیه کتابخونههای وابسته به رفتار قدیمی رو از بین ببره.
اگه از ES6 استفاده نمی کنیم ممکنه لازم باشه که به جای اون از ماژول create-react-class استفاده کنیم. برای propهای پیش فرض، نیاز داریم که getDefaultProps() رو به عنوان یه تابع روی آبجکت پاس داده شده تعریف کنیم. در حالی که برای state اولیه، باید یه متد getInitialState جداگانه ارائه بدیم که یه state اولیه برمیگردونه.
var Greeting = createReactClass({ getDefaultProps: function () { return { name: "Jhohn", }; }, getInitialState: function () { return { message: this.props.message }; }, handleClick: function () { console.log(this.state.message); }, render: function () { return <h1>Hello, {this.props.name}</h1>; }, });
یادداشت: اگه از createReactClass استفاده میکنیم اتصال خودکار برای همه روشها در دسترسه. یعنی نیازی به استفاده از.bind(this) توی constructor برای event handlerها نیست.
بله، JSX برای استفاده از ریاکت اجباری نیست. در واقع مناسب زمانی هست که ما نمیخوایم کامپایلی رو توی محیط build تنظیم کنیم. هر عنصر JSX فقط syntactic sugar هستش برای فراخوانی React.createElement(component, props,...children). برای مثال بیاین یه مثال greeting با JSX بزنیم.
class Greeting extends React.Component { render() { return <div>Hello {this.props.message}</div>; } } ReactDOM.render( <Greeting message="World" />, document.getElementById("root") );
میتونیم همین کد رو بدون JSX مثل زیر بنویسیم،
class Greeting extends React.Component { render() { return React.createElement("div", null, `Hello ${this.props.message}`); } } ReactDOM.render( React.createElement(Greeting, { message: "World" }, null), document.getElementById("root") );
ریاکت نیاز به استفاده از الگوریتمها داره تا بفهمه چطور به طور موثر UI رو برای مطابقت با آخرین درخت بهروز کنه. الگوریتمهای مختلفی در حال تولید حداقل تعداد عملیات برای تبدیل یه درخت به درخت دیگه هستن. با این حال، الگوریتمها به ترتیب O(n3) دارای پیچیدگی هستن، جایی که n تعداد عناصر موجود در درخت هستش.
توی این مورد، برای نمایش ۱۰۰۰ عنصر به ترتیب یک میلیارد مقایسه نیازه و این خیلی هزینه بر هستش. در عوض ریاکت یه الگوریتم ابتکاری O(n) رو بر اساس دو پیش فرض پیادهسازی میکنه:
دو عنصر از انواع مختلف باعث تولید درختهای مختلفی میشه.
برنامه نویس میتونه اشاره کنه که کدوم یکی از عناصر فرزند ممکنه توی رندرهای مختلف با یه prop اصلی پایدار باشن.
موقع تفاوت بین دو درخت، ریاکت اول دو عنصر ریشه رو با هم مقایسه میکنه. رفتار بسته به انواع عناصر ریشه تغییر میکنه. مواردی که اینجا گفته شده قوانینی از الگوریتم reconciliation هستن.
<a>
تا <img>
یا از <Article>
تا <Comment>
از انواع مختلف باعث بازسازی کامل میشن.<div className="show" title="ReactJS" /> <div className="hide" title="ReactJS" />
<ul> <li>first</li> <li>second</li> </ul> <ul> <li>first</li> <li>second</li> <li>third</li> </ul>
<ul> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> <ul> <li key="2014">Connecticut</li> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul>
موارد استفاده کمی برای refها وجود داره
مدیریت focus، text selection یا پخش media
راهاندازی انیمیشنهای ضروری.
ادغام با کتابخانههای third-party DOM.
حتی اگه یه الگویی به اسم render props وجود داشته باشه، برای استفاده از این الگو نیازی به استفاده از یه prop به اسم render نیست. به عنوان مثال، هر prop که تابعی باشه، که کامپوننتی از اون برای دونستن اینکه چه چیزی باید ارائه بده استفاده کنه، از نظر فنی "render prop" هستش. بیاین یه مثال در مورد prop فرزند برای رندر prop بزنیم
<Mouse
children={(mouse) => (
<p>
The mouse position is {mouse.x}, {mouse.y}
</p>
)}
/>
در واقع نیازی نیست که از prop فرزند توی لیست "attribute"ها توی عنصر JSX نام برده بشه. در عوض میتونیم اونو مستقیما توی المنت نگه داریم.
<Mouse> {(mouse) => ( <p> The mouse position is {mouse.x}, {mouse.y} </p> )} </Mouse>وقتی که از روش بالا (تابع بدون نام) برای رندر کردن فرزند استفاده میکنیم، به صراحت و اجبار میگیم که فرزند پاس داده شده، باید یه تابع توی propType هامون باشن.
Mouse.propTypes = { children: PropTypes.func.isRequired, };
اگه بیایم داخل متد رندر یه تابعی ایجاد کنیم، در این صورت هدف اصلی pure componentها رو نفی کردیم. چون که مقایسه سطحی propها معمولا همیشه مقدار false رو برای propهای جدید برمی گردونه و هر رندر در این حالت یه مقدار جدیدی رو برای رندر ارائه میده. با تعریف یه تابع رندر به عنوان متد instance میتونیم این مشکل رو حل کنیم.
میتونیم کامپوننتهای با الویت بالا (HOC) رو با استفاده از یه کامپوننت همعمولی با یه رندر پیاده سازی کنیم. به عنوان مثال اگه ترجیح میدیم که به جای کامپوننت
function withMouse(Component) { return class extends React.Component { render() { return ( <Mouse render={(mouse) => <Component {...this.props} mouse={mouse} />} /> ); } }; }
این روش رندر کردن prop ها، انعطاف پذیری استفاده از هر دو الگو رو میده.
windowing تکنیکیه که فقط زیر مجموعه ای از سطرهامون رو در هر زمان ارائه میده و میتونه مدت زمان لازم برای رندر مجدد کامپوننتها و همینطور تعداد گرههای DOM ایجاد شده روبه طرز چشمگیری کاهش بده. اگه برنامه مون لیستهای طولانی ای از از داده رو ارائه میده، این روش توصیه میشه.react-window و react-virtualized هر دو کتابخونههای معروف windowing هستن که چندین کامپوننت قابل استفاده مجدد رو برای نمایش لیست ها، شبکهها و دادههای جدولی فراهم میکنن.
مقادیر جعلی مثل false، null، undefined و true معتبر هستند ولی هیچ چیزی رو رندر نمیکنن. اگه بخوایم اونا رو نمایش بدیم باید به رشته تبدیلشون کنیم. بیاین یه مثال در مورد تبدیل به رشته بزنیم،
<div>My JavaScript variable is {String(myVariable)}.</div>
portalهای ریاکت وقتی که یه کامپوننت والد سرریز(overflow) میشه خیلی کاربرد دارن، برای مثال: المنتهایhidden یا استایلهایی که روی context تاثیر دارن(z-index، position، opacity و...) و لازمه که به شکل ظاهری container اون المنت رو بشکنید و استایل دهی رو عمومیتر کنید. برای مثال: dialogها، پیامهای notification عمومی، tooltipها و ... از این قبیل موارد هستند.
توی ریاکت، مشخصه value روی عناصر فرم مقدار رو توی DOM لغو میکنه. با یه کامپوننت کنترل نشده، ممکنه بخوایم ریاکت یه مقدار اولیه مشخص کنه ولی به روزرسانیهای بعدی رو کنترل نشده بذاره. برای هندل کردن این مورد، میتونیم به جای value مشخصه defaultValue رو تعیین کنیم.
render() { return ( <form onSubmit={this.handleSubmit}> <label> User Name: <input defaultValue="John" type="text" ref={this.input} /> </label> <input type="submit" value="Submit" /> </form> ); }
همین کار برای select
و textArea
هم انجام میشه ولی برای checkbox
باید از defaultchecked استفاده کنیم.
حتی اگه tech stack از توسعه دهنده ای به توسعه دهنده دیگه متفاوت باشه، معروف ترین stack توی کد پروژه biolerplate ری اکت استفاده شده. boilerplate به طور عمده از ریداکس و ریداکس ساکا برای مدیریت استیت و ساید افکتهای ناهمزمان، styled-components برای استایل دهی کامپوننت ها، axios برای فراخوانی rest api و پشتیبانیهای دیگه از قبیل webpack، reselect، ESNext و babel.
میتونیم پروژه https://github.com/react-boilerplate/react-boilerplate رو کلون کنیم و کار روی هر پروژه ری اکت جدیدی رو شروع کنیم.
اینجا تفاوتهای اصلی بین DOM واقعی و DOM مجازی گفته شده:
واقعی DOM:
به روز رسانیها کند هستن
دستکاری DOM هزینه بر هستش.
میتونیم HTML رو مستقیما به روزرسانی کنیم.
باعث اتلاف بیش از حد حافظه میشه.
در صورت به روز رسانی یه المنت، یه DOM جدید ایجاد میکنه.
مجازی DOM:
به روز رسانیها سریع هستن
دستکاری DOM خیلی راحته.
HTML رو نمیتونیم مستقیما به روز رسانی کنیم.
هیچ اتلاف حافظه ای وجود نداره.
در صورت به روز رسانی یه المنت، JSX رو به روز میکنه.
Bootstrap رو به سه روش میتونیم به برنامه ریاکت اضافه کنیم
Bootstrap as Dependency:
npm install bootstrap
react-bootstrap
reactstrap
این زیر یه لیست از 10 وبسایت مشهور
که از ریاکت برای فرانتاندشون استفاده میکنن رو لیست میکنیم:
Uber
Khan Academy
Airbnb
Dropbox
Netflix
PayPal
ریاکت هیچ ایدهای راجع به اینکه استایلها چطوری تعریف شدن نداره اما اگه تازه کار باشین میتونین از یه فایل جداگانه *.css که مث قبلا توی پروژههای ساده استفاده میشد کمک بگیرین و با استفاده از className از استایلها استفاده کنین. CSS In Js یه بخش از خود ریاکت نیست و توسط کتابخونههای third-party بهش اضافه شده اما اگه میخوایین. ازش(CSS-In-JS) استفاده کنین کتابخونه styled-components میتونه گزینه خوبی باشه.
نه. ولی میتونین از هوکها توی بعضی از کامپوننتهای قدیمی یا جدید استفاده کنین و سعی کنین باهاش راحت باشین البته برنامهای برای حذف classes از ریاکت هنوز وجود نداره.
هوک این افکت اسمش useEffect
هستش و میشه خیلی ساده ازش برای فراخوانی API با استفاده از axios استفاده کرد. نتیجه درخواست رو هم خیلی ساده میشه ریخت تو یه state داخلی از component که وظیفه این ثبت شدن داده رو هم تابع setter از useState به عهده میگیره.
خب بزارین یه مثال بزنیم که لیست مقالات رو از یه API میگیره:
import React, { useState, useEffect } from "react"; import axios from "axios"; function App() { const [data, setData] = useState({ hits: [] }); useEffect(() => { async function fetchData(){ const result = await axios( "http://hn.algolia.com/api/v1/search?query=react" ); setData(result.data); } fetchData() }, []); return ( <ul> {data.hits.map((item) => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); } export default App;
دقت کنین که یه آرایه خالی به عنوان پارامتر دوم به هوک effect دادیم که فقط موقع mount شدن درخواست رو بفرسته و لازم نباشه با هر بار رندر درخواست زده بشه، اگ لازم بود با تغییرات یه مقدار(مثلا شناسه مقاله) درخواست API رو مجددا بزنیم، میتونستیم عنوان متغیر رو توی اون آرایه قرار بدیمش و با هر تغییر اون متغیر افکت مجددا اجرا بشه.
هوکها میشه گفت همه موارد کارکردی کلاسها رو پوشش نمیدن ولی با اضافه شدن هوکهای جدید برنامههای خوبی برای آینده هوکها پیشبینی میشه. در حال حاضر هیچ هوکی وجود نداره که کارکرد متدهای getSnapshotBeforeUpdate و componentDidCatch رو محقق کنه.
ریاکت حالت پایداری از هوکها رو توی نسخه 16.8 برای پکیجهای زیر منتشر کرد:
React DOM
React DOM Server
React Test Renderer
React Shallow Renderer
وقتی که با استفاده از هوک useState
یه state رو معرفی میکنیم، یه آرایه دوتایی برمیگردونه که اندیس اولش متغیر مورد نظر برای دسترسی به state هست و اندیس درم متد setter یا تغییر دهنده اون state. یه روش اینه که با استفاده از اندیسهای آرایه و [0] و [1] بهشون دسترسی پیدا کنیم ولی یه کم ممکنه گیچ کننده باشه. ولی با استفاده از حالت destructuring خیلی سادهتر میشه این کار رو انجام داد.
برای مثال دسترسی به state با اندیسهای آرایه این شکلی میشد:
var userStateVariable = useState("userProfile"); // Returns an array pair var user = userStateVariable[0]; // Access first item var setUser = userStateVariable[1]; // Access second item
ولی همون کد با استفاده از destructuring آرایهها به شکل پایین درمیاد:
const [user, setUser] = useState("userProfile");
ایده معرفی هوک از منابع مختلفی به وجود اومد. این پایین یه لیستی ازشون رو میاریم:
تجربه قبلی که با functional API توی پکیج react-future داشتن
تجربه انجمن ریاکت با پراپ render مثل کامپوننتهای Reaction
متغیرهای state و سلولهای state توی DisplayScript.
Subscriptionهای موجود توی Rxjs.
کامپوننتهای reducer توی ReasonReact.
کامپوننتهای web اکثرا به عنوان APIهای imperative برای اجرای یه وظیفه خاص قلمداد میشن. برای استفاده ازشون باید با استفاده از ref که امکان کار با DOM را فراهم میکنه بیاییم یه کامپوننت که به شکل imperative کار میکنه ایجاد کنیم. ولی اگه از وب کامپوننتهای کاستوم یا همون third-party استفاده میکنیم، بهترین کار نوشتن یه کامپوننت wrapper برای استفاده از اون وب کامپوننت هست.
Formik یه کتابخونه ریاکت هست که امکان حل سه مشکل اساسی رو فراهم میکنه:
دریافت و مدیریت مقادیر از state
اعتبارسنجی و مدیریت خطاها
مدیریت ثبت فرمها
یه سری از میانافزارهای(middleware) معروف برای مدیریت فراخوانی actionهایی که به شکل asynchronous توی Redux فراخوانی میشن اینا هستن: Redux Thunk، Redux Promise و Redux Saga.
نه، مرورگرها نمیتونن کد JSX رو متوجه بشن. مجبوریم که از یه transpiler برای تبدیل کد JSX به کد جاواسکریپت عادی که مرورگرها متوجه میشن تبدیل کنیم. مشهورترین transpiler در حال حاضر Babel هست که برای اینکار استفاده میشه.
ریاکت از روش جربان داده یک طرفه استفاده میکنه. استفاده از prop باعث میشه از تکرار موارد بدیهی جلوگیری بشه و درک کردنش سادهتر از روش سنتی data-binding دو طرفه باشه.
پکیج react-scripts
یه مجموعه از اسکریپتهاست که توی create-react-app برای ایجاد سریع و ساده پروژه ریاکتی ازشون استفاده میشه. دستور react-scripts start
محیط توسعه کد رو ایجادمیکنه و یه سرور براتون استارت میکنه که از لود درلحظه و داغ ماژولها پشتیبانی میکنه.
این پایین به یه سری از ویژگیهای create-react-app رو لیست میکنیم.
پشتیبانی کامل از React، JSX، ES6، Typescript و Flow
Autoprefixed CSS
CSS Reset/Normalize
سرور live development
یه اجرا کننده unit-test که پشتیبانی built-in برای گزارش coverage داره
یه اسکریپت build برای bundle کردن فایلهای JS، CSS و تصاویر که برای استفاده production با قابلیت hash و sourcemap عمل میکنه
یه سرویس ورکر برای استفاده به صورت offline-first که قابلیت استفاده به صورت web-app و pwa رو فراهم میکنه
متد ReactDOMServer#renderToNodeStream
برای تولید HTML روی سرور و ارسال اون به درخواست initial کاربر استفاده میشه که باعث میشه صفحات سریعتر لود بشن. البته علاوه بر سرعت، به موتورهای جستجو این امکان رو میده که وبسایت شما رو به سادگی crawl کنن و SEO سایت بهتر بشه.
Note: البته یادتون باشه که این متد توی مرورگر قابل اجرا نیست و فقط روی سرور کار میکنه.
MobX یه راهحل ساده، scalable برای مدیریت state هست که خیلی قوی تست شده. این روش برای برنامهنویسی تابعی کنشگرا(TFRP) استفاده میشه. برای برنامههای ریاکتی لازمه که پکیجهای زیر رو نصب کنین:
npm install mobx --save npm install mobx-react --save
این پایین به یه سری از اصلیترین تفاوتهای Redux و MobX اشاره میکنیم:
موضوع | Redux | MobX |
---|---|---|
تعریف | یه کتابخونه جاواسکریپتی هستش که امکان مدیریت state رو فراهم میکنه | یه کتابخونه جاواسکریپتی هستش که امکان مدیریت state به صورت کنشگرا رو فراهم میکنه |
برنامهنویسی | به صورت پایهای با ES6 نوشته شده | به صورت پایهای با ES5 نوشته بشه |
Store دیتا | فقط یه store برای مدیریت همه دادهها وجود داره | بیش از یه store برای ذخیره و مدیریت داده وجود داره |
کاربرد | به شکل اساسی برای برنامههای پیچیده و بزرگ استفاده میشه | برای برنامههای ساده بیشتر کاربرد داره |
پرفورمنس | نیاز به یه سری بهبودها داره | پرفورمنس بهتری ارائه میده |
چگونگی ذخیره داده | از آبجکت جاواسکریپت به عنوان store استفاده میکنه | از observable برای نگهداری داده استفاده میکنه |
نه، اجبار برای یادگرفتن es2015/es6 برای کار با ریاکت وجود نداره. ولی توصیه شدیدی میشه که یاد بگیریدش چون منابع خیلی زیادی هستن که به شکل پیشفرض با es6 کار شدن. بزارین یه نگاه کلی به مواردی که الزاما با es6 کارشدن رو ذکر کنیم:
Destructuring: برای گرفتن مقادیر prop و استفاده از اونا توی کامپوننت
// in es 5 var someData = this.props.someData; var dispatch = this.props.dispatch; // in es6 const { someData, dispatch } = this.props;
عملگر spread: به پاس دادن propها به پایین برای کامپوننتهای فرزند کمکمیکنه
// in es 5 <SomeComponent someData={this.props.someData} dispatch={this.props.dispatch} /> // in es6 <SomeComponent {...this.props} />
توابع arrow: کدها رو کم حجمتر میکنه
// es 5 var users = usersList.map(function (user) { return <li>{user.name}</li>; }); // es 6 const users = usersList.map((user) => <li>{user.name}</li>);
Concurrent rendering باعث میشه برنامه ریاکتی بتونه توی رندر کردن درخت کامپوننتها به شکل مسئولانهتری عمل کنه و انجام این رندر رو بدون بلاک کردن thread اصلی مرورگر انجام بده. این امر به ریاکت این اجازه رو میده که بتونه اجرا شدن یه رندر طولانی رو به بخشهای مرتب شده بر اساس اولویت تقسیم کنه و توی پیکهای مختلف رندر رو انجام بده. برای مثال وقتی حالت concurrent فعال باشه، ریاکت یه نیم نگاهی هم به بقیه تسکهایی که هنوز انجام نشدن داره و اگه تسک با اولویت دیگهای رو ببینه، حالت فعلی که داشت رندر میکرد رو متوقف میکنه و به انجام کار با اولویتتر میرسه. این حالت رو به دو روش میشه فعال کرد:
۱. برای یه بخش از برنامه با wrap کردن کامپوننت توی تگ concurrent:
<React.unstable_ConcurrentMode> <Something /> </React.unstable_ConcurrentMode>
۲. برای کل برنامه با استفاده از createRoot موقع رندر
ReactDOM.unstable_createRoot(domNode).render(<App />);
هر دوتاشون به یه چیز اشاره میکنن. قبلا حالت concurrent با عنوان "Async Mode" توسط تیم ریاکت معرفی میشد. عنوان این قابلیت به این دلیل تغییر پیدا کرد که قابلیت ریاکت برای کار روی مرحلههای با اولویت متفاوت رو نشون بده. همین موضوع جلوی اشتباهات در مورد طرز تفکر راجع به رندر کردن async رو میگیره.
آره، میشه از javascript: استفاده کرد ولی یه warning توی کنسول برامون نشون داده میشه. چون آدرسهایی که با javascript: شروغ میشن خطرناکن و میتونن باعث ایجاد باگ امنیتی توی برنامه بشن.
const companyProfile = { website: "javascript: alert('Your website is hacked')", }; // It will log a warning <a href={companyProfile.website}>More details</a>;
البته بخاطر داشته باشین که نسخههای بعدی ریاکت قراره بجای warning یه ارور برای این مورد throw کنن.
پلاگین ESLint میاد یهسری قوانین برای صحیح نوشتن هوکها توی برنامه رو الزامی میکنه. روش تشخیص دادن هوکها هم اینطوریه که میگه اگه اسم تابعی با ”use” شروع بشه و درست بعد اون یه حرف بزرگ بیاد پس اون تابع هوک هستش. این پلاگین در حالت پایه این دوتا شرط رو الزام میکنه:
فراخوانی هوکها یا باید داخل یه تابع که عنوانش PascalCase هست (منظور یه کامپوننته) یا یه تابع دیگه که مثلا useSomething هست (custom هوک) انجام بشه.
هوکها باید توی همه رندرها با یه ترتیب مشخص اجرا بشن و هیچ شرطی چیزی نباشه که یه بار اجازه اجرای هوک رو بده و دفعه دیگه اجازه نده.
یه کامپوننت ساده UI رو تصور کنین، مثلا یه دکمه "لایک". وقتی که روش کلیک میکنین رنگ از خاکستری به آبی تغییر پیدا میکنه و اگه دوباره کلیک کنید باز خاکستری میشه.
رویش imperative برای انجام این کار اینطوریه:
if (user.likes()) { if (hasBlue()) { removeBlue(); addGrey(); } else { removeGrey(); addBlue(); } }
لازمه اول بررسی کنیم که چه چیزی رو توی اسکرین داریم نمایش میدیم و بعدش بیایم state رو عوض کنیم به حالتی که میخواییم برامون نمایش انجام بشه، توی برنامههای بزرگ و واقعی مدیریت این حالتها خیلی میتونه سخت باشه.
در حالت مقابل، روش declarative میتونه اینطوری باشه:
if (liked) { return <blueLike />; } else { return <greyLike />; }
چون روش declarative حالتها رو جدا در نظر میگیره، این بخش از کد براساس state فقط تصمیم میگیره که چه ظاهری رو نمایش بده و به همین دلیل درک کردنش سادهتره.
یه سری از مزایای استفاده از typescript با Reactjs اینا هستن:
میتونیم از آخرین ویژگیهای جاواسکریپت استفاده کنیم
از interfaceها برای تعریف نوعهای دلخواه و پیچیده استفاده کنیم
IDEهایی مثل VS Code برای TypeScript ساخته شدن
با افزایش خوانایی و Validation از خطاهای ناخواسته جلوگیری کنیم