logo
ریکت خودت رو بساز - قسمت اول
ریکت

ریکت

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)

قدم اول: The createElement Function

خوب بیاید یک 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>
)

قدم دوم: The render Function

خوب حالا میخوایم تابع 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 رندر کنه:


خوب بچه ها امیدوارم این قسمت براتون مفید بوده باشه. خوشحال میشم اگه انتقادی یا پیشنهادی دارید برام بنویسید تا محتوای بهتری تولید بشه ❤️

منبع: https://pomb.us/build-your-own-react/

این پست برات مفید بود؟

0

heart

0

like

0

happy

0

sad