نوشتن HOC در React به صورت Functional Component مثلا connect توی redux

تایتل خیلی طولانی شد . نه؟

چون مفهوم HOC هنوز به عنوان یک عبارت قابل جستجو ، جا نیفتاده .

میخوام ببینیم که چجوری میشه یک فانکشن نوشت شبیه به connect توی redux که زمان اکسپورت کردن کامپوننت ، داخل اون تابع این اتفاق بیفته .

تمام عبارتی که بالا گفتم رو بهش میگن HOC یا High-order-Component .

همونطور که میدونیم ، جاوا اسکریپت از بهترین زبان های Functional base موجوده . توابع نمیتونن مثل اشیاء ، اولویت داشته باشن . برای مثال شما میتونید داخل جاوا ، اولیت یک کلاس رو به یک کلاس دیگه با استفاده از extends و implement مشخص کنین . مثلا کلاس hello از کلاس HelloBase ارث میبره پس HelloBase اولویت بالاتری داره و از توابع میشه استفاده کرد .

خوب این مثال داخل جاوا اسکریپت از صفر شدنی نیست . مگر اینکه از es6 استفاده کنیم و مفهوم class که یک مفهوم "Syntactical Sugar " هست .

Classes are in fact "special functions", and just as you can define function expressions and function declarations, the class syntax has two components: class expressions and class declarations.

خوب برای اینکه بتونیم این مفاهیم رو داشته باشیم ، از high-order-functions استفاده میکنیم و میتونیم مفاهیم ارث بری رو پیاده کنیم .

داخل ری اکت به علت component base بودن ، مفهوم high-order-component یا HOC داریم .

من یک مدل خیلی راحت رو پیاده سازی کردم که از سمپل cra هست .

خوب فرض کنید که من یک کامپوننت دارم و میخوام تا زمانی که props ها موجود نیستند ، به من Loading... نمایش بده و وقتی props اومد ، محتویات کامپوننت . چیز پیچیده ای نیست . اما مین میخوام که Loading رو به شکل یک HOC بنویسم .

اول از همه App.js رو به شکل زیر مینویسم .

import React, {Component} from 'react';
import './App.css';
import Head from "./Head";

const App = () => {
    return (
        <div className="App">
            <Head hello={"Do it Now"}/>
        </div>
    );
}

export default App;

حالا یک نگاه میندازم به Head . کامپوننت Head چیز خاصی نیست .

تا الان اصلا کار پیچیده ای نکردم .

import React from "react";
import logo from "./logo.svg";

const Header=(props)=>{
    return (
        <header className="App-header">
            {props.hello}
            <img src={logo} className="App-logo" alt="logo" />
            <p>
                Edit <code>src/App.js</code> and save to reload.
            </p>
            <a
                className="App-link"
                href="https://reactjs.org"
                target="_blank"
                rel="noopener noreferrer"
            >
                Learn React
            </a>
        </header>
    )
}

export default Header;

خوب الان که پروژه اجرا بشه همه چیز سر جاشه .

اما من میخوام یک ثانیه بهم Loading رو نمایش بده و بعدش کاپوننت اجرا بشه .

خوب میام داخل پوشه HOC یک فایل به اسم Loading میسازم .

import React,{useState,useEffect} from "react"

export const Loading = WrappedComponent => {
    return (props) => {
        const [hello,setHello]=useState("");

        useEffect(()=>{
            setTimeout(()=>{
                setHello(props.hello)
            },1000)
        })

        return(hello ?
            <WrappedComponent {...props}/>
            :
            <div>Loading...</div>)
    }
}

داخل ایت فایل ( که خیلی شبیه به یک کامپوننت نوشته شده ) ابتدا ایمپورت ها رو آوردم و یک state رو اماده کردم که کار کنه . باید بگم اگر hello مقدار داشت ، WrappedComponent رو نمایش بده .

WrappedComponent در واقع میشه کامپوننتی که داخل HOC ما قرار هست که بیاد .

همونطور که بالاتر گفتم HOC یک کامپوننت رو به عنوان آرگومان میگیره و روی اون تصمیم میگیره و کار رو جلو میبره . برای همینه که تابع connect داخل ریداکس ، مهمترین تابع هست . چون تمام state ها رو اونجا هندل میکنه و بالاترین سطح رو داره . یا تابع withStyle داخل کامپوننت material-ui .

برای اینکه یک ثانیه من درست بشه و فکر کنم دارم async کار میکنم ، اومدم داخل useEffect یک setTimeout قرار دادم و گفتم بعد از یک ثانیه state رو تغییر بده و hello رو پر کن . با چی؟
با props ه داخل کامپوننت تعریف شده . خوب همه چیز ظاهرا خوبه . اما چجوری استفده کنیم؟

یک تغییر کوچیک داخل Head.js باید بدیم . به شکل زیر :

import React from "react";
import logo from "./logo.svg";
import {Loading} from "./HOC/Loading";

const Header=(props)=>{
    return (
        <header className="App-header">
            {props.hello}
            <img src={logo} className="App-logo" alt="logo" />
            <p>
                Edit <code>src/App.js</code> and save to reload.
            </p>
            <a
                className="App-link"
                href="https://reactjs.org"
                target="_blank"
                rel="noopener noreferrer"
            >
                Learn React
            </a>
        </header>
    )
}

export default Loading(Header);

Loading رو ایمپورت کردم و انتهای کامپوننت Header رو wrapped کردم داخل Loading .
حالا اجرا کنین و میبینین که یک ثانیه ، Loading... رو میبینید رو بعدش کامپوننت رو .

این کار فوایدش خیلی زیاده . برای مثال شما با context api میخواین تمام استیت ها رو منیج کنین و روی هر تغییر تصمیمگیری کنین ( مثل سبد خرید فروشگاه ) . میتونید یک HOC به اسم shop بنویسید و تمام . همه چیز راحت قابل هندل کردنه .