ساخت یک برنامه‌ی چت با هوک‌های ریکت در 100 خط کد

ترجمه و تالیف : مهدی عقیقی
تاریخ انتشار : 03 اسفند 98
خواندن در 4 دقیقه
دسته بندی ها : react

ما قبلا در مورد هوک‌ها صحبت کرده‌ایم و از کار آن‌ها با خبریم. خب، کاری که ما می‌خواهیم در این مقاله انجام بدهیم، این است که چطوری می‌توانیم با استفاده از هوک‌ها، در قالب ساخت یک برنامه‌ی چت، برنامه‌نویسی رو سریع‌تر و آسان‌تر کنیم.

به طور مشخص، ما می‌خواهیم یک برنامه‌ی چت با استفاده از create react app بسازیم. و در این عملیات با استفاده از هوک‌ها کارمان را راحت‌تر کنیم و بسیاری از کدهای تکراری که برای کار خیلی لازم نیست، حذف کنیم.

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

الزامات ساخت برنامه‌ی چت

برنامه‌ای که ما می‌خواهیم آن‌را بسازیم؛ دارای قابلیت‌های زیر است:

  • گرفتن لیست پیام‌های قدیمی از سرور
  • وصل شدن به یک روم برای چت گروهی
  • گرفتن آپدیت زمانی که ارتباط افراد با روم قطع می‌شود یا به روم وصل می‌شوند
  • ارسال و دریافت پیام

ما قرار است بر طبق یک‌سری فرضیات کار کنیم:

  • ما در نظر می‌گیریم که سروری که داریم از آن استفاده می‌کنیم، بلک‌باکس است. نگران این نباشید که کاملاً کار کند زیرا می‌خواهیم با استفاده از سوکت‌های ساده با آن ارتباط برقرار کنیم.
  • تمام استایل‌های استفاده شده در این آموزش در یک فایل CSS ذخیره شده است. که می‌توانید از این لینک آن‌را دانلود کنید.

آماده شدن برای شروع کار

ما باید محیط برنامه‌نویسی خودمان را برای نوشتن کد آماده کنیم.

خب بیایید با درست کردن پروژه از ترمینال شروع کنیم.

create-react-app socket-client
cd socket-client
npm start

حالا وقتی وارد http://localhost:3000 می‌شویم، باید در صفحه‌ی شروع خود ریکت را ببینیم.

استفاده از useState

اولین هوکی که قرار است از آن استفاده کنیم useState است. این هوک باعث می‌شود که ما بتوانیم از state ها در کامپوننت خودمان استفاده کنیم، بدون این‌که مجبور باشیم یک کلاس بسازیم یا همیشه از this.state استفاده کنیم. داده‌های ثابت، مانند نام کاربری در متغیرهای useState ذخیره می‌شوند. این تضمین می‌کند که داده‌ها به راحتی در دسترس هستند.

مزیت اصلی useState این است که به صورت خودکار، در کامپوننت رندر‌شده منعکس می‌شود. اگر ما از متغیرهای عادی استفاده می‌کردیم، آن‌ها به عنوان state شناخته نمی‌شدند و برای این که کامپوننت با تغییر کردن آن‌ها ریرندر شود باید آن‌ها را به عنوان پراپرتی به کامپوننت می‌دادیم. پس دوباره ما داریم کارهای بسیار زیادی را با استفاده از هوک‌ها کاهش می‌دهیم.

این هوک مستقیم توسط react منتشر شده است، پس می‌توانیم با یک خط کد آن را import کنیم.

import React, { useState } from 'react';

ما در قدم اول قرار هست که یک کامپوننت ساده درست کنیم که اگر کاربر لاگین شده بود بنویسد “Hello” و اگر لاگین نشده بود، فرم لاگین را به او نشان دهد. ما برای این‌کار متغیر id را چک می‌کنیم.

اطلاعات تمام فرم‌های ما پس از سابمیت برای رسیدگی به درخواست به تابع handleSubmit داده می‌شود. این تابع چک می‌کند اگر فیلد Name خالی نباشد، id و room را برای user قرار می‌دهد وگرنه ما یک پیام به کاربر می‌دهیم که برای ادامه‌ی کار باید Name را وارد کند.

// App.js

import React, { useState } from 'react';
import './index.css';

export default () => {
  const [id, setId] = useState("");
  const [nameInput, setNameInput] = useState("");
  const [room, setRoom] = useState("");

  const handleSubmit = e => {
    e.preventDefault();
    if (!nameInput) {
      return alert("Name can't be empty");
    }
    setId(name);
    socket.emit("join", name, room);
  };

  return id !== '' ? (
    <div>Hello</div>
  ) : (
    <div style={{ textAlign: "center", margin: "30vh auto", width: "70%" }}>
      <form onSubmit={event => handleSubmit(event)}>
        <input
          id="name"
          onChange={e => setNameInput(e.target.value.trim())}
          required
          placeholder="What is your name .."
        />
        <br />
        <input
          id="room"
          onChange={e => setRoom(e.target.value.trim())}
          placeholder="What is your room .."
        />
        <br />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

دوباره: 

ما هوک‌ها را از react ایمپورت می‌کنیم؛ مقادیر را برای شناسایی id user و chatroom user دریافت می‌کنیم، در صورت ورود کاربر به سیستم این مقادیر را تعریف می‌کنیم، و در صورت عدم ورور کاربر؛ دوباره فرم ورور را نشان می‌دهیم.

ساخت یک برنامه‌ی چت با هوک‌های ریکت در 100 خط کد

استفاده از هوک useSocket

ما قرار است از یک هوک اوپن‌سورس به اسم useSocket برای برقراری ارتباط با سرور استفاده کنیم. برعکس useState این هوک توسط خود react توسعه داده نشده است، پس قبل از ایمپورت کردن، باید آن‌را به پروژه‌مان اضافه کنیم.

npm add use-socket.io-client

این کانکشن با استفاده از هوک‌های کتابخانه‌ی socket.io درست می‌شود که یک راه راحت‌تر و سریع‌تر برای برقراری ارتباط با وب‌سوکت است.

قرار است که ما از این برای ارسال و دریافت پیام، و البته قابلیت‌های دیگر مانند گروپ‌چت در روم‌ها استفاده کنیم.

متغیر socket تعریف شده می‌تواند توسط هر کامپوننتی مورد استفاده قرار بگیرد. با این‌حال داده‌های ما از هر جایی قابل دستکاری هستند و ما نمی‌دانیم این تغییرات در کجا اتفاق می‌افتد.

استفاده‌ی ساده از useSocket مانند زیر است:

const [socket] = useSocket('socket-url')

ما همین‌طور که جلو می‌رویم قرار است از چند API سوکت استفاده کنیم. البته همه‌ی آن‌ها در مستندات socket.io معرفی شده‌اند. اما برای الان بیایید آن را در برنامه‌مان ایمپورت کنیم.

import useSocket from 'use-socket.io-client';

بعد از آن ما باید هوک را initialize کنیم تا به سرور وصل شود. سپس می‌توانیم از هوک‌مان لاگ بگیریم تا ببینیم که با موفقت وصل شده یا خیر.

const [id, setId] = useState('');
const [socket] = useSocket('<https://open-chat-naostsaecf.now.sh>');

socket.connect();
console.log(socket);

کنسول مرورگر را باز کنید؛ url بالا باید لاگ شده باشد.

استفاده از useImmer

برنامه‌ی ما از هوک useImmer استفاده می‌کند تا آرایه‌ها و آبجکت‌های داخل استیت را بدون تغییر آن‌ها ویرایش دهد. این برنامه؛ از useState و Immer استفاده می‌کند تا مدیریت state تغییرناپذیر را به ما بدهد. این مورد در نشان دادن لیست افراد آنلاین و پیام‌ها به ما کمک می‌کند.

استفاده از Immer و useState به ما این امکان را می‌دهد که با ایجاد یک state جدید از state فعلی، آرایه یا آبجکتی را تغییر دهیم و از تغییر مستقیم state فعلی جلوگیری کنیم. این مورد به ما امکان  بیشتری می‌دهد؛ تا آن‌جایی که می‌توان state فعلی را دست‌نخورده و در عین حال قادر به دست‌کاری آن، بر اساس شرایط مختلف دانست.

دوباره، ما می‌خواهیم از هوکی استفاده کنیم که توسط خود react تولید نشده است. پس باید اول آن را نصب کنیم.

npm add use-immer

استفاده از آن بسیار سرراست است. اولین مقدار در کانستراکتور همان state کنونی است و مقدار دوم تابعی است که با آن می‌توان state را تغییر داد. useImmer مقادیر اول را برای state فعلی در نظر می‌گیرد.

const [data, setData] = useImmer(default_value)

استفاده از setData

در مثال آخر به تابع setData توجه کنید. ما از آن استفاده می‌کنیم که یک کپی از داده‌های فعلی ذخیره کنیم، و با استفاده از آن اطلاعات را دستکاری کنیم و به عنوان state جدید آن‌ها را ذخیره کنیم.

setData(draftState => { 
  draftState.operation(); 
});

// ...or

setData(draft => newState);

// Here, draftState is a copy of the current data

استفاده از useEffect

خب، دوباره برمی‌گردیم به یکی از هوک‌ها که توسط خود react تولید شده. ما قرار است از useEffect استفاده کنیم که یک تکه کد را فقط زمانی که کامپوننت ما برای اولین بار لود می‌شود، اجرا کنیم. این تضمین می‌کند که کد ما فقط یک‌بار اجرا می‌شود و هر باری که کامپوننت ریرندر شود، این کد دوباره اجرا نمی‌شود؛ که این موضوع برای پرفورمنس خیلی خوب است.

تنها کاری که برای استفاده از این هوک نیاز است، این است که ایمپورتش کنیم.

import React, { useState, useEffect } from 'react';

ما به کامپوننتی نیاز خواهیم داشت که بر اساس وجود یا عدم وجود شناسه ارسال‌کننده در آرایه، پیام یا آپدیت  را ارائه دهد. بیایید اسم کامپوننت را Messages بزاریم.

const Messages = props => props.data.map(m => m[0] !== '' ? 
(<li key={m[0]}><strong>{m[0]}</strong> : <div className="innermsg">{m[1]}</div></li>) 
: (<li key={m[1]} className="update">{m[1]}</li>) );

خب بیایید منطق سوکت برنامه‌مان را در useEffect بزاریم تا وقتی کامپوننت ریرندر می‌شود، ما دوباره همان مجموعه از پیام‌ها را بارها و بارها تکرار نکنیم. ما هوک message را در کامپوننت تعریف می‌کنیم، سوکت را وصل می‌کنیم، و بعد listener ها (گوشکنندگان) را برای دریافت پیام‌ها و آپدیت‌های جدید در useEffect آماده می‌کنیم. و همچنین توابع به‌روزرسانی را در داخل listener ها تنظیم می‌کنیم.

const [socket] = useSocket('<https://open-chat-naostsaecf.now.sh>');      
socket.connect();

const [messages, setMessages] = useImmer([]);
useEffect(()=>{
  socket.on('update', message => setMessages(draft => {
    draft.push(['', message]);
  }));

  socket.on('message que',(nick, message) => {
    setMessages(draft => {
      draft.push([nick, message])
    })
  });
},0);

در صورت صحیح بودن نام کاربر و اتاق، آپدیت دیگری که برای گذاشتن خوب است؛ ارسال پیام "پیوستن شخص" به اتاق است. این کار باعث گرفتن پیام‌های قدیمی و راه افتادن بقیه listener ها برای دریافت به‌روزرسانی‌های دیگر است.

// ...
  socket.emit('join', name, room);
};

return id ? (
  <section style={{ display: "flex", flexDirection: "row" }}>
      <ul id="messages">
        <Messages data={messages} />
      </ul>
      <ul id="online">
        {" "}
        &#x1f310; : <Online data={online} />{" "}
      </ul>
      <div id="sendform">
        <form onSubmit={e => handleSend(e)} style={{ display: "flex" }}>
          <input id="m" onChange={e => setInput(e.target.value.trim())} />
          <button style={{ width: "75px" }} type="submit">
            Send
          </button>
        </form>
      </div>
    </section>
) : (
// ...

کارهای پایانی

ما فقط به اجرای چند ترفند دیگر نیاز داریم تا برنامه چت خودمان را تکمیل کنیم. به طور مشخص ما هنوز به موارد زیر نیاز داریم:

  • یک کامنت برای نشان دادن افرادی که آنلاین هستند.
  • استفاده از یک useImmer و یک socket listener برای آن
  • کنترل ‌کننده‌ی ارسال پیام با سوکت‌های مناسب

همه‌ی این‌ها از روش‌هایی که قبلا پوشش داده‌ایم ساخته می‌شوند. من می‌خواهم کل کد فایل app.js را اینجا بگذارم تا نشان دهم چگونه  همه چیز با هم هماهنگ می‌شود.

// App.js

import React, { useState, useEffect } from 'react';
import useSocket from 'use-socket.io-client';
import { useImmer } from 'use-immer';

import './index.css';

const Messages = props => props.data.map(m => m[0] !== '' ? (<li><strong>{m[0]}</strong> : <div className="innermsg">{m[1]}</div></li>) : (<li className="update">{m[1]}</li>) );

const Online = props => props.data.map(m => <li id={m[0]}>{m[1]}</li>);

export default () => {
  const [id, setId] = useState('');
  const [nameInput, setNameInput] = useState('');
  const [room, setRoom] = useState('');
  const [input, setInput] = useState('');

  const [socket] = useSocket('https://open-chat-naostsaecf.now.sh');
  socket.connect();

  const [messages, setMessages] = useImmer([]);
  const [online, setOnline] = useImmer([]);

  useEffect(()=>{
    socket.on('message que',(nick,message) => {
      setMessages(draft => {
        draft.push([nick,message])
      })
    });

    socket.on('update',message => setMessages(draft => {
      draft.push(['',message]);
    }));

    socket.on('people-list',people => {
      let newState = [];
      for(let person in people){
        newState.push([people[person].id,people[person].nick]);
      }
      setOnline(draft=>{draft.push(...newState)});
      console.log(online)
    });

    socket.on('add-person',(nick,id)=>{
      setOnline(draft => {
        draft.push([id,nick])
      })
    });

    socket.on('remove-person',id=>{
      setOnline(draft => draft.filter(m => m[0] !== id))
    });

    socket.on('chat message',(nick,message)=>{
      setMessages(draft => {draft.push([nick,message])})
    });
  },0);

  const handleSubmit = e => {
    e.preventDefault();
    if (!nameInput) {
      return alert("Name can't be empty");
    }
    setId(name);
    socket.emit("join", name,room);
  };

  const handleSend = e => {
    e.preventDefault();
    if(input !== ''){
      socket.emit('chat message',input,room);
      setInput('');
    }
  };

  return id ? (
    <section style={{display:'flex',flexDirection:'row'}} >
      <ul id="messages"><Messages data={messages} /></ul>
      <ul id="online"> &#x1f310; : <Online data={online} /> </ul>
      <div id="sendform">
        <form onSubmit={e => handleSend(e)} style={{display: 'flex'}}>
            <input id="m" onChange={e=>setInput(e.target.value.trim())} /><button style={{width:'75px'}} type="submit">Send</button>
        </form>
      </div>
    </section>
  ) : (
    <div style={{ textAlign: 'center', margin: '30vh auto', width: '70%' }}>
      <form onSubmit={event => handleSubmit(event)}>
        <input id="name" onChange={e => setNameInput(e.target.value.trim())} required placeholder="What is your name .." /><br />
        <input id="room" onChange={e => setRoom(e.target.value.trim())} placeholder="What is your room .." /><br />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

جمع‌بندی نهایی

تمام شد! ما یک برنامه‌ی چت گروهی باهم‌ ساختیم. شما می‌توانید کدهای کامل این پروژه رو در این لینک ببینید.

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

حالا که شما اطلاعات خوبی از هوک‌های react به دست آوردید، از دانسته‌های خودتان استفاده کنید و بیشتر تمرین کنید. می‌توانید با ساختن مثال‌های زیر خودتان را تست کنید.

  • یک بلاگ
  • اینستاگرام خودتان
  • یک نمونه از سایت reddit

سوالی دارید؟ در کامنت‌ها بپرسید.

منبع

گردآوری و تالیف مهدی عقیقی
آفلاین
user-avatar

برنامه‌نویس وب، عاشق جاوااسکریپت و ریکت و لاراول :)

دیدگاه‌ها و پرسش‌ها

برای ارسال نظر لازم است ابتدا وارد سایت شوید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید