آشنایی با hook در react.js (نسخه ی 16.8 به بالا)

Introduction to hook in react.js

23 بهمن 1399
آشنایی با hook در react.js (نسخه ی 16.8 به بالا)

hook در ری اکت چیست؟

در قسمت قبلی یاد گرفتیم که چطور می توان state را در برنامه های react تغییر داد. تا قبل از معرفی نسخه 16.8 کتابخانه react استفاده از روش جلسه قبل (this.setState) تنها راه تغییر state به حساب می آمد اما در نسخه جدید قابلیتی به نام hook در ری اکت اضافه شده است که به ما اجازه می دهد state را از طریق کامپوننت های کاربردی (functional) نیز تغییر دهیم.

در این دوره شما را با قابلیت hook آشنا خواهیم کرد (یک فصل کامل hook را به صورت مجزا آموزش خواهیم داد و در یک فصل کامل دیگر پروژه این دوره را دوباره با استفاده از hook می نویسیم) اما در طول دوره از همان روش اولیه this.setState استفاده خواهیم کرد.

برای شروع کامپوننت App را به یک کامپوننت کاربردی تغییر می دهیم:

const app = props => {

  render() {
    return (
      <div className="App">
        <h1>Hi, I'm a React App!</h1>
        <p>This is really working!</p>
        <button onClick={this.switchNameHandler}>Switch Name</button>
        <Person name={this.state.persons[0].name} age={this.state.persons[0].age} />
        <Person name={this.state.persons[1].name} age={this.state.persons[1].age} >My Hobbies: Racing</Person>
        <Person name={this.state.persons[2].name} age={this.state.persons[2].age} />
      </div>
    );
    // return React.createElement('div', {className: 'App'}, React.createElement('h1', null, 'Does this work now?'));
  }
}

state = {
  persons: [
    { name: "Max", age: 28 },
    { name: "Manu", age: 29 },
    { name: "Stephanie", age: 26 }
  ],
  otherState: 'some other value'
}

switchNameHandler = () => {
  // console.log('was clicked!');
  // DON'T DO THIS: this.state.persons[0].name = 'Maximilian';
  this.setState({
    persons: [
      { name: "Maximilian", age: 28 },
      { name: "Manu", age: 29 },
      { name: "Stephanie", age: 27 }
    ]
  })
}

همانطور که متوجه شده اید از یک تابع به جای کلاس استفاده کرده ام:

const app = props =>

 state و switchNameHandler را هم از درون آن به آخر صفحه منتقل کرده ام.

از آنجایی که نام ثابت ما app است، بنابراین دستور export آخر صفحه را نیز باید به حرف a کوچک تغییر دهیم:

export default app;

نکته: اگر در این قسمت با خطا مواجه شدید، نام app را به App تغییر دهید. ممکن است linter (خطایاب) موجود در بسته Create-react-app به شما اجازه استفاده از متغیرها با حروف اولیه کوچک را ندهد.

در آخر هم هیچ متد render ای نخواهیم داشت بلکه مستقیما JSX را return می کنیم:

const app = props => {

    return (
      <div className="App">
        <h1>Hi, I'm a React App!</h1>
        <p>This is really working!</p>
        <button onClick={this.switchNameHandler}>Switch Name</button>
        <Person name={this.state.persons[0].name} age={this.state.persons[0].age} />
        <Person name={this.state.persons[1].name} age={this.state.persons[1].age} >My Hobbies: Racing</Person>
        <Person name={this.state.persons[2].name} age={this.state.persons[2].age} />
      </div>
    );
    // return React.createElement('div', {className: 'App'}, React.createElement('h1', null, 'Does this work now?'));
  }

ما کار خاصی انجام نداده ایم بلکه همان چیزهایی را که برای کامپوننت های کاربردی یاد گرفته بودیم، روی این کامپوننت پیاده کردیم.

حالا دیگر نیازی به Component نداریم. بنابراین به جای import کردن آن باید یک hook به نام useState را وارد کنیم. بنابراین کد را به این شکل تغییر می دهیم:

import React, { useState } from 'react';

ما با hook های مختلفی در react آشنا خواهیم شد اما مهم ترین آن ها همین useState است. نحوه استفاده از آن هم مانند فراخوانی یک تابع است. شما آن را به صورت یک تابع فراخوانی کرده و State خود را به عنوان پارامتر به آن می دهید:

const app = props => {

  useState({
    persons: [
      { name: "Max", age: 28 },
      { name: "Manu", age: 29 },
      { name: "Stephanie", age: 26 }
    ],
    otherState: 'some other value'
  })

  return (
    <div className="App">
      <h1>Hi, I'm a React App!</h1>
      <p>This is really working!</p>
      <button onClick={this.switchNameHandler}>Switch Name</button>
      <Person name={this.state.persons[0].name} age={this.state.persons[0].age} />
      <Person name={this.state.persons[1].name} age={this.state.persons[1].age} >My Hobbies: Racing</Person>
      <Person name={this.state.persons[2].name} age={this.state.persons[2].age} />
    </div>
  );
  // return React.createElement('div', {className: 'App'}, React.createElement('h1', null, 'Does this work now?'));
}

در واقع state قبلی را (از پایین صفحه) پاک کردیم و فقط شیء جاوا اسکریپتی آن را به تابع useState دادیم. useState همیشه یک آرایه با دو عضو برمی گرداند. بنابراین بهتر است آن را درون یک ثابت قرار دهیم:

const stateArr = useState({
    persons: [
      { name: "Max", age: 28 },
      { name: "Manu", age: 29 },
      { name: "Stephanie", age: 26 }
    ],
    otherState: 'some other value'
  })

اولین عضو آرایه برگشتی state فعلی ما است (یعنی همان state ای که در حال حاضر داریم) و عضو دوم یک تابع است که به ما اجازه می دهد state را تغییر دهیم. بنابراین برای راحتی کار از یک ویژگی جدید جاوا اسکریپت به نام destructuring استفاده می کنیم (ر.ک به جلسات قبل) و کد را بدین شکل تغییر می دهیم:

const [personsState, setPersonsState] = useState({
    persons: [
      { name: "Max", age: 28 },
      { name: "Manu", age: 29 },
      { name: "Stephanie", age: 26 }
    ],
    otherState: 'some other value'
  })

اگر از جلسات «مروری بر ES6» یادتان باشد این قابلیت به ما اجازه می دهد که اعضای آرایه را از آن بیرون بکشیم و در متغیرهای مورد نظر خودمان قرار دهیم. انتخاب نام های personsState و setPersonsState کاملا بر اساس سلیقه شما است. اینها متغیرهایی هستند که به ترتیب اعضای اول و دوم آرایه برگشتی را درون خود قرار می دهند.

حالا در قسمت JSX باید هر جایی که از this.state استفاده کرده ایم از personsState استفاده کنیم:

return (
    <div className="App">
      <h1>Hi, I'm a React App!</h1>
      <p>This is really working!</p>
      <button onClick={this.switchNameHandler}>Switch Name</button>
      <Person name={personsState.persons[0].name} age={personsState.persons[0].age} />
      <Person name={personsState.persons[1].name} age={personsState.persons[1].age} >My Hobbies: Racing</Person>
      <Person name={personsState.persons[2].name} age={personsState.persons[2].age} />
    </div>
  );

دلیل آن هم واضح است؛ this.state دیگر وجود ندارد! ما دیگر کلاسی نداریم که this.state بخواهد در آن کار کند. حالا باید تابع switchNameHandler را درون این کامپوننت کاربردی بیاوریم! بله می توانیم درون یک تابع، یک تابع دیگر داشته باشیم!

const app = props => {

  const [personsState, setPersonsState] = useState({
    persons: [
      { name: "Max", age: 28 },
      { name: "Manu", age: 29 },
      { name: "Stephanie", age: 26 }
    ],
    otherState: 'some other value'
  });

  const switchNameHandler = () => {
    // console.log('was clicked!');
    // DON'T DO THIS: this.state.persons[0].name = 'Maximilian';
    this.setState({
      persons: [
        { name: "Maximilian", age: 28 },
        { name: "Manu", age: 29 },
        { name: "Stephanie", age: 27 }
      ]
    })
  }

  return (
    <div className="App">
      <h1>Hi, I'm a React App!</h1>
      <p>This is really working!</p>
      <button onClick={this.switchNameHandler}>Switch Name</button>
      <Person name={personsState.persons[0].name} age={personsState.persons[0].age} />
      <Person name={personsState.persons[1].name} age={personsState.persons[1].age} >My Hobbies: Racing</Person>
      <Person name={personsState.persons[2].name} age={personsState.persons[2].age} />
    </div>
  );
  // return React.createElement('div', {className: 'App'}, React.createElement('h1', null, 'Does this work now?'));
}

همانطور که می بینید تابع را درون یک ثابت (const) ذخیره کرده ام. در قسمت JSX از کد زیر برای فراخوانی تابع رویداد استفاده می کردیم:

onClick={this.switchNameHandler}

اما از آنجایی که در هیچ کلاسی نیستیم باید کلیدواژه this را حذف کنیم:

onClick={switchNameHandler}

حالا به جای استفاده از this.setState درون تابع switchNameHandler باید از setPersonsState استفاده کنیم:

const switchNameHandler = () => {
    // console.log('was clicked!');
    // DON'T DO THIS: this.state.persons[0].name = 'Maximilian';
    setPersonsState({
      persons: [
        { name: "Maximilian", age: 28 },
        { name: "Manu", age: 29 },
        { name: "Stephanie", age: 27 }
      ]
    })
  }

اکنون اگر به محیط مرورگر خود بروید و برنامه را تست کنید، همه چیز سالم کار خواهد کرد و تفاوتی با حالت قبلی نخواهیم داشت.

هشدار بسیار جدی: استفاده از state در کامپوننت های کلاس-محور و کامپوننت های کاربردی (با hooks) تفاوت مهمی دارد. اگر یادتان باشد گفته بودیم که تغییر یک state در کامپوننت های کلاس محور به بقیه state ها (مقدار otherState) دست نمی زند اما زمانی که از hooks استفاده می کنیم باید بدانید که setState مقدار state قبلی را به طور کل پاک کرده و state جدید را جایگزین آن می کند. بنابراین otherState حذف خواهد شد.

برای اثبات این مسئله می توانیم personsState را console.log کنیم:

  console.log(personsState);
state فعلی برنامه با console.log
state فعلی برنامه با console.log

حالا با فشردن دکمه switch Name دوباره state برای ما نمایش داده می شود:

state جدید برنامه و حذف otherState
state جدید برنامه و حذف otherState

همانطور که می بینید state کاملا replace شده و دیگر otherState را نداریم. اگر بخواهیم چنین اتفاقی نیفتد باید تمام state ها را به تابع setPersonsState بدهیم:

setPersonsState({
      persons: [
        { name: "Maximilian", age: 28 },
        { name: "Manu", age: 29 },
        { name: "Stephanie", age: 27 }
      ],
      otherState: personsState.otherState
    })

حالا اگر تست کنید هیچ مشکلی نخواهیم داشت.

البته روش بهتری هم برای جلوگیری از حذف state وجود دارد: استفاده چند باره از useState ! مثلا می توانیم بگوییم:

  const [otherState, setOtherState] = useState({otherState: 'some other value'});

از آنجایی که تنها یک رشته را به آن داده ایم می توانیم آن را مستقیما به صورت رشته وارد کنیم (الزامی نیست که پارامتر ما شیء باشد):

  const [otherState, setOtherState] = useState('some other value');

اگر دوباره console.log را برای هر دو state بنویسیم متوجه می شویم که این بار otherState حذف نمی شود. شما می توانید به تعداد دلخواه خودتان useState را صدا بزنید و محدودیتی در انجام این کار ندارید.

خلاصه ای از دستور useState
خلاصه ای از دستور useState

دانلود فایل های این جلسه

بحث نهایی ما این است: در قسمت قبلی تفاوت بین کامپوننت های stateful (با نام های دیگر smart و container) و کامپوننت های stateless (با نام های دیگر dumb و presentational) را توضیح دادیم. همیشه سعی کنید تا حد ممکن است کامپوننت های بدون state یا dumb استفاده کنید، چرا که اگر تعداد کامپوننت های smart شما زیاد شود مدیریت پروژه بسیار طاقت فرسا خواهد بود. ما هم در طول این دوره سعی خواهیم کرد که تا حد ممکن از کامپوننت های dumb یا همان presentational استفاده کنیم.

امیدوارم به طور کامل با useState (یکی از انواع hook ها در react) آشنا شده باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.