در این پست می خواهیم با استفاده از کتابخانه React، بازی XO را ایجاد کنیم. تکنیک هایی که شما در این پست فرا می گیرید اساس ساخت هر برنامه React است و تسلط بر آن باعث ایجاد درک عمیق از این کتابخانه می شود.
این آموزش به بخش های زیر تقسیم می شود:
-
آماده سازی اولیه برای شروع پروژه
-
بررسی اصول اولیه React شامل state، props و Component
-
توسعه پروژه بازی که طی آن با اغلب تکنیک های رایج توسعه اپلیکیشن های مبتنی بر React آشنا خواهید شد
-
اضافه کردن Time travel که باعث ایجاد درک عمیق تر نسبت به نقاط قوت React خواهد شد.
در ادامه مطلب همراه من باشید.
می توانید این پروژه را به صورت کامل از این لینک دریافت کنید. اگر با کدها آشنا نیستید نگران نباشید هدف این پست کمک به شما در درک React و syntax آن می باشد.
پیشنهاد می کنیم قبل از شروع آموزش، بازی XO را بررسی کنید. یکی از عناصری که باید در پروژه به آن توجه کنید لیستی است که در طرف راست Board بازی قرار دارد. این لیست تاریخچه حرکاتی که در بازی انجام شده است را نمایش می دهد و با پیش رفتن در بازی به روز می شود.
از طراحی قالب ساده بازی در این آموزش شروع می کنیم و فرض می کنیم که شما با HTML، Javascript و مفاهیم برنامه نویسی مانند: function، object، آرایه و کمی هم با کلاس ها آشنا هستید.
توجه داشه باشید که در این آموزش از برخی ویژیگی های ES6 )نسخه آخر جاوااسکریپت( مانند: arrow function، class
، let
و const
استفاده می کنیم. علاوه بر این شما می توانید از BabelREPL جهت بررسی اینکه کد ES6 به چه کدی کامپایل می شود استفاده کنید.
React چیست؟
یک کتابخانه منعطف و تاثیرگذار که مبتنی بر جاوااسکریپت می باشد و برای ساخت رابط های کاربری از آن استفاده می شود. پارادایم این کتابخانه از نوع اعلانی می باشد و به شما این امکان را می دهد تا UI های پیچیده را با استفاده از قسمت های مستقل و کوچکتر که کامپوننت نامیده می شوند ایجاد کنید.
چند نوع کامپوننت در React وجود دارد که با زیر کلاس React.Component شروع می کنیم:
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
</div>
);
}
}
کامپوننت ها برای توصیف چیزی که می خواهیم در صفحه نمایش مشاهده کنیم استفاده می شوند. همچنین زمانی که داده های کامپوننت تغییر می کند، مجددا رندر و به روز رسانی می شود.
در اینجا ShopingList یک React component class یا React component type می باشد. یک کامپوننت پارامترهایی که props (کوتاه شده properties) نامیده می شود را دریافت می کند و سلسله مراتبی از view ها را با استفاده از متد render نمایش می دهد.
متد render شامل توصیف آنچیزی است که می خواهیم روی صفحه، نمایش دهیم. React توصیف را دریافت و نتیجه را نمایش می دهد. متد render یک React element را بر می گرداند که در واقع همان توصیفی است که پیشتر در ارتباط با آن توضیح دادیم. اکثر توسعه دهنده های React از Syntax خاصی به نام JSX استفاده می کنند که ساختار کد را ساده تر می کند. به عنوان مثال در JSX، <div/>
به React.createElement(‘div’)
تبیدل می شود. مثال بالا معادل با:
return React.createElement('div', {className: 'shopping-list'},
React.createElement('h1', /* ... h1 children ... */),
React.createElement('ul', /* ... ul children ... */)
);
می باشد.
در JSX می توانیم از هر عبارت جاوااسکریپت بین آکولاد استفاده کنیم. هر عنصر React یک شی جاوااسکریپت است و می توان آن را در متغیر ذخیره کرد یا از آن در جای دیگری از برنامه استفاده کرد.
در کد بالا کامپوننت ShopingList تنها عناصر DOM مانند <div/>
و <li/>
را render می کند علاوه بر این می توان کامپوننت های React را ترکیب و render کرد. برای مثال می توانیم از طریق تگ <ShopingList/> در کامپوننت های دیگر به این کامپوننت دسترسی داشته باشیم. هر کامپوننت React به صورت بسته بندی شده می باشد و می تواند به صورت مستقل عمل کند و این مورد به ما اجازه می دهد تا UI های پیچیده را با استفاده از کامپوننت ها، ساده تر ایجاد کنیم.
بررسی کد شروع
ابتدا یک فایل HTML ایجاد می کنیم و نام آن را index قرار می دهیم و کدهای زیر را در آن وارد می کنیم:
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>Tic Tac Toe</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="errors" style="background: #c00;color: #fff;display: none;margin: -20px -20px 20px;padding: 20px;white-space: pre-wrap;">
</div>
<div id="root">
</div>
<script src='js/react-development.js'></script>
<script src='js/react-dom-development.js'></script>
<script src="js/index.js"></script>
</body>
</html>
در پوشه پروژه سپس پوشه js، فایل index.js را باز کنید. در این آموزش ما سه کامپوننت خواهیم داشت:
- Square
- Board
- Game
Square کامپوننت یک عنصر <button>
، کامپوننت Board تعداد 9 کامپوننت Square و کامپوننت Game هم کامپوننت Board را با مقادیر placeholder که بعدا آن را ویرایش می کنیم render می کنند. تا اینجا هیچ کامپوننت محاوره ای وجود ندارد.
ارسال داده از طریق Props
در متد renderSquare
کامپوننت Board، برای ارسال یک prop به کلاس Square که نام آن value می باشد، کد را به صورت زیر تغییر می دهیم:
class Board extends React.Component {
renderSquare(i) {
return <Square value={i} />;
}
همچنین متد render کامپوننت Square را به صورت زیر تغییر می دهیم:
class Square extends React.Component {
render() {
return (
<button className="square">
{this.props.value}
</button>
);
}
}
تغییرات اعمال شده به صورت زیر است:
قبل از ارسال value:
بعد از ارسال value:
تا اینجا توانستیم یک prop را از کامپوننت والد یعنی Board به کامپوننت فرزند یعنی Square ارسال کنیم.
ایجاد کامپوننت محاوره ای
حالا می خواهیم زمانی که روی Square کلیک شد این کامپوننت با یک حرف “X” پر شود. ابتدا کد render کامپوننت Square را به صورت زیر تغییر می دهیم:
class Square extends React.Component {
render() {
return (
<button className="square" onClick={function() { alert('click'); }}>
{this.props.value}
</button>
);
}
}
نکته:
به منظور حجم کمتر کد و جلوگیری از سردرگمی در رفتار this از arrow function برای event handler استفاده می کنیم.
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => alert('click')}>
{this.props.value}
</button>
);
}
}
توجه داشته باشید که چگونه به جای onClick={() => alert('click')}
،
یک تابع را برای onClick قرار دادیم. این تابع فقط در زمان کلیک اجرا می شود. فراموش کردن ()=> و نوشتن onClick={alert('click')}
یک اشتباه رایج است و هر بار که کامپوننت، مجددا render شود این تابع اجرا می شود.
حال اگر روی Square کلیک کنیم یک alert در مرورگر نمایش داده می شود.
در قدم بعدی می خواهیم کامپوننت Square حالت خود را حفظ کند (با “X” پر شده است یا نه). برای حفظ کردن یا به خاطر داشتن هر چیزی در React از state استفاده می کنیم.
کامپوننت های React می توانند با تنظیم this.state در constructor مربوط به خودشان حالت خود را نگهداری کنند. this.state ها به صورت خصوصی در کامپوننت تعریف می شوند و کامپوننت های دیگر به آن دسترسی ندارند. حالا می توانیم مقدار جاری کامپوننت Square را در this.state ذخیره کنیم و زمانی که Square کلیک شد آن را تغییر دهیم.
ابتدا ما متد constructor را به صورت زیر به کامپوننت Square اضافه می کنیم:
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button className="square" onClick={() => alert('click')}>
{this.props.value}
</button>
);
}
}
نکته:
به خاطر داشته باشید که درزیر کلاس های جاوااسکریپت (کلاس هایی که extend شده اند) زمانی که constructor را تعریف می کنید باید super
را در constructor فراخوانی کنید. همه کلاس های React.Component که دارای constructor می باشند باید با فراخوانی super(props)
آغاز شوند.
حالا جهت نمایش مقدار state کامپوننت Square در زمان کلیک شدن، متد render این کامپوننت را به صورت زیر تغییر می دهیم.
- جایگزین کردن
this.props.value
به جایthis.state.value
در تگ button - جایگزین کردن
()=> alert()
با()=> this.setState({value:’X’})
در onClick - className و onClick را در 2 خط جدا قرار دادیم تا کد ما از خوانایی بالاتری برخوردار باشد
پس از اعمال تغییرات، تگ <button> که توسط متد render کامپوننت Square بازگردانده می شود باید به صورت زیر باشد:
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return (
<button
className="square"
onClick={() => this.setState({value: 'X'})}
>
{this.state.value}
</button>
);
}
}
در واقع ما با فراخوانی this.setState
از onClick در متد render کامپوننت Square به React اعلام می کنیم که هر زمان که روی <button> کلیک شد، کامپوننت Square را render مجدد کند. پس از به روز رسانی، this.state.value برابر با ‘X’ می شود و می توانیم آن را روی Borad بازی ببینیم. اگر روی هر Square کلیک کنیم ‘X’ نمایش داده می شود.
زمانی که شما setState را در یک کامپوننت فراخوانی می کنید React به صورت خودکار همه کامپوننت های فرزند داخل آن کامپوننت را به روز می کند.
ابزار توسعه دهنده
افزونه React Devtools برای مرورگرهای Chrome و Firefox این امکان را به ما می دهد تا درخت کامپوننت های React را ردیابی کنیم. پس از نصب افزونه و برای مشاهده آن، در صفحه مرورگر کلیک راست کرده و گزینه Inspect element را انتخاب کنید، تب آخر مربوط به افزونه React Devtools می باشد.
همچنین می توانید در React Devtools مقادیر props و state های مربوط به کامپوننت های React را چک کنید.
ادامه توسعه بازی
حالا بلاک های پایه بازی XO را ایجاد کردیم. برای تکمیل بازی نیاز داریم تا “X” و “O” به شکل متناوب در Borad بازی قرار بگیرند و پس از آن برنده بازی را مشخص کنیم.
انتقال State به سطح بالاتر
در حال حاضر هر Square حالت بازی را جداگانه نگهداری می کند. برای مشخص کردن برنده بازی باید مقدار هر 9 Square را در یک جا نگهداری کنیم.
کامپوننت Board می تواند از هر Square مقدار State را درخواست کند، با اینکه این روش در React امکان پیاده سازی دارد اما باعث پیچیده شدن کد و کاهش خوانایی آن می شود و کد ما مستعد ایجاد باگ خواهد شد. می توانیم حالت بازی را در کامپوننت والد یعنی Board به جای هر Square نگهداری کنیم. کامپوننت Borad می تواند با ارسال Prop به هر Square مشخص کند که چه چیزی باید در Square نمایش داده شود، دقیقا مانند ارسال یک عدد از طریق Prop به Square که پیش تر انجام دادیم.
برای جمع آوری داده از چندین فرزند، یا داشتن ارتباط بین دو فرزند، باید یک state اشتراکی در کامپوننت والد تعریف کنیم. کامپوننت والد می تواند با استفاده از props، state را به فرزندان برگرداند که باعث همگام سازی کامپوننت های فرزند با یکدیگر و همچنین کامپوننت والد می شود.
یک تابع constructor به کامپوننت Borad اضافه می کنیم و حالت اولیه state را برابر با یک آرایه 9 قسمتی که همه آنها null می باشد قرار می دهیم.
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
renderSquare(i) {
return <Square value={i} />;
}
بعدا زمانی که بازی را شروع کردیم آرایه this.state.squares
کامپوننت Borad به صورت زیر خواهد بود:
[
'O', null, 'X',
'X', 'X', 'O',
'O', null, null,
]
متد renderSquare
کامپوننت Borad به صورت زیر خواهد بود.
renderSquare(i) {
return <Square value={i} />;
}
در ابتدای آموزش مقادیر را از کامپوننت Board به کامپوننت Square ارسال کردیم تا اعداد را نمایش دهد. حالا در اینجا ما اعداد را با نشانه “X” که توسط حالت Square مشخص شده جایگذاری می کنیم. به همین دلیل است که کامپوننت Square مقدار ارسال شده توسط Board را نادیده می گیرد.
در اینجا دوباره از مکانیسم ارسال prop استفاده می کنیم و کامپوننت Board را برای ساخت Square با مقدار جاری خودش یعنی ‘X’، ‘O’ یا null تغییر می دهیم. در حال حاضر آرایه squares را در constructor کامپوننت Board تعریف کردیم و حالا باید متد renderSquare را برای خواندن از آن تغییر دهیم.
renderSquare(i) {
return <Square value={this.state.squares[i]} />;
}
حالا هر Square یک prop به نام value دریافت می کند که می تواند ‘X’، ‘O’ یا null برای Square های خالی باشد.
در قدم بعد، ما باید عملی که در زمان کلیک Square انجام می شده را تغییر دهیم. در حال حاضر کامپوننت Board، Square هایی را که پر شده است را نگهداری می کند. ما باید از کامپوننت Square مقادیر State کامپوننت Borad را به روز رسانی کنیم. از آنجایی که State در کامپوننتی که تعریف می شود به صورت خصوصی است نمی توانیم به صورت مستقیم از Square، مقدار State کامپوننت Board را تغییر دهیم.
برای حفظ محرمانگی state کامپوننت Borad، ما یک function را از کامپوننت Board به Square ارسال می کنیم. این تابع زمانی فراخوانی می شود که Square کلیک شده باشد. متد renderSquare در کلاس Borad را به صورت زیر تغییر می دهیم:
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
حالا دو props را از کامپوننت Board به Square ارسال می کنیم (value و onClick). در واقع onClick تابعی است که کامپوننت Square می تواند در زمان کلیک فراخوانی کند. سپس تغییرات زیر را در کامپوننت Square اعمال می کنیم.
- جایگزین کردن
this.state.value
باthis.props.value
در متد render کامپوننت Square - جایگزین کردن
this.setState
باthis.props.onClick
در متد render کامپوننت Square - حذف constructor از کامپوننت Square به علت عدم وجود State در این کامپوننت
پس از اعمال تغییرات کامپوننت Square به شکل زیر خواهد بود:
class Square extends React.Component {
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()}
>
{this.props.value}
</button>
);
}
}
زمانی که Square کلیک می شود، تابع onClick ارائه شده توسط کامپوننت Board فراخوانی می شود. در اینجا این مورد را بیشتر بررسی می کنیم:
1. onClick مربوط به <button> به React اعلام می کند که یک click event listener در نظر بگیرد.
2. React زمانی که button کلیک می شود، onClick که در متد render کامپوننت Square تعریف شده است را فراخوانی می کند.
3.در اینجا this.props.onClick()
فراخوانی می شود. onClick کامپوننت Square توسط کامپوننت Board تعریف شده است.
4. کامپوننت Board onClick={() => this.handleClick(i)}
را به کامپوننت Square ارسال کرده است و Square زمانی که کلیک می شود this.handleClick(i)
را فراخوانی می کند.
5. البته ما هنوز handleClick()
را تعریف نکردیم و زمانی که روی Square کلیک می کنیم، با خطا مواجه می شویم که دلیل آن عدم تعریف متد handleClick
در کامپوننت Board است. در اینجا ما به صورت زیر این متد را به کامپوننت Borad اضافه می کنیم:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Next player: X';
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
پس از این تغییرات، جهت پر شدن Square دوباره روی آن کلیک می کنیم و اینبار خطا برطرف شده است. علاوه بر این state به جای کامپوننت Square در کامپوننت Board ذخیره می شود. زمانی که state مربوط به کامپوننت Board تغییر می کند، Square به صورت خودکار render مجدد می شود. نگهداری همه state های مربوط به Square ها در Board به ما این امکان را می دهد تا در ادامه برنده بازی را مشخص کنیم.
با توجه به اینکه کامپوننت Square از این به بعد state را نگهداری نمی کند، مقادیر مربوط به state را از کامپوننت Board درخواست می کند و زمانی که کلیک می شود به کامپوننت Borad اطلاع می دهد. در React به کامپوننت هایی مانند Square، کامپوننت کنترلی گفته می شود و کامپوننت Borad کنترل کاملی بر آنها دارد.
توجه داشته باشید که در متد handleClick
، تابع slice را برای کپی آرایه square فراخوانی کردیم، تا به جای آرایه موجود، آرایه کپی شده را ویرایش کنیم. در قسمت بعدی توضیح خواهیم داد چرا یک کپی از آرایه squaers ایجاد کردیم.
چرا تغییر ناپذیری مهم است
در قسمت قبل ما از تابع slice() برای ایجاد یک کپی از آرایه squares استفاده کردیم و به جای ویرایش مستقیم آرایه squares، کپی آن را ویرایش کردیم. در اینجا قصد داریم بررسی کنیم که تغییر ناپذیری چیست و چرا حائز اهمیت است.
به طور کلی دو روش برای تغییر داده وجود دارد. اولین روش تغییر داده به طور مستقیم است. دومین روش جایگزین کردن و تغییر دلخواه، در کپی داده است.
تغییر داده به صورت مستقیم
var player = {score: 1, name: 'Jeff'};
player.score = 2;
// Now player is {score: 2, name: 'Jeff'}
تغیییر داده به صورت غیر مستقیم
var player = {score: 1, name: 'Jeff'};
var newPlayer = Object.assign({}, player, {score: 2});
// Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'}
// Or if you are using object spread syntax proposal, you can write:
// var newPlayer = {...player, score: 2};
نتیجه نهایی هر دو یکسان می باشد اما روش غیر مستقیم دارای مزایای زیر می باشد:
ویژیگی های پیچیده ساده می شوند
تغییر ناپذیری، پیاده سازی ویژیگی های پیچیده را بسیار آسان تر می کند. در ادامه قصد داریم تا ویژگی Time Travel را که امکان بررسی تاریخچه بازی و بازگشت به حرکات قبلی را فراهم می کند پیاده سازی کنیم. این قابلیت مختص بازی ها نیست وبیشتر به صورت ویژگی Undo و Redo در اپلیکیشن ها موجود می باشد. با عدم استفاده از ویرایش مستقیم داده می توانیم تاریخچه بازی را نگهداری کنیم و بعدا از آن استفاده کنیم.
ردیابی تغییرات
ردیابی تغییرات در object های تغییر پذیر مشکل تر است زیرا به صورت مستقیم ویرایش شده است و در این صورت جهت ردیابی تغییرات باید object تغییر پذیر را با نسخه های کپی قبلی خودش مقایسه کنیم و کل درخت object پیمایش شود.
ردیابی تغییرات در object های تغییر ناپذیر به مراتب آسان تر می باشد. اگر object تغییر ناپذیری که به آن ارجاع داده شده با object قبلی تفاوت داشته باشد بنابراین object تغییر کرده است.
تعیین زمان render مجدد در React
با استفاده از تغییر داده به صورت غیر مستقیم می توانیم pure component را در React ایجاد کنیم. و در صورت تغییر object می توان به آسانی مشخص کرد که کامپوننت نیاز به render مجدد دارد.
کامپوننت های تابعی
در این قسمت قصد داریم تا کامپوننت Square را به یک کاپوننت تابعی تبدیل کنیم.
در React، کامپوننت تابعی راه ساده ای برای نوشتن کامپوننت هایی است که فقط دارای متد render هستند و state ندارند. به جای تعریف کلاس و ارث بری از React.Component، می توانیم یک تابع تعریف کنیم که props را به عنوان ورودی دریافت می کند و چیزی که باید render شود را باز می گرداند. کامپوننت های تابعی از لحاظ حجم کد نسبت به کلاس ها سبک تر هستند و می توان آن ها را راحت تر ایجاد کرد. بسیاری از کامپوننت ها از این طریق ایجاد می شوند.
کلاس Square را با تابع زیر جایگزین کنید.
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
در این کد this.props
به props
تغییر کرده است.
نکته
زمانی که ما Square را تغییر دادیم و آن را به یک کامپوننت تابعی تبدیل کردیم، onClick={() => this.props.onClick()}
را به
onClick={props.onClick}
تغییر دادیم (توجه داشته باشید که پرانتزهای هر دو طرف حذف شده). در کلاس ما از arrow function برای دسترسی به مقدار this استفاده کردیم اما در کامپوننت تابعی ما نیازی به this نخواهیم داشت.
تعیین نوبت
در این قسمت نقص آشکار در بازی را برطرف می کنیم. در بازی ما “O” نمی تواند روی Board بازی قرار بگیرد و با هربار کلیک فقط نشانه "X" روی Board قرار می گیرد.
ما به صورت پیش فرض اولین حرکت را “X” قرار دادیم که می توان با تغییر state در متد constructor کامپوننت Board آن را تغییر بدهیم.
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
};
}
هر بار کاربر حرکت می کند متغیر xIsNext که از نوع Boolean می باشد تغییر می کند و طبق مقدار این متغیر نوبت بعدی یعنی بازیگر “X” یا “O” تعیین و state بازی ذخیره می شود. حالا ما باید تابع handleClick را برای تغییر مقدار xIsNext تغییر دهیم.
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
با این تغییر “X” و “O” می توانند نوبت خود را دریافت کنند. حالا باید متن status را در متد render کامپوننت Board تغییر دهیم که به ما نشان می دهد نوبت حرکت کدام بازیکن است.
render() {
const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
return (
// the rest has not changed
پس از اعمال تغییرات، کامپوننت Borad باید به شکل زیر باشد:
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
};
}
handleClick(i) {
const squares = this.state.squares.slice();
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
renderSquare(i) {
return (
<Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>
);
}
render() {
const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
در بخش بعدی آموزش تابعی را برای تعیین برنده بازی ایجاد می کنیم و ویژیگی های بیشتری را به بازی اضافه می کنیم.
منبع: reactjs.org
React | Javascript |