برنامه نویسی 318 بازدید

در این راهنمای مقدماتی به صورت گام به گام با شیوه ایجاد یک لیست تعاملی در React آشنا می‌شویم. مراحل کار به صورت زیر است:

  • راه‌اندازی پروژه با کمک Infrastructure-Components
  • استایل‌بندی کامپوننت‌ها به کمک styled-components
  • افزودن امکان «کشیدن و رها کردن» (drag-and-drop) به کمک react-sortable-hoc

ایجاد اپلیکیشن ری‌اکت

فیسبوک یک اسکریپت به نام create-react-app (+) ارائه کرده است که روش پیش‌فرض برای آغاز یک پروژه ری‌اکت محسوب می‌شود. اما این گزینه موجب می‌شود که در زمان توزیع و انتشار برنامه با مشکلاتی مواجه شوید. برای حل این مشکل می‌توانید از Infrastructure-Components (+) استفاده کنید. این کامپوننت‌های ری‌اکت امکان تعریف معماری زیرساخت را به عنوان یک بخش از اپلیکیشن ری‌اکت فراهم می‌سازند و این بدان معنی است که به پیکربندی‌های دیگر مانند Webpack ،Babel یا Serverless نیاز ندارید.

سه روش برای راه‌اندازی پروژه وجود دارد:

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

ساختار زیر به دست می‌آید:

menu/
├── src/
│   └── index.tsx
├── .env
├── .gitignore
├── LICENSE
├── package.json
└── README

فایل package.json همه وابستگی‌های پروژه را تعیین می‌کند. در این راهنما از کتابخانه‌های زیر استفاده می‌کنیم که از طریق دستور npm install نصب شده‌اند:

"dependencies": {
  "infrastructure-components": "^0.3.1",
  "react": "^16.10.2",
  "react-dom": "^16.10.2",
  "react-sortable-hoc": "^1.10.1",
  "styled-components": "^4.1.3"
},
"devDependencies": {
  "infrastructure-scripts": "^0.3.1",
  "serverless-single-page-app-plugin": "^1.0.2"
}

مهم‌ترین فایل، src/index.tsx نام دارد. این نقطه ورودی اپلیکیشن ری‌اکت است. در این فایل یک کامپوننت <SinglePageApp/> اکسپورت می‌کنیم. زمانی که فایل src/index.tsx آماده شد، می‌توانید پروژه خود را به صورت مقدماتی با دستور npm run build بیلد کنید.

فایل index.tsx – سورس کد یک اپلیکیشن ری‌اکت ابتدایی به صورت تک‌صفحه‌ای و بدون سرور

import React from 'react';

import {
    Environment,
    Route,
    SinglePageApp
} from "infrastructure-components";

export default (
    <SinglePageApp
        stackName = "interactive-list"
        buildPath = 'build'
        region='us-east-1'>

        <Environment name="dev" />

        <Route
            path='/'
            name='Infrastructure-Components'
            render={()=><div>Hello Infrastructure-Components!</div>}
        />

    </SinglePageApp>
);

مرحله بیلد یک دستور به فایل package.json اضافه می‌کند. این دستور اپلیکیشن تک‌صفحه‌ای شما را در حالت hot-development آغاز می‌کند:

npm run interactive-list

در دستور فوق به جای interactive-list نام اپلیکیشن تک‌صفحه‌ای خود را قرار دهید. هنگامی که به آدرس localhost:3000 در مرورگر مراجعه کنید، باید متنی به صورت زیر بینید که نشان می‌دهد اپلیکیشن با موفقیت بیلد شده است:

Hello Infrastructure-Components!

در ادامه یک لیست نامرتب ساده با سه آیتم در یک فایل جدید به نام src/list.tsx می‌سازیم. هر آیتم یک چک‌باکس دارد.

فایل list.tsx – یک کامپوننت لیست

import React from 'react';

export default function () {
    return <ul>
        <li>First Item</li>
        <li>Second Item</li>
        <li>Third Item</li>
    </ul>
};

از آنجا که در تابع خود در فایل فوق از export default استفاده کرده‌ایم، باید این default را در ماژول در index.tsx ایمپورت کنیم. این کار را در خط 9 انجام می‌دهیم. در خط 22 نیز کامپوننت ایمپورت شده را رندر می‌کنیم.

فایل index.tsx – ادغام لیست در index.tsx

import React from 'react';

import {
    Environment,
    Route,
    SinglePageApp
} from "infrastructure-components";

import List from './list';

export default (
    <SinglePageApp
        stackName = "interactive-list"
        buildPath = 'build'
        region='us-east-1'>

        <Environment name="dev" />

        <Route
            path='/'
            name='Infrastructure-Components'
            render={()=><List/>}
        />

    </SinglePageApp>
);

در تصویر زیر لیست مورد نظر را می‌بینید. تا به اینجا کار خاصی انجام نداده‌ایم.

لیست تعاملی در React

استایل تعاملی

آسان‌ترین روش برای مدیریت استایل در ری‌اکت به کمک کتابخانه styled components (+) است. این کتابخانه امکان استایل‌بندی کامپوننت‌های ری‌اکت را از طریق لفظ‌های محلی CSS فراهم می‌سازد. در ادامه به بررسی نخستین کامپوننت استایل‌دار خود می‌پردازیم. بخش‌هایی از کد را که تغییر نیافته‌اند حفظ خواهیم کرد.

فایل list.tsx – استایل‌بندی آیتم‌ها

/* ...*/ 
import styled from 'styled-components'

const Item = styled.li`
  display: block;
  border-top: 1px solid #888;
  list-style-type: none;
  padding: 5px 0;
  &:hover {
      background: #EEE;
  }  
`;

export default function () {
    return <ul>
        <Item>First Item</Item>
        <Item>Second Item</Item>
        <Item>Third Item</Item>
    </ul>
};

ابتدا ماژول پیش‌فرض یعنی styled را از کتابخانه ایمپورت می‌کنیم. ماژول styled اکسپورت پیش‌فرض کتابخانه styled-components است. این یک کارخانه سطح پایین است که متدهای کمکی را به شکل styled.tagname ارائه می‌کند. منظور از tagname هر تگ معتبر HTML است. برای نمونه styled.li یک کامپوننت آیتم لیست می‌سازد (<li/>) تابع styled.li تعاریف CSS را در یک رشته قالبی که درون بک‌تیک محصور شده می‌گیرد.

سپس یک کامپوننت Item (<li/>) ایجاد و یک استایل ساده روی آن اعمال می‌کنیم. چارچوب قالب‌بندی را روی display: block;‎ تنظیم می‌کنیم. در چارچوب قالب‌بندی بلوکی، کامپوننت‌ها یکی پس از دیگری به صورت عمودی قرار می‌گیرند. لبه‌های خارجی هر کامپوننت لبه بلوک پیرامونی‌اش را لمس می‌کند. به بیان ساده کامپوننت‌ها کل یک خط را اشغال می‌کنند. هر آیتم یک حاشیه نازک 1 پیکسلی به رنگ خاکستری در بخش فوقانی خود می‌گیرد. همچنین دایره‌های توپری که عناصر لیست به صورت معمول دارند را با کد زیر حذف می‌کنیم:

list-style-type: none;

بخش padding: 5px 0;‎ فضای ناحیه فوقانی و تحتانی را روی 5 پیکسل و این مقدار را برای بخش‌های چپ و راست روی 0 پیکسل تنظیم می‌کند. در ادامه مقداری استایل‌های تعاملی اضافه می‌کنیم:

&:hover { background: #EEE }

کد فوق زمانی که کاربر اشاره‌گر ماوس را روی آیتم ببرد، پس‌زمینه آیتم را به رنگ خاکستری روشن درمی‌آورد. در کامپوننت اکسپورت شده به جای عناصر <li/> از کامپوننت‌های خود به صورت استایل‌دار استفاده می‌کنیم. در این مرحله استایل‌ها را روی لیست اعمال کرده و یک هدر به آن اضافه می‌کنیم. بار دیگر بخش‌های تغییر نیافته را نیاورده‌ایم.

فایل list.tsx – استایل‌بندی لیست

/* ...*/ 
const List = styled.ul`
    margin: auto;
    width: calc(100% - 20px);
    padding-left: 0;
`;

const Header = styled.li`
    padding: 5px;
    color: #888;
    font-weight: bold;
    list-style-type: none;
`;

export default function () {
    return <List>
        <Header>Name</Header>
        <Item>First Item</Item>
        <Item>Second Item</Item>
        <Item>Third Item</Item>
    </List>
};

ما می‌خواهیم کامپوننت <List/> کل عرض مرورگر را به جز یک فضای کوچک اشغال کند. این وضعیت با تنظیم width روی 100% منهای فضایی که می‌خواهیم بماند یعنی 20px انجام می‌شود.

نکته: پیش و پس از علامت – در تابع calc باید فاصله خالی باشد.

بخش margin: auto;‎ فضای باقیمانده را به تناسب توزیع می‌کند و از این رو لیست در مرکز قرار می‌گیرد. البته روش‌های دیگری نیز برای رسیدن به این وضعیت وجود دارند. استایل‌های دیگر خود گویا هستند. اینک نگاهی به اپلیکیشن خود می‌اندازیم:

لیست تعاملی در React

تعامل‌های پیشرفته کاربر

ظرفیت‌های بصری و تعاملی هر اپلیکیشن ری‌اکت بسیار فراتر از اعمال استایل‌های CSS روی کامپوننت‌ها هستند. برای نمونه می‌توان به ژست «کشیدن و رها کردن» (drag-and-drop) اشاره کرد. کشیدن و رها کردن یک روش شهودی برای جابجایی و چیدمان عناصر در اپلیکیشن‌های وب و موبایل محسوب می‌شود. این روش به یک رویداد اشاره‌گر (ماوس یا لمس انگشت) گوش می‌دهد، با داده‌ها کار می‌کند و DOM را تغییر می‌دهد.

drag-and-drop بخشی از HTML5 است. از این رو می‌توانیم این ژست را بر مبنای API سطح پایین HTML5 به اپلیکیشن خود اضافه کنیم. با این حال چندین کتابخانه دیگر نیز وجود دارند که این ژست را به روشی مناسب برای برنامه‌نویسی و در سطح بالا ارائه می‌کنند. برای مثال می‌توان به react-sortable-hoc (+) اشاره کرد. این کتابخانه مجموعه‌ای از کامپوننت‌های مرتبه بالاتر ری‌اکت است که هر لیست را به یک لیست قابل مرتب‌سازی، انیمیت شده و مناسب تعامل لمسی تبدیل می‌کند.

«کامپوننت‌های مرتبه بالاتر ری‌اکت» (higher-order React component) یا به‌اختصار HOG تکنیکی پیشرفته در ری‌اکت محسوب می‌شوند که برای استفاده مجدد از منطق کامپوننت معرفی شده‌اند. کامپوننت‌های راکت به طور معمول مشخصه‌ها را به چیزی بصری تبدیل می‌کنند. یک کامپوننت مرتبه بالاتر کارکردی را اضافه می‌کند که یک کامپوننت را به کامپوننتی دیگر تبدیل می‌کند.

از این کامپوننت مرتبه بالای react-sortable-hoc می‌توانیم برای افزودن قابلیت «کشیدن و رها کردن» به کامپوننت‌های خود استفاده می‌کنیم. اما پیش از افزودن کارکرد کشیدن و رها کردن به لیست طراحی شده، باید ابتدا لی‌آوت و داده‌ها را از هم جدا کنیم. در حال حاضر داده‌های ما (نام‌های لیست) در کامپوننت بصری ترکیب شده‌اند. هر آیتم منفرد به صورت هارد کد در کامپوننت نوشته شده است. این وضعیت نه انعطاف‌پذیر و نه بسط‌پذیر نیست. کد زیر روش جداسازی لی‌آوت و داده‌ها را نشان می‌دهد.

فایل list.tsx – جداسازی لیست و داده‌ها

/* ...*/ 
export default function () {
    const items = ["First Item", "Second Item", "Third Item"];

    return <List>
        <Header>Name</Header>
        {
            items.map((item, index) => (
                <Item key={`item-${index}`}>{item}</Item>
            ))
        }
    </List>
}

درون تابع اکسپورت شده یک ثابت به نام items تعریف می‌کنیم. این ثابت آرایه‌ای از رشته‌ها است که شامل داده‌هایی که است که قبلاً درون کامپوننت‌های <Item /> هارد کد کرده بودیم.

در بلوک کد بین خطوط 7 تا 11 داده‌های جداسازی شده را مجدداً در کامپوننت و در بخش کامپوننت‌های <Item /> وارد کرده‌ایم. این آرایه تابع map ارائه می‌کند. این تابع هر یک از آیتم‌های آرایه را گرفته و آن را به صورت تابعی که به عنوان آرگومان ارائه می‌کنیم تبدیل می‌کند و نتیجه را به صوت یک آرایه جدید بازگشت می‌دهد. در این فرایند آرایه اصلی تغییر نمی‌یابد.

تابعی که به عنوان آرگومان ارائه می‌کنیم از خط 8 کد فوق آغاز می‌شود. این یک تابع «بی‌نام» (anonymous) است و از نمادگذاری arrow پیروی می‌کند:

(arguments) => ("returned result")

همچنین از دو آرگومان item و index استفاده می‌کنیم. با این که نامگذاری این دو آرگومان به اختیار ما است اما ترتیب آن‌ها مهم است. آرگومان اول آیتم کنونی است. این همان رشته است. آرگومان دوم اندیس آیتم جاری درون آرایه است. از آنجا که سه آیتم در آرایه items داریم، تابع map، تابع ارائه شده را سه بار فرا می‌خواند. ابتدا با آرگومان‌های “item=”First Item و index=0 فرا می‌خواند. سپس با آرگومان‌های “item=”Second Item و index=1 فراخوانی می‌کند و بار سوم نیز می‌توانید حدس بزنید که با کدام آرگومان‌ها فراخوانی خواهد کرد.

این تابع آن دو آرگومان را تبدیل کرده و یک کامپوننت <Item/> بازگشت می‌دهد. این تابع {item} را به عنوان یک محتوای بصری تعیین می‌کند و یک رشته به صورت “item-0” and item-1 به عنوان مقدار مشخصه key ارائه می‌کند. این وضعیت یک دلیل فنی دارد. هنگامی که مانند تابع map یک بلوک کد را در JSX ارائه می‌کنیم که یک آرایه بازگشت می‌دهد، ری‌اکت ملزم است که یک مشخصه key یکتا برای هر آیتم آرایه در اختیار ما قرار دهد. اگر اپلیکیشن را اجرا کنید، نباید هیچ تفاوتی ببینید. اما اکنون همه داده‌های خود را در یک آرایه داریم که از لی‌آوت مجزا است. از این رو اکنون آماده افزودن کارکرد «کشیدن و رها کردن» از طریق کتابخانه هستیم. ابتدا به کد زیر نگاه کنید:

فایل list.tsx – افزودن کارکرد کشیدن و رها کردن

/* ...*/
import {SortableContainer, SortableElement} from 'react-sortable-hoc';
const SortableItem = SortableElement(Item);

const SortableList = SortableContainer(props => {
    return (
        <List>
            <Header>Name</Header>
            {
                props.items.map((item, index) => (
                    <SortableItem key={`item-${index}`} index={index}>{item}</SortableItem>
                ))
            }
        </List>
    );
});

export default function () {
    const items = ["First Item", "Second Item", "Third Item"];

    return <SortableList items={items}/>;
};

در خط 2 کد فوق، دو کامپوننت مرتبه بالاتر به نام‌های SortableContainer و SortableElement را ایمپورت کرده‌ایم. تابع SortableElement یک کامپوننت قابل کشیدن می‌گیرد. در خط 3 SortableElement تابع Item را به عنوان یک آرگومان می‌گیرد و تابع قابل کشیدن مشابهی بازگشت می‌دهد.

نکته: توجه کنید که آن را به صورت یک کامپوننت رندر شده با <Item/> عرضه نکرده‌ایم.

تابع SortableContainer یک کامپوننت والد آماده می‌کند که شامل یک فرزند قابل کشیدن است. می‌توانستیم مانند قبل، تابع List را به درون آن ارسال کنیم. اما روش دیگری نیز وجود دارد که در ادامه بررسی می‌کنیم. هر تابع ری‌اکت مشخصه‌ها را به چیزی بصری مانند زیر تبدیل می‌کند:

const Component = (props) => <div/>

یک کامپوننت مرتبه بالاتر اعلان تابع (نام) از کامپوننت یا بدنه آن را می‌پذیرد. در خط 5 از حالت دوم استفاده کرده‌ایم. ما props را گرفته و آن‌ها را به <List/> به همراه یک <Header/> و آرایه items نگاشت شده تبدیل می‌کنیم. ما props را تنها برای گنجاندن items می‌پذیریم. آرایه items زمانی که <SortableList/> رندر شده در تابع اکسپورت شده بازگشت یابد در اختیار ما قرار می‌گیرد. نتیجه یک تابع اکسپورت شده منسجم است. بدین ترتیب همه منطق پراکنده و به هم مرتبط در کامپوننت <SortableList/> می‌ماند.

در خط 11، مشخصه index به <SortableFile/> اضافه می‌شود. این همان sortableIndex عنصر است که درون آرایه قرار دارد و برای کتابخانه react-sortable-hoc الزامی است. اگر به اپلیکیشن مراجعه کنید، می‌بینید که اینک می‌توان دو فایل را کشید. اما زمانی که آن‌ها را رها کنید، به موقعیت اولیه خود بازمی‌گردند. این وضعیت جای شگفتی ندارد، زیرا آرایه items ما تغییر نمی‌یابد.

لیست تعاملی در React

حالت کامپوننت محلی

هنگامی که به مستندات react-sortable-hoc (+) مراجعه می‌کنیم، می‌بینیم که کامپوننت مرتبه بالاتر SortableContainer مشخصه onSortEnd را به <List/> ما اضافه می‌کند. این مشخصه یک تابع به عنوان آرگومان می‌گیرد که هنگام پایان مرتب‌سازی فراخوانی می‌شود. این مشخصه مقادیر oldIndex و newIndex آیتم کشیده شده را می‌گیرد. اینک سؤال این است که آرایه items را چگونه باید تغییر دهیم؟ این آرایه یک const «تغییرناپذیر» (immutable) است. عوض کردن آن به یک متغیر var «تغییرپذیر» (mutable) به ما کمک نخواهد کرد زیرا هر زمان که ری‌اکت کامپوننت را رندر کند از نو آغاز می‌شود. این بدان معنی است که مقدار یک متغیر را به خاطر نمی‌سپارد. تا زمانی که ری‌اکت کامپوننتی را رندر مجدد نکرده است، ما هیچ تغییری نخواهیم دید.

راه‌حل این مسئله استفاده از قلاب useState ری‌اکت است که از نسخه 16.8.2 ارائه شده است. قلاب‌ها روشی برای الصاق رفتار با قابلیت استفاده مجدد به یک کامپوننت محسوب می‌شوند. در چنین وضعیتی قلاب‌ها مشابه کامپوننت‌های مرتبه بالاتر عمل می‌کنند. علی‌رغم این که کامپوننت‌های مرتبه بالاتر باید خارج از wrapper-ها قرار گیرند و از این رو سلسله‌مراتب کامپوننت را تغییر می‌دهند، قلاب‌ها امکان استفاده مجدد از منطق را بدون تغییر دادن آن فراهم می‌سازند.

کد زیر یک حالت (State) به لیست ما اضافه می‌کند.

فایل list.tsx – افزودن قلاب useState

import React, {useState} from 'react';
/* ...*/
export default function () {
    const [items, setItems] = useState(["First Item", "Second Item", "Third Item"]);

    return <SortableList items={items} onSortEnd={
        ({oldIndex, newIndex}) => {
            const removed = items.slice(0, oldIndex)
                .concat(items.slice(oldIndex+1));
                
            setItems(
                removed.slice(0,newIndex)
                    .concat([items[oldIndex]])
                    .concat(removed.slice(newIndex))
            );
        }
    }/>
};

useState یک آرایه بازگشت می‌دهد که آرایه‌ای با دو عنصر است. عنصر نخست حالت کنونی است. از آنجا که تغییری در آن نداده‌ایم، حالت اولیه ما یعنی آرایه items را شامل می‌شود. از آن به همان روش قبل استفاده می‌کنیم. در خط 6 یک مشخصه برای کامپوننت <SortableList/> ارائه کرده‌ایم. همه موارد جدید در همین عنصر دوم هستند. این تابعی است که حالت را تعیین می‌کند. می‌توانیم از این نام خصوصیت استفاده کنیم. نام آن را setItems می‌گذاریم. از این تابع در تابع onSortEnd در خط 11 استفاده کرده‌ایم. بدین ترتیب آرایه جدیدی به نام items با به‌روزرسانی ترتیب آیتم‌ها ارائه می‌کنیم.

در خط 8 یک آرایه جدید موقت بدون فایل‌های قابل کشیدن ارائه می‌کنیم. تابع slice آرایه یک بخش از آرایه را بازگشت می‌دهد. دو پارامتر slice اندیس‌های begin و end را تعیین می‌کنند. تابع connect دو آرایه را به هم اتصال می‌دهد. بنابراین آرایه فرعی را از آغاز تا آیتم کشیده شده می‌گیریم و آن را به آرایه فرعی از پس از آیتم کشیده شده تا انتها وصل می‌کنیم. این اتفاق در زمان فراخوانی slice در خط 9 انجام می‌شود. بدین ترتیب آرایه را از آغاز تا موقعیت جدید برش داده و آن را به آیتم کشیده شده که از آرایه items اصلی گرفته شده، اتصال می‌دهیم و سپس همه این موارد را به آرایه موقت باقیمانده اتصال می‌دهیم. بدین ترتیب آرایه items با ترتیب جدید در اختیار تابع setItems قرار می‌گیرد.

آیا خطایی در خط 4 وجود دارد؟ ما آرایه items را به صورت یک const اعلان کردیم، اما نمی‌توان یک const را تغییر داد! تابع setItems این مقادیر را تغییر نمی‌دهد. این تابع ری‌اکت را وامی‌دارد که رابط کاربری را با مقادیر جدید رندر مجدد کند. ری‌اکت <SortableList/> قدیمی را با جدید تعویض می‌کند. این تابع تنها در مورد حالت جدید اطلاع دارد. برای آن مهم نیست که چه حالتی به آن ارسال می‌کنید و یا این حالت چگونه ایجاد شده است.

اینک نگاهی به لیست تعاملی خود می‌اندازیم:

لیست تعاملی در React

کد کامل فایل list.tsx به صورت زیر است:

import React, { useState } from 'react';

import styled from 'styled-components'
import {SortableContainer, SortableElement} from 'react-sortable-hoc';

const List = styled.ul`
    margin: auto;
    width: calc(100% - 20px);
    padding-left: 0;
`;

const Header = styled.li`
    padding: 5px;
    color: #888;
    font-weight: bold;
    list-style-type: none;
`;

const Item = styled.li`
  display: block;
  border-top: 1px solid #888;
  list-style-type: none;
  padding: 5px 0;
  &:hover {
      background: #EEE;
  }  
`;

const SortableItem = SortableElement(Item);

const SortableList = SortableContainer(props => {
    return (
        <List>
            <Header>Name</Header>
            {
                props.items.map((item, index) => (
                    <SortableItem key={`item-${index}`} index={index}>{item}</SortableItem>
                ))
            }
        </List>
    );
});

export default function () {
    const [items, setItems] = useState(["First Item", "Second Item", "Third Item"]);

    return <SortableList items={items} onSortEnd={
        ({oldIndex, newIndex}) => {
            const removed = items.slice(0, oldIndex)
                .concat(items.slice(oldIndex+1));

            setItems(
                removed.slice(0,newIndex)
                    .concat([items[oldIndex]])
                    .concat(removed.slice(newIndex))
            );
        }
    }/>
};

سورس کد کامل این پروژه را می‌توانید در این ریپوی گیت‌هاب (+) ملاحظه کنید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای 0 نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

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

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *