متن اصلی مقاله: JavaScript To Know for React نوشته Kent C. Dodds
ویژگی های مورد نیاز جاوا اسکریپتی که برای آموختن و استفاده کردن از ریاکت باید بدانید.
یکی از چیزهایی که راجع به ریاکت نسبت به بقیه فریمورک هایی که ازشون استفاده کردم خیلی دوستش دارم اینه که چقدر ناچاریم به جاوا اسکریپت پناه ببریم وقتی که از ریاکت استفاده میکنیم. هیچ الگو DSL ای وجود نداره (JSX به جاوا اسکریپت محسوس کامپایل میشه)، API کامپوننت ها با اضافه شدن ریاکت هوکس فقط ساده تر شده، و فریمورکش انتزاع خیلی کوچکی خارج از مسئله های اصلی UI ای که قراره حلشون کنه رو بهتون میده.
واسه همین، آموختن ویژگی های جاوا اسکریپت براتون خیلی مطلوبه تا بتونید در ساختن برنامه های کاربردی با ریاکت مؤثر باشید. پس این شما و اینم یه مقدار ویژگی جاوا اسکریپتی که بهتون پیشنهاد میکنم یه مقدار زمان بذارید و یاد بگیرید. باشد که در استفاده از ریاکت تا جای ممکن مؤثر واقع بشید.
قبل از اینکه وارد یه سری قضایای سینتکسی بشیم، یه چیز دیگه ای که خیلی میتونه در فهم ریاکت مفید واقع بشه مفهوم کلوژر [closure] هست. یه مقاله عالی در باب این مفهوم اینجاست: mdn.io/closure.
خیله خب، بریم سراغ ویژگی های JS ای که در مواجهه با ریاکت میخواید بدونید.
الگو های تحت اللفظی شبیه string های سادهان با قدرت های ماورائی:
const greeting = 'Hello' const subject = 'World' console.log(`${greeting} ${subject}!`) // Hello World! // :معادله با console.log(greeting + ' ' + subject + '!') // :توی ریاکت function Box({className, ...props}) { return <div className={`box ${className}`} {...props} /> }
این انقدر متداول و کارآمده که دیگه الان بدون اینکه بهش فکر کنم انجامش میدم.
const a = 'hello' const b = 42 const c = {d: [true, false]} console.log({a, b, c}) // :معادله با console.log({a: a, b: b, c: c}) // :توی ریاکت function Counter({initialCount, step}) { const [count, setCount] = useCounter({initialCount, step}) return <button ={setCount}>{count}</button> }
MDN: Object initializer New notations in ECMAScript 2015
توابع پیکانی یه راه دیگه برای نوشتن توابع در جاوا اسکریپت هستن، ولی یه چندتا تفاوت معنایی هم دارن. خوشبختانه ما در دنیای ریاکت، نیازی نیست که اونقدری نگران this باشیم اگه از ریاکت هوکس توی پروژه مون استفاده کنیم (بجای کلاس ها)، ولی توابع پیکانی اجازه نوشتن توابع ناشناس مختصر و return های ضمنی رو میدن، در نتیجه شما به کرات توابع پیکانی رو میبینید و میخواید که ازشون استفاده کنید.
const getFive = () => 5 const addFive = a => a + 5 const divide = (a, b) => a / b // :معادله با function getFive() { return 5 } function addFive(a) { return a + 5 } function divide(a, b) { return a / b } // :توی ریاکت function TeddyBearList({teddyBears}) { return ( <ul> {teddyBears.map(teddyBear => ( <li key={teddyBear.id}> <span>{teddyBear.name}</span> </li> ))} </ul> ) }
یه چیزی که راجع به مثال بالا بهتره بهش دقت کنیم پرانتز های باز و بسته هستن (. این یه روش مرسوم برای استفاده حداکثری از قابلیت return ضمنی توابع پیکانیه وقتی که داریم با JSX کار میکنیم.
تخریب کردن احتمالا ویژگی جاوا اسکریپتی مورد علاقه منه. من همش اشیاء [obejcts] و آرایه ها [arrays] رو تخریب میکنم (و اگه شما هم از useState استفاده میکنین پس یحتمل شما هم همینکارو میکنین، به این شکل). عاشق این همه اعلامی بودنشم.
// const obj = {x: 3.6, y: 7.8} // makeCalculation(obj) function makeCalculation({x, y: d, z = 4}) { return Math.floor((x + d + z) / 3) } // :معادله با function makeCalculation(obj) { const {x, y: d, z = 4} = obj return Math.floor((x + d + z) / 3) } // :که معادله با function makeCalculation(obj) { const x = obj.x const d = obj.y const z = obj.z === undefined ? 4 : obj.z return Math.floor((x + d + z) / 3) } // :توی ریاکت function UserGitHubImg({username = 'ghost', ...props}) { return <img src={`https://github.com/${username}.png`} {...props} /> }
حتما مقاله MDN رو بخونید. مطمئنا یه چیز جدید یاد میگیرید. وقتی تموم شد، سعی کنید کد پایین رو طوری refactor کنید که توی یک خط از قابلیت تخریب کنندگی استفاده کنه:
function nestedArrayAndObject() { // کنید که از قابلیت تخریب کنندگی استفاده کنه refactor این کد رو طوری const info = { title: 'Once Upon a Time', protagonist: { name: 'Emma Swan', enemies: [ {name: 'Regina Mills', title: 'Evil Queen'}, {name: 'Cora Mills', title: 'Queen of Hearts'}, {name: 'Peter Pan', title: `The boy who wouldn't grow up`}, {name: 'Zelena', title: 'The Wicked Witch'}, ], }, } // const {} = info // <-- بعدی رو با این جایگزین کنید 'const' چند خط const title = info.title const protagonistName = info.protagonist.name const enemy = info.protagonist.enemies[3] const enemyTitle = enemy.title const enemyName = enemy.name return `${enemyName} (${enemyTitle}) is an enemy to ${protagonistName} in "${title}"` }
اینم یه ویژگی دیگه که من همش ازش استفاده میکنم. راهی به قطع قدرتمند برای بیان مقادیر پیشفرض به شیوه اعلامی برای توابع شما.
// add(1) // add(1, 2) function add(a, b = 0) { return a + b } // :معادله با const add = (a, b = 0) => a + b // :معادله با function add(a, b) { b = b === undefined ? 0 : b return a + b } // :توی ریاکت function useLocalStorageState({ key, initialValue, serialize = v => v, deserialize = v => v, }) { const [state, setState] = React.useState( () => deserialize(window.localStorage.getItem(key)) || initialValue, ) const serializedState = serialize(state) React.useEffect(() => { window.localStorage.setItem(key, serializedState) }, [key, serializedState]) return [state, setState] }
سینتکس ... (سه نقطه) رو میشه به عنوان یه جور سینتکس "مجموعه" بهش فکر کرد از اونجایی که روی مجموعه ای از مقادیر، عملیاتی رو انجام میده. من همیشه ازش استفاده میکنم و قویاً پیشنهاد میکنم شما هم یاد بگیرید که کجا و چگونه میشه به خوبی ازش استفاده کرد. در حقیقت در زمینه های مختلف، معانی مختلفی میگیره، پس فراگرفتن تفاوت های ظریف، اونجاها بهتون کمک میکنه.
const arr = [5, 6, 8, 4, 9] Math.max(...arr) // :معادله با Math.max.apply(null, arr) const obj1 = { a: 'a from obj1', b: 'b from obj1', c: 'c from obj1', d: { e: 'e from obj1', f: 'f from obj1', }, } const obj2 = { b: 'b from obj2', c: 'c from obj2', d: { g: 'g from obj2', h: 'g from obj2', }, } console.log({...obj1, ...obj2}) // :معادله با console.log(Object.assign({}, obj1, obj2)) function add(first, ...rest) { return rest.reduce((sum, next) => sum + next, first) } // :معادله با function add() { const first = arguments[0] const rest = Array.from(arguments).slice(1) return rest.reduce((sum, next) => sum + next, first) } // :توی ریاکت function Box({className, ...restOfTheProps}) { const defaultProps = { className: `box ${className}`, children: 'Empty box', } return <div {...defaultProps} {...restOfTheProps} /> }
اگه در حال ساختن یه app با ابزار های مدرن هستین، شانس اینکه از ماژول ها پشتیبانی کنه بالاس، اینکه یاد بگیرید سینکتسش هم چجوری کار میکنه ایده ی خوبیه چون هر اپلیکیشنی حتی در سایز های جزئی هم یحتمل نیاز داشته باشه که از ماژول ها برای استفاده مجدد از کد و سازمان دهی استفاده کنه.
export default function add(a, b) { return a + b } /* * import add from './add' * console.assert(add(3, 2) === 5) */ export const foo = 'bar' /* * import {foo} from './foo' * console.assert(foo === 'bar') */ export function subtract(a, b) { return a - b } export const now = new Date() /* * import {subtract, now} from './stuff' * console.assert(subtract(4, 2) === 2) * console.assert(now instanceof Date) */ // ایمپورت های داینامیک import('./some-module').then( allModuleExports => { // همون آبجکتی خواهد بود که میگیرید اگه از allModuleExports آبجکت // .استفاده کرده باشید import * as allModuleExports from './some-module' // تنها فرقش اینه که به صورت غیرهمزمان بارگذاری خواهد شد که این میتونه // .توی بعضی موارد مفید باشه }, error => { // رسیدگی به خطا // این زمانی اتفاق میوفته که یه خطایی در حین بارگذاری یا اجرا کردن ماژول داشته باشیم }, ) // :توی ریاکت import React, {Suspense, Fragment} from 'react' // React ایمپورت داینامیک یه کامپوننت const BigComponent = React.lazy(() => import('./big-component')) // big-component.js would need to "export default BigComponent" for this to work
به عنوان یه مرجع دیگه، من (جناب Kent C. Dodds) یه ارائه کامل راجع به این سینتکس دادم و شما میتونید اون ارائه رو اینجا نگاه کنید.
من عاشق سه تایی هام. اونا به شکل زیبایی اعلامی ان. بخصوص توی JSX.
const message = bottle.fullOfSoda ? 'The bottle has soda!' : 'The bottle may not have soda :-(' // معادله با let message if (bottle.fullOfSoda) { message = 'The bottle has soda!' } else { message = 'The bottle may not have soda :-(' } // :توی ریاکت function TeddyBearList({teddyBears}) { return ( <React.Fragment> {teddyBears.length ? ( <ul> {teddyBears.map(teddyBear => ( <li key={teddyBear.id}> <span>{teddyBear.name}</span> </li> ))} </ul> ) : ( <div>There are no teddy bears. The sadness.</div> )} </React.Fragment> ) }
متوجه هستم که سه تایی ها میتونن یجور عکسالعمل غیر ارادی حاکی از انزجار از سمت بعضیا دریافت کنن. کسایی که مجبور بودن تلاش برای پیدا کردن یه درکی از سه تایی ها رو تحمل کنن تا قبل از اینکه prettier سر و کلهش پیدا بشه و کدمون رو تر و تمیز کنه. اگه هنوز از prettier استفاده نکردین، قویاً بهتون توصیه میکنم از همین الان شروع کنین. Prettier یه کاری میکنه سه تایی هاتون بسیار راحت تر خونده بشن.
MDN: Conditional (ternary) operator
آرایه ها محشرن و من همیشه خدا از متد های آرایه استفاده میکنم! یحتمل غالباً از متد های ذیل استفاده میکنم:
اینم یه چندتا مثال:
const dogs = [ { id: 'dog-1', name: 'Poodle', temperament: [ 'Intelligent', 'Active', 'Alert', 'Faithful', 'Trainable', 'Instinctual', ], }, { id: 'dog-2', name: 'Bernese Mountain Dog', temperament: ['Affectionate', 'Intelligent', 'Loyal', 'Faithful'], }, { id: 'dog-3', name: 'Labrador Retriever', temperament: [ 'Intelligent', 'Even Tempered', 'Kind', 'Agile', 'Outgoing', 'Trusting', 'Gentle', ], }, ] dogs.find(dog => dog.name === 'Bernese Mountain Dog') // {id: 'dog-2', name: 'Bernese Mountain Dog', ...etc} dogs.some(dog => dog.temperament.includes('Aggressive')) // false dogs.some(dog => dog.temperament.includes('Trusting')) // true dogs.every(dog => dog.temperament.includes('Trusting')) // false dogs.every(dog => dog.temperament.includes('Intelligent')) // true dogs.map(dog => dog.name) // ['Poodle', 'Bernese Mountain Dog', 'Labrador Retriever'] dogs.filter(dog => dog.temperament.includes('Faithful')) // [{id: 'dog-1', ..etc}, {id: 'dog-2', ...etc}] dogs.reduce((allTemperaments, dog) => { return [...allTemperaments, ...dog.temperament] }, []) // [ 'Intelligent', 'Active', 'Alert', ...etc ] // :توی ریاکت function RepositoryList({repositories, owner}) { return ( <ul> {repositories .filter(repo => repo.owner === owner) .map(repo => ( <li key={repo.id}>{repo.name}</li> ))} </ul> ) }
اگه یه مقداری null یا undefined باشه، اون وقته که یحتمل بخواین به یه مقدار پیشفرض دلتون رو گرم کنین:
// console.log("") // :این اون کاریه که قبلاً غالباً انجام میدادیم x = x || 'some default' // مقادیر معتبری بودن مشکل ساز بود "false" اما این وقتایی که توی اعداد یا بولیَن ها "0" یا // :پس اگه میخواستیم یه همچین چیزی رو ساپورت کنیم add(null, 3) // :این کاری بود که باید قبلش انجام میدادیم function add(a, b) { a = a == null ? 0 : a b = b == null ? 0 : b return a + b } // این کاریه که الان میتونیم انجام بدیم function add(a, b) { a = a ?? 0 b = b ?? 0 return a + b } // :توی ریاکت function DisplayContactName({contact}) { return <div>{contact.name ?? 'Unknown'}</div>}
MDN: Nullish coalescing operator
موسوم به "اُپراتور اِلویس"، به شما این اجازه رو میده که بی خطر به پراپرتی ها دسترسی پیدا کنین و توابعی رو call کنین ممکنه وجود داشته و یا نداشته باشن. قبل از زنجیر کردن اختیاری، از یه راه حل هک طور استفاده میکردیم که به falsy و truthy بودن وابسته بود.
// console.log("") // :کاری که قبل از زنجیر کردن اختیاری میکردیم const streetName = user && user.address && user.address.street.name // :کاری که الان میتونیم بکنیم const streetName = user?.address?.street?.name // باشه بازم اجرا میشه (که در اون undefined هم options این حتی اگه // (میشه undefined هم onSuccess حالت // میشد fail هیچوقت تعریف نمیشد بازم options هرچند اگه // .از اونجایی که زنجیر کردن اختیاری نمیتونه توی یه شیء ریشه ای که وجود نداره استفاده بشه // if (typeof options == "undefined") زنجیر کردن اختیاری بررسی هارو جایگزین نمیکنه. مثلا const onSuccess = options?.onSuccess // باشه هم بازم بدون هیچ خطایی اجرا میشه undefined حتی onSuccess این اگه // (که در اون حالت، هیچ تابعی کال نمیشه) onSuccess?.({data: 'yay'}) // :و میتونیم همه ی اونارو ترکیب کنیم توی یه خط options?.onSuccess?.({data: 'yay'}) // وجود داشته باشه options یه تابعه اگه که onSuccess و اگه ۱۰۰٪ مطمئنید که // پس دیگه به اون ؟. اضافه نیازی ندارید قبل از اینکه کالش کنید. از ؟. فقط در شرایطی استفاده کنید که // .اون چیزی که سمت چپه محتمله که وجود نداشته باشه options?.onSuccess({data: 'yay'}) // in React: function UserProfile({user}) { return ( <div> <h1>{user.name}</h1> <strong>{user.bio?.slice(0, 50)}...</strong> </div> ) }
فقط حواستون باشه که اگه دیدید که دارید خیلی توی کدتون از ؟. استفاده میکنید، شاید بخواید یه نگاهی به اونجایی که این مقادیر از اونجا سرچشمه میگیرن بندازید و مطمئن بشید که دائماً همون مقادیری رو بر میگردونن که باید.
این یکی یه موضوع گسترده اس و ممکنه لازم باشه یه مقدار باهاش تمرین کنید و خلاصه یکم زمان ببره تا لِمِش دستتون بیاد. وعده ها [promises] جای جایِ اِکوسیستمِ جاوا اسکریپت هستن و با تشکر از میزان زیادِ تثبیت شدگی ریاکت در همین اکوسیستم، جای جایِ ریاکت هم قرار دارن. (در حقیقت، خوده ریاکت توی کد بیسش از وعده ها [promises] استفاده میکنه).
وعده ها [promises] بهتون توی مدیریت کردن کد غیرهمزمان [asynchronous] کمک میکنه و توسط خیلی از API های DOM هم return میشه همچنین از خیلی از کتابخونه های شخص ثالث هم همینطور. سینتکس همزمان/انتظار [async/await] یه سینتکس خاص واسه برخورد کردن با وعده هاست [promises]. این دو تا اصولاً دست در دست همن و با هم به کار میان.
function promises() { const successfulPromise = timeout(100).then(result => `success: ${result}`) const failingPromise = timeout(200, true).then(null, error => Promise.reject(`failure: ${error}`), ) const recoveredPromise = timeout(300, true).then(null, error => Promise.resolve(`failed and recovered: ${error}`), ) successfulPromise.then(log, logError) failingPromise.then(log, logError) recoveredPromise.then(log, logError) } function asyncAwaits() { async function successfulAsyncAwait() { const result = await timeout(100) return `success: ${result}` } async function failedAsyncAwait() { const result = await timeout(200, true) return `failed: ${result}` // این اجرا نخواهد شد } async function recoveredAsyncAwait() { let result try { result = await timeout(300, true) return `failed: ${result}` // این اجرا نخواهد شد } catch (error) { return `failed and recovered: ${error}` } } successfulAsyncAwait().then(log, logError) failedAsyncAwait().then(log, logError) recoveredAsyncAwait().then(log, logError) } function log(...args) { console.log(...args) } function logError(...args) { console.error(...args) } // (? این مادرِ همه غیرهمزمانی هاست (خدایی خودش اینطوری نوشته function timeout(duration = 0, shouldReject = false) { return new Promise((resolve, reject) => { setTimeout(() => { if (shouldReject) { reject(`rejected after ${duration}ms`) } else { resolve(`resolved after ${duration}ms`) } }, duration) }) } // :توی ریاکت function GetGreetingForSubject({subject}) { const [isLoading, setIsLoading] = React.useState(false) const [error, setError] = React.useState(null) const [greeting, setGreeting] = React.useState(null) React.useEffect(() => { async function fetchGreeting() { try { const response = await window.fetch('https://example.com/api/greeting') const data = await response.json() setGreeting(data.greeting) } catch (error) { setError(error) } finally { setIsLoading(false) } } setIsLoading(true) fetchGreeting() }, []) return isLoading ? ( 'loading...' ) : error ? ( 'ERROR!' ) : greeting ? ( <div> {greeting} {subject} </div> ) : null }
بی تردید تعداد زیادی ویژگی برنامه نویسی وجود داره وقتی که داریم یه اپ ریاکتی میسازیم به کارمون میان، ولی اینا یه تعدادی از مورد علاقه های من بودن که میدیدم خودم گاه و بی گاه استفاده میکنم. امیدوارم براتون مفید بوده باشه.
اگه دلتون میخواد راجع هر کدوم از اینا بیشتر بدونید، من (جناب کنت سی دادز) یه ورکشاپ جاوا اسکریپت دارم که وقتی توی پی پال کار میکردم ضبط و منتشرش کردم که ممکنه یاری دهنده باشه: ES6 and Beyond Workshop at Paypal
موفق باشید!