نحوه‌ی کار Hook‌ها در React به زبان ساده

984
Hook‌ها در React

Hook چیست؟

Hook‌ها توابعی در React هستند که امکان استفاده از state و ویژگی‌های React در یک کامپوننت مبتنی بر تابع را به ما می‌دهند. Hookها به ما این امکان را می‌دهند تا به جای جابجایی بین HOCها، کلاس‌ها و توابع، از توابع استفاده کنیم. از آنجایی که Hook‌ها توابع جاوا اسکریپت معمولی هستند، می‌توانید از Hook‌های پیش‌فرض استفاده کرده و Hook دلخواه خود را ایجاد کنید. بنابراین راه‌حل مشکل شما اکنون یک دستور یک‌خطی خواهد بود.

در مثال‌هایی که در ادامه آورده شده است closureهایی وجود خواهد داشت، بنابراین قبل از توضیح نحوه عملکرد Hook‌ها در React، اجازه دهید closure را تعریف کنیم:

«closure زمانی است که یک تابع قادر به حفظ و دسترسی به lexical scope خود باشد؛ حتی زمانی که آن تابع خارج از lexical scope خود اجرا می‌شود.»

useState چگونه کار می‌کند؟

اجازه دهید نحوه عملکرد useState را با یک مثال توضیح دهیم:

const OurReact = (function() {
    let val; // 'val' stores the value in the module scope 
        return {
            render(Component) {
               const Comp = Component();
               Comp.render();
               return Comp;
            },
            useState(initialVal) {
               val = val || initialVal; // Assigns a new value every run 
               function setState(newVal) {
                   val = newVal;
               }
               return [val, setState];
            },
        };  
})();

در این مثال، ما از Module Pattern برای ایجاد کلون React کوچک خود استفاده کرده‌ایم. بنابراین اگر می‌خواهید به useState دسترسی داشته باشید، باید به عنوان (…)OurReact.useState به آن دسترسی پیدا کنید. مانند React، برای زیر نظر داشتن وضعیت Component، از متغیر val استفاده می‌کنیم (برای سادگی فقط یک کامپوننت را زیر نظر دارد). همانطور که می‌بینید، useState در داخل یک closure قرار دارد. حال اجازه دهید از کد بالا استفاده کنیم:

مطلب مرتبط: چرا باید React را بیاموزید

function Character () {
    const [characteName, setCharacterName] = OurReact.useState('Mario'); // default value to 'Mario'
    return{
        changeName: (charName) => setCharacterName(charName),
        render: () => console.log('Rendered character:', characteName),
    }
}
let App;
App = OurReact.render(Character);  // Rendered character: Mario
App.changeName('Luigi');
App = OurReact.render(Character); // Rendered character: Luigi

این یک کد ساده برای توضیح نحوه عملکرد useState hook بود.

useEffect چگونه کار می‌کند؟

هر زمان که بخواهید بعد از هر رندر، کاری را به عنوان یک اثر جانبی انجام دهید (یعنی فراخوانی یک API یا چک کردن اینکه کامپوننت داده را دارد یا نه)، می‌توانید چنین اثراتی را به useEffect منتقل کنید. اگر با کامپوننت‌های Class-based آشنا هستید، useEffect همان کاربرد componentDidMount ،componentDidUpdate و componentWillUnmount را در کلاس‌های React دارد، با این تفاوت که در یک API واحد یکپارچه شده است.

function Example() {
    const [characterName, setCharacterName] = useState('Mario');
    // Similar to componentDidMount and componentDidUpdate:
    useEffect(()=>{
       document.title = `Character name ${characterName}`;
    });
    return(
       <div>
          <p>Character : {characterName}</p>
          <input type='text' value={characterName} onChange={e => setCharacterName(e.target.value)} />
       </div>
    );
};

اکنون می‌خواهیم clone کوچکی که از React ساخته بودیم را با افزودن هوک useEffect گسترش دهیم:

const OurReact = (function() {
    let val, deps; // A new variable 'deps' to hold our dependencies
    return {
        render(Component) {
            const Comp = Component();
            Comp.render();
            Return Comp;
        },
        useEffect(callBack, dependencyArr){
           const hasNoDependency = !dependencyArr,
                 hasDependencyChanged = deps ? !dependencyArr.every((el, i) => el === deps[i]) : true;
           if (hasNoDependency  || hasDependencyChanged ) {
               callback();
               deps = dependencyArr;
           }
        },
        useState(initialVal) {
           val = val || initialVal;
           function setState(newVal) {
              val = newVal;
           };
           return [val, setState];
        },
    };  
})();

کاربرد:

function Character () {
    const [characteName, setCharacterName] = OurReact.useState('Mario');
    OurReact.useEffect(() => {
        console.log('effect called ', characterName);
    }, [characteName])
    return{
        changeName: (charName) => setCharacterName(charName),
        noopFunction: () => setCharacterName(characteName),  // Similar to Jquery.noop that does nothing.
        render: () => console.log('Rendered character:', characteName),
    }
}

let App;
App = OurReact.render(Character);
//   effect called Mario
// Rendered character: Mario

App.changeName('Luigi');
App = OurReact.render(Character); 
// effect called Luigi
// Rendered character: Luigi

App.noopFunction()
App = OurReact.render(Character); 
// No effects
// Rendered character: Luigi

App.changeName('Yoshi');
App = OurReact.render(Character); 
// effect called Yoshi
// Rendered character: Yoshi

هر زمان که تغییری در وابستگی‌ها ایجاد شود، useEffect اجرا می‌شود. به همین دلیل در مثال بالا، یک متغیر جدید به نام deps را معرفی کرده‌ایم.

مطلب مرتبط: چه چیزی React را سریع کرده است

قوانینی که باید هنگام استفاده از Hook رعایت شوند

  • Hook‌ها باید همیشه در بالاترین سطح فراخوانی شوند: با پیروی از این قانون، مطمئن می‌شوید که Hook‌ها همیشه به همان ترتیبی که در هر بار رندرِ مؤلفه‌ی شما اعلام شده‌اند، فراخوانی می‌شوند. به خاطر داشته باشید که هرگز Hookها را در توابع تو‌در‌تو و حلقه‌ها فراخوانی نکنید.

توضیح:

functions character() {
    const [characterName, setCharacterName] = useState('Mario');
    useEffect(function storeTheCharacter(){
        localStorage.setItem('formData', characterName);
    });
    const [characterAbility, setCharacterAbility] = useState('Fire flower'); 
    useEffect(function displayDetails(){
    document.getElementById('characterView').innerHTML(`Character: ${characterName}, Ability: ${ characterAbility}`)
    });
}

ممکن است این سؤال برای شما پیش بیاید که React چگونه می‌داند کدام state با کدام useState مطابقت دارد؟ پاسخ همان چیزی است که در مورد آن بحث کردیم، ما همیشه باید Hookها را به همان ترتیبی که اعلام شده‌اند فراخوانی کنیم. اگر Hookهای داخل حلقه را فراخوانی کنیم یا ترتیب Hook تغییر کند، React در مورد اینکه چگونه  state کامپوننت ما را حفظ کند، گیج می‌شود.

// 1st Render
useState('Mario);  // Initialize the 'characterName' state variable to 'Mario'
useEffect(storeTheCharacter);  // Adding an effect to store the 'characterName' to the localStorage
useState('Fire Flower');  // Initialize the 'characterAbility' state variable with 'Active'
useEffect(displayDetails);  // Adding an effect to update the displaying data

// 2nd render
useState('Mario);  // Read the characterName state variable (argument is ignored)
useEffect(storeTheCharacter);  // Replace the effect for persisting the form
useState('Fire Flower');  // Read the characterAbilities state variable (argument is ignored)
useEffect(displayDetails);  // Replace the effect for updating the displaying data

از آنجایی که ترتیب Hookها حفظ شده است، React می‌تواند state کامپوننت ما را حفظ کند.

اگر یک Hook را با یک شرط فراخوانی کنیم چه اتفاقی می‌افتد؟

if( characterName !== '' ){
    useEffect(function storeTheCharacter () {
            localStorage.setItem('formData', characterName);
    });
}

در اینجا اولین قانون Hook در یک شرط نقض شده است. بیایید ببینیم اگر شرط نادرست باشد، Hook در حین رندر نادیده گرفته شود و ترتیب فراخوانی Hook متفاوت شود، چه اتفاقی می‌افتد:

useState(Mario) // 1. Read the name state variable (argument is ignored)
// useEffect(storeTheCharacter)  // This Hook was skipped!
useState('Fire Flower')  // 2 (but was 3). Fail to read the surname state variable
useEffect(updateTitle)  // 3 (but was 4). Fail to replace the effect

React نمی‌تواند تشخیص دهد که برای دومین فراخوانی useState Hook چه چیزی باید برگرداند. React انتظار داشت که دومین فراخوانی Hook در این کامپوننت با اثر storeTheCharacter مطابقت داشته باشد، درست مانند رندر قبلی، اما دیگر اینطور نیست. بعد از فراخوانی‌ای که ما از آن صرف‌نظر کردیم، همه فراخوانی‌های بعدی Hook نیز به اندازه یک واحد جابجا می‌شوند که منجر به باگ می‌شود. به همین دلیل است که Hookها همیشه در بالاترین سطح کامپوننت فراخوانی می‌شوند.

  • Hookها باید همیشه از توابع React فراخوانی شوند: Hook‌ها را از توابع معمولی جاوا اسکریپت فراخوانی نکنید. شما می‌توانید:
  1. Hook‌ها را از کامپوننت‌های تابعی React فراخوانی کنید
  2. Hookها را از Hookهای سفارشی فراخوانی کنید

توضیح:

import { useState } from 'react';
const lives = 3;
const isGameOver = (noOfDeaths) =>{
    const livesLeft = lives - noOfDeaths;
    const [characterName, setCharacterName] = useState('Mario');
        if (livesLeft === 0) { return 'Game Over''; } else { return 'Continue'; }
}

Hookها بخش مهمی از اکثر کامپوننت‌های React هستند. امیدواريم با خواندن این مقاله نحوه عملکرد همه‌چیز در Hookهای API ری‌اکت و قوانینی که باید هنگام استفاده از آن‌ها رعایت کنید را متوجه شده باشید.

نسرین نادری

اشتراک در
اطلاع از
guest

0 دیدگاه‌
بازخورد (Feedback) های اینلاین
View all comments