React reconciliation
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: پنج دقیقه

در پروژه‌های React، نقطه‌ی آغازین فرآیند rendering، قطعه کد زیر میباشد که درون فایل index.js قرار دارد:
ReactDOM.render(<App />, document.getElementById('root'));
 
توسط متد ReactDOM.render یک وهله از کامپوننت App ایجاد شده و منجر به فراخوانی متد render کامپوننت مربوطه خواهد شد. درون متد ذکر شده ممکن است چندین کامپوننت تعریف شده باشند. در نتیجه به ازای هر کامپوننت، متد render متناظر با آن فراخوانی خواهد شد. در نهایت یک ساختار سلسله مراتبی از عناصر HTML تشکیل شده و توسط آرگومان دوم متد فوق، درون عنصری با آی‌دی root درج خواهند شد. 
برای مثال کامپوننت‌های زیر را در نظر بگیرید که هر کدام درون فایل مربوط به خودشان هستند:
// List.js
import React, { Component } from 'react';
import { ActionButton } from './ActionButton';
export class List extends Component {
    constructor(props) {
        super(props);
        this.state = {
            items: [
                { id: 1, title: "Item 1" },
                { id: 2, title: "Item 2" },
                { id: 3, title: "Item 3" },
                { id: 4, title: "Item 4" },
                { id: 5, title: "Item 5" },
            ]
        };
    }

    reverse = () => {
        this.setState({ items: this.state.items.reverse() });
    }

    render() {
        console.log("Render List Component");
        return (
            <div className="list-container">
                <ActionButton callback={this.reverse} />
                <ul>
                    {this.state.items.map(item => {
                        return <li key={item.id}>
                            {item.title}
                        </li>
                    })}
                </ul>
            </div>
        );
    }
}

// ActionButton.js
import React, { Component } from 'react';
export class ActionButton extends Component {
    render() {
        console.log("Render ActionButton Component");
        return (
            <button onClick={this.props.callback}>Click me</button>
        );
    }
}
در نهایت درون کامپوننت App.js نیز همچین ساختاری خواهیم داشت:
import React from 'react';
import './App.css';
import { List } from './List';

function App() {
  console.log("Render App Component");
  return (
    <div className="App">
      <h1>Reconciliation Process</h1>
      <List />
    </div>
  );
}

export default App;

 
درون متد render هر کدام از کامپوننت‌های فوق یک console.log نوشته شده است. با اجرای برنامه خروجی زیر در کنسول مرورگر قابل مشاهده است:
Render App Component
Render List Component
Render ActionButton Component

دلیل آن نیز این است که با اجرای اپلیکیشن، React از تمامی کامپوننت‌ها درخواست فراخوانی متد renderشان را خواهد کرد. به محض اینکه محتوای HTML درون صفحه نمایش داده شد، اپلیکیشن در وضعیتی تحت عنوان reconciled قرار خواهد گرفت؛ در این مرحله، خروجی نمایش داده شده با state کامپوننت‌ها سازگار است. React منتظر خواهد بود تا تغییری رخ دهد که در بیشتر اپلیکیشن‌ها این تغییر توسط کاربر انجام خواهد گرفت که در نهایت منجر به فراخوانی متد setState میشود. متد setState در واقع state dataی یک کامپوننت را بروزرسانی میکند؛ اما اینکار کامپوننت را stale یا کهنه میکند. یعنی محتوای HTMLی نمایش داده شده به کاربر قدیمی خواهد شد و با تنها یک رخداد ممکن است چندین state data تغییر کنند. این کار باعث خواهد شد که متد render برای تمامی کامپوننت‌های تغییر کرده فراخوانی شود. اما از آنجائیکه بروزرسانی DOM عملی هزینه‌بر است، در نتیجه React محتوای قبلی (کش شده با عنوان Virtual DOM) را با محتوای جدید مقایسه میکند تا کمترین میزان بروزرسانی DOM را داشته باشد. به این فرآیند Reconciliation گفته میشود.
برای درک بهتر این فرآیند، درون کامپوننت List یک آی‌دی به عنصر ul اضافه خواهیم کرد:
<ul id="list">
    {this.state.items.map(item => {
        return <li key={item.id}>
            {item.title}
        </li>
    })}
</ul>

اکنون در حالیکه پروژه در حال اجرا است، کد زیر را درون کنسول مرورگر اجرا کنید:
document.getElementById("list").classList.add("message")

همانطور که مشخص است کد فوق کلاسی با نام message را به عنصر ul اضافه کرده است:

.message {
  border: 1px solid green;
  padding: 2rem;
}


اکنون وقتی بر روی دکمه Click me کلیک کنیم، محتوای درون کامپوننت فوق تغییر پیدا میکند، اما عنصر ul همچنان دارای کلاس message است؛ دلیل آن نیز همانطور که عنوان شد این است که React محتوای تولید شده توسط کامپوننت List را با Virtual DOM خودش مقایسه میکند و چون از لحاظ ساختار DOM با هم برابر هستند تغییری در ساختار خروجی کامپوننت ایجاد نمیکند و فقط قسمت‌هایی را که تغییر کرده‌اند، بروزرسانی خواهد کرد.


اکنون کامپوننت فوق را اینگونه تغییر خواهیم داد:

import React, { Component } from 'react';
import { ActionButton } from './ActionButton';
export class List extends Component {
    constructor(props) {
        // as before
    }

    reverse = () => {
        this.setState({ items: this.state.items.reverse(), wrapInDiv: true });
    }

    generateElement = () => {
        const list = <ul id="list">
            {this.state.items.map(item => {
                return <li key={item.id}>
                    {item.title}
                </li>
            })}
        </ul>;

        return this.state.wrapInDiv ? <div>{list}</div> : list;
    }

    render() {
        console.log("Render List Component");
        return (
            <div className="list-container">
                <ActionButton callback={this.reverse} />
                {this.generateElement()}
            </div>
        );
    }
}

  در اینجا اگر مجدداً کلاس ذکر شده را به عنصر ul اضافه کنیم و سپس بر روی دکمه کلیک کنیم، خواهیم دید که کلاس message از عنصر ul حذف خواهد شد. دلیل آن نیز این است که ساختار HTML با Virtual DOM یکی نیست و React کامپوننت موردنظر را با تغییرات جدید مجدداً رندر خواهد کرد. 

نکته: برای مشاهده تغییرات DOM می‌توانید قابلیت Paint flashing را در مرورگر Chrome از قسمت Developer Tool > More tools > Rendering فعال کنید و تغییرات را به صورت visual ببینید.
  • #
    ‫۴ سال و ۱۱ ماه قبل، یکشنبه ۳۱ شهریور ۱۳۹۸، ساعت ۱۷:۲۱
    وقت بخیر

    از آنجا که دست خط نمونه کد‌های موجود در این وب‌سایت، دست خط کد نویسی خیلی از افراد خواهد شد، پیشنهاد می‌کنم نمونه‌های React با دست خط Hooks نوشته بشه.
    • #
      ‫۴ سال و ۱۱ ماه قبل، یکشنبه ۳۱ شهریور ۱۳۹۸، ساعت ۲۰:۴۲
      ممنون از دقت نظر شما؛
      البته هدف از این مطلب آشنایی با فرآیند رندر شدن کامپوننت‌ها میباشد. در مطلب بعدی به Hooks هم اشاره خواهیم کرد.