ریکت
19 شهریور 1402
ریکت خودت رو بساز - قسمت اول
داخل این سری قرار هست ریکت خودمون رو از پایه قدم به قدم بنویسیم. قرار هست براساس معماری واقعی ریکت پیش بریم اما از بهینه سازی و feature های غیرضروری صرف نظر میکنیم.
این آیتم ها مواردی هست که داخل version ایی که قرار هست بنویسیم اضافه میکنیم:
- قدم اول: The
createElement
Function - قدم دوم: The
render
Function - قدم سوم: Concurrent Mode
- قدم چهارم: Fibers
- قدم پنجم: Render and Commit Phases
- قدم ششم: Reconciliation
- قدم هفتم: Function Components
- قدم هشتم: Hooks
قدم صفر: Review
اول از همه بیایید چند تا موضوع پایه ایی رو مرور کنیم. این قسمت رو میتونید رد کنید اگر دید خوبی نسبت به نحوه کار ریکت، JSX و المان های DOM دارید.
به این سه خط از یک برنامه ریکت توجه کنید. خط اول یک المان ریکت رو تعریف میکنه. خط بعدی میاد node رو از DOM میگیره. خط آخر هم المان رو داخل container
رندر میکنه.
const element = <h1 title="foo">Hello</h1>
const container = document.getElementById("root")
ReactDOM.render(element, container)
خوب حالا بیاید کدهای ریکت رو حذف کنیم و به vanilla JavaScript تبدیلش کنیم.
توی خط اول یک element داریم، که JSX هست. در واقع JS به کمک ابزار هایی مثل Babel، میاد JSX رو به JS تبدیل میکنه. تبدیل کردنش هم معممولا راحت هست: لازم هست createElement
رو صدا بزنیم و اسم tag، فرزندان و props رو به عنوان پارامتر بهش پاس بدیم. در نهایت React.createElement
یک object از آرگومان هایی که پاس دادیم ایجاد میکنه.
const element = React.createElement(
"h1",
{ title: "foo" },
"Hello"
)
در واقع element همین هست، یک آبجکت با دو پراپرتی: type
و props
(در واقع بیشتر هست، ولی ما فعلا با این دو تا کار داریم)
type
یک string هست که مشخص کننده تایپ DOM node ایی هست که قرار ایجاد کنیم، در واقع یک tagName
ایی هست که به document.createElement
زمان ایجاد HTML element پاس میدیم. همچنین ممکن هست تابع هم باشه که بعدا بررسی میکنیم.
props
یک آبجکت دیگه هست که key و value های JSX attributes رو نگهداری میکنه. همچنین که پراپرتی ویژه به نام children
داره.
در اینجا children
ما string هست، اما معمولا آرایه ایی هست از چندین element.
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello",
},
}
در مرحله بعد لازم هست ReactDOM.render
رو هم جایگزین کنیم. render
جایی هست که ریکت DOM رو آپدیت میکنه، پس بیاید آپدیت رو خودمون انجام بدیم.
اول از همه لازم هست node رو به کمک element type
که اینجا h1
هست ایجاد کنیم. حالا تمام prop
های element رو به node میایم اختصاص (assign) میدیم. اینجا فقط title
هست.
*برای اینکه گیج نشید، "element" به React elements و "node" به DOM elements اشاره میکنه.
const node = document.createElement(element.type)
node["title"] = element.props.title
بعدش برای فرزند ها node ایجاد میکنیم. چون اینجا فقط string داریم، پس میایم text node ایجاد میکنیم. بعدش children رو بهش assign میکنیم.
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
در نهایت h1
رو به textNode
میاییم append میکنیم و خود h1
رو هم به container
.
node.appendChild(text)
container.appendChild(node)
حالا همون app قبلی رو داریم با این تفاوت که از ریکت استفاده نکردیم:
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello",
},
}
const container = document.getElementById("root")
const node = document.createElement(element.type)
node["title"] = element.props.title
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
node.appendChild(text)
container.appendChild(node)
createElement
Function
قدم اول: The خوب بیاید یک app دیگه رو درنظر بگیریم. این بار قرار هست ما کد ریکت رو با ریکت ایی که ورژن خودمون هست جایگزین کنیم.
با نوشتن createElement
خودمون شروع میکنیم. برای اینکه متوجه بشیم createElement
کجا استفاده میشه، بیاید JSX رو به JS تبدیل کنیم. همینطور که توی قدم صفر گفتیم، element یک آبجکت با type
و props
هست. تنها کاری که تابع ما لازم هست انجام بده ایجاد این آبجکت هست.
const element = React.createElement(
"div",
{ id: "foo" },
React.createElement("a", null, "bar"),
React.createElement("b")
)
ما از spread operator برای props
و rest parameter syntax برای children
استفاده میکنیم.
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children,
},
}
}
توی این روش children
همیشه یک آرایه هست. بطور مثال فرض کنید، createElement("div")
این رو برمیگردونه:
{
"type": "div",
"props": { "children": [] }
}
createElement("div", null, a)
این رو برمیگردونه:
{
"type": "div",
"props": { "children": [a] }
}
و createElement("div", null, a, b)
این رو برمیگردونه:
{
"type": "div",
"props": { "children": [a, b] }
}
همچنین آرایه children
شامل مقدارهای primitive مثل string یا number میتونه باشه. در نتیجه هر چیزی که آبجکت نیست رو جدا میکنیم و براش element جدا تعریف میکنیم.
ریکت برای مقدارهای primitive یا زمانی که children
نداره یک آرایه خالی ایجاد نمیکنه، اما ما برای ساده سازی کد اینکار رو میکنیم، چون الان سادگی رو درنظر گرفتیم نه optimize بودن!
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === "object"
? child
: createTextElement(child)
},
}
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
}
}
خوب برای اینکه دیگه از createElement
ریکت استفاده نکنیم، لازم هست به library ایی که داریم مینویسیم یک اسم بدیم تا بتونیم ازش استفاده کنیم. ما Didact
صداش میزنیم.
const Didact = {
createElement,
}
const element = Didact.createElement(
"div",
{ id: "foo" },
Didact.createElement("a", null, "bar"),
Didact.createElement("b")
)
اما ما دلمون میخواد از JSX استفاده کنیم. چطوری به babel بگیم از createElement
کتابخونه خودمون که Didact هست استفاده کنه بجای ریکت؟
با استفاده از این کامنت ریکت زمان تبدیل کردن JSX به JS از تابع ایی که ما تعریف کردیم استفاده میکنه:
/** @jsx Didact.createElement */
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
)
render
Function
قدم دوم: The خوب حالا میخوایم تابع ReactDOM.render
خودمون رو بنویسیم. الان فقط به اینکه چیزی به DOM اضافه بشه اهمیت میدیم و آپدیت و حذف کردن رو برای بعدا میذاریم.
برای شروع میاییم، ابتدا DOM node رو با استفاده از تایپ element ایجاد میکنیم و بعدش node جدید رو به container میاییم append میکنیم.
function render(element, container) {
const dom = document.createElement(element.type)
container.appendChild(dom)
}
لازم هست بصورت بازگشتی اینکار برای تمام فرزندان انجام بشه:
function render(element, container) {
const dom = document.createElement(element.type)
element.props.children.forEach(child =>
render(child, dom)
)
container.appendChild(dom)
}
همچنین میایم این رو هم هندل میکنیم که اگه تایپ المنت TEXT_ELEMENT
بودش بیایم text node ایجاد کنیم:
function render(element, container) {
const dom =
element.type == "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type)
element.props.children.forEach(child =>
render(child, dom)
)
container.appendChild(dom)
}
آخرین چیزی که نیاز داریم این هست که prop های المنت هم داخل node قرار بگیرند:
function render(element, container) {
const dom =
element.type == "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type)
const isProperty = key => key !== "children";
Object.keys(element.props)
.filter(isProperty)
.forEach(name => {
dom[name] = element.props[name]
})
element.props.children.forEach(child =>
render(child, dom)
)
container.appendChild(dom)
}
حالا یک library داریم که میتونه JSX رو داخل DOM رندر کنه:
خوب بچه ها امیدوارم این قسمت براتون مفید بوده باشه. خوشحال میشم اگه انتقادی یا پیشنهادی دارید برام بنویسید تا محتوای بهتری تولید بشه ❤️
این پست برات مفید بود؟
0
0
0
0
نظرات