Avastage JavaScripti mustrisobituse võimsus. Uurige, kuidas see funktsionaalse programmeerimise kontseptsioon täiustab switch-lauseid puhtama, deklaratiivsema ja robustsema koodi jaoks.
Elegantsuse jõud: sügav sukeldumine JavaScripti mustrisobitusse
Aastakümneid on JavaScripti arendajad tingimusloogika jaoks toetunud tuttavale tööriistakomplektile: auväärsele if/else ahelale ja klassikalisele switch-lausele. Need on hargnemisloogika tööhobused, funktsionaalsed ja ettearvatavad. Kuid mida keerukamaks meie rakendused muutuvad ja mida enam me omaks võtame paradigmasid nagu funktsionaalne programmeerimine, seda ilmsemaks muutuvad nende tööriistade piirangud. Pikki if/else ahelaid võib olla raske lugeda ning switch-laused oma lihtsate võrdsuskontrollide ja läbikukkumise (fall-through) omapäradega jäävad sageli hätta keeruliste andmestruktuuridega tegelemisel.
Siin tuleb mängu mustrisobitus (Pattern Matching). See ei ole lihtsalt „steroididel switch-lause”; see on paradigma muutus. Funktsionaalsetest keeltest nagu Haskell, ML ja Rust pärinev mustrisobitus on mehhanism väärtuse kontrollimiseks mitmete mustrite vastu. See võimaldab teil lahti struktureerida keerulisi andmeid, kontrollida nende kuju ja käivitada koodi selle struktuuri põhjal – kõik ühes ainsas väljendusrikkas konstruktsioonis. See on liikumine imperatiivselt kontrollilt („kuidas väärtust kontrollida”) deklaratiivsele sobitamisele („milline väärtus välja näeb”).
See artikkel on põhjalik juhend mustrisobituse mõistmiseks ja kasutamiseks tänapäeva JavaScriptis. Uurime selle põhimõisteid, praktilisi rakendusi ja seda, kuidas saate teekide abil selle võimsa funktsionaalse mustri oma projektidesse tuua ammu enne, kui sellest saab keele omapära.
Mis on mustrisobitus? Samm edasi switch-lausetest
Oma olemuselt on mustrisobitus andmestruktuuride lahtivõtmise protsess, et näha, kas need vastavad konkreetsele „mustrile” või kujule. Kui vaste leitakse, saame käivitada seotud koodibloki, sidudes sageli sobitatud andmete osad kohalike muutujatega, et neid selles blokis kasutada.
Võrdleme seda traditsioonilise switch-lausega. switch on piiratud range võrdsuse (===) kontrolliga ühe väärtuse suhtes:
function getHttpStatusMessage(status) {
switch (status) {
case 200:
return 'OK';
case 404:
return 'Not Found';
case 500:
return 'Internal Server Error';
default:
return 'Unknown Status';
}
}
See töötab ideaalselt lihtsate, primitiivsete väärtuste puhul. Aga mis siis, kui tahaksime käsitleda keerukamat objekti, näiteks API vastust?
const response = { status: 'success', data: { user: 'John Doe' } };
// or
const errorResponse = { status: 'error', error: { code: 'E401', message: 'Unauthorized' } };
switch-lause ei suuda seda elegantselt käsitleda. Te oleksite sunnitud kasutama segast if/else-lausete jada, kontrollides omaduste olemasolu ja nende väärtusi. Siin särabki mustrisobitus. See suudab kontrollida objekti kogu kuju.
Mustrisobituse lähenemine näeks kontseptuaalselt välja selline (kasutades hüpoteetilist tuleviku süntaksit):
function handleResponse(response) {
return match (response) {
when { status: 'success', data: d }: `Õnnestus! Andmed kasutaja ${d.user} kohta on kätte saadud`,
when { status: 'error', error: e }: `Viga ${e.code}: ${e.message}`,
default: 'Vigane vastuse formaat'
}
}
Pange tähele peamisi erinevusi:
- Struktuurne sobitamine: See sobitub objekti kujuga, mitte ainult ühe väärtusega.
- Andmete sidumine: See eraldab pesastatud väärtused (nagu `d` ja `e`) otse mustri sees.
- Avaldisele orienteeritus: Kogu `match`-blokk on avaldis, mis tagastab väärtuse, kaotades vajaduse ajutiste muutujate ja `return`-lausete järele igas harus. See on funktsionaalse programmeerimise põhitõde.
Mustrisobituse hetkeseis JavaScriptis
On oluline seada selge ootus globaalsele arendajaskonnale: mustrisobitus ei ole veel JavaScripti standardne, sisseehitatud funktsioon.
On olemas aktiivne TC39 ettepanek selle lisamiseks ECMAScripti standardisse. Kuid selle kirjutamise hetkel on see 1. etapis (Stage 1), mis tähendab, et see on varajases uurimisfaasis. Tõenäoliselt kulub mitu aastat, enne kui näeme seda kõigis suuremates brauserites ja Node.js-i keskkondades sisseehitatuna.
Niisiis, kuidas saame seda täna kasutada? Saame toetuda elavale JavaScripti ökosüsteemile. On välja töötatud mitu suurepärast teeki, mis toovad mustrisobituse võimsuse kaasaegsesse JavaScripti ja TypeScripti. Selle artikli näidetes kasutame peamiselt ts-pattern'i, mis on populaarne ja võimas teek, täielikult tüübistatud, väga väljendusrikas ning töötab sujuvalt nii TypeScripti kui ka tavalistes JavaScripti projektides.
Funktsionaalse mustrisobituse põhimõisted
Süveneme põhimustritesse, millega kokku puutute. Kasutame oma koodinäidetes ts-pattern'i, kuid kontseptsioonid on universaalsed enamikus mustrisobituse implementatsioonides.
Literaalsed mustrid: kõige lihtsam vaste
See on kõige elementaarsem sobitamise vorm, sarnane `switch`-i case'iga. See sobitub primitiivsete väärtustega nagu stringid, numbrid, tõeväärtused, `null` ja `undefined`.
import { match } from 'ts-pattern';
function getPaymentMethod(method) {
return match(method)
.with('credit_card', () => 'Töötlemine krediitkaardiväravaga')
.with('paypal', () => 'Suunamine PayPal-i')
.with('crypto', () => 'Töötlemine krüptoraha rahakotiga')
.otherwise(() => 'Vigane makseviis');
}
console.log(getPaymentMethod('paypal')); // "Suunamine PayPal-i"
console.log(getPaymentMethod('bank_transfer')); // "Vigane makseviis"
.with(pattern, handler) süntaks on keskne. .otherwise() klausel on ekvivalentne `default`-juhuga ja on sageli vajalik, et tagada vaste ammendavus (käsitleb kõiki võimalusi).
Destruktureerivad mustrid: objektide ja massiivide lahtipakkimine
Siin eristub mustrisobitus tõeliselt. Saate sobitada objektide ja massiivide kuju ja omaduste vastu.
Objekti destruktureerimine:
Kujutage ette, et töötlete sündmusi rakenduses. Iga sündmus on objekt, millel on `type` ja `payload`.
import { match, P } from 'ts-pattern'; // P on kohatäitja objekt
function handleEvent(event) {
return match(event)
.with({ type: 'USER_LOGIN', payload: { userId: P.select() } }, (userId) => {
console.log(`Kasutaja ${userId} logis sisse.`);
// ... käivita sisselogimise kõrvalmõjud
})
.with({ type: 'ADD_TO_CART', payload: { productId: P.select('id'), quantity: P.select('qty') } }, ({ id, qty }) => {
console.log(`Lisati ${qty} toodet ${id} ostukorvi.`);
})
.with({ type: 'PAGE_VIEW' }, () => {
console.log('Lehekülje vaatamine jälgitud.');
})
.otherwise(() => {
console.log('Tundmatu sündmus vastu võetud.');
});
}
handleEvent({ type: 'USER_LOGIN', payload: { userId: 'u-123', timestamp: 1678886400 } });
handleEvent({ type: 'ADD_TO_CART', payload: { productId: 'prod-abc', quantity: 2 } });
Selles näites on P.select() võimas tööriist. See toimib metamärgina, mis sobitub mis tahes väärtusega selles positsioonis ja seob selle, tehes selle kättesaadavaks käsitlejafunktsioonile. Saate valitud väärtustele isegi nimesid anda kirjeldavama käsitleja signatuuri jaoks.
Massiivi destruktureerimine:
Saate sobitada ka massiivide struktuuri järgi, mis on uskumatult kasulik selliste ülesannete jaoks nagu käsurea argumentide parsimine või tuplilaadsete andmetega töötamine.
function parseCommand(args) {
return match(args)
.with(['install', P.select()], (pkg) => `Paigaldan paketti: ${pkg}`)
.with(['delete', P.select(), '--force'], (file) => `Kustutan faili sunniviisiliselt: ${file}`)
.with(['list'], () => 'Kuvatakse kõik elemendid...')
.with([], () => 'Käsku ei antud. Valikute nägemiseks kasuta --help.')
.otherwise((unrecognized) => `Viga: Tundmatu käskude jada: ${unrecognized.join(' ')}`);
}
console.log(parseCommand(['install', 'react'])); // "Paigaldan paketti: react"
console.log(parseCommand(['delete', 'temp.log', '--force'])); // "Kustutan faili sunniviisiliselt: temp.log"
console.log(parseCommand([])); // "Käsku ei antud..."
Metamärgi ja kohatäitja mustrid
Oleme juba näinud P.select()-i, siduvat kohatäitjat. ts-pattern pakub ka lihtsat metamärki, P._, kui peate sobitama positsiooni, kuid ei hooli selle väärtusest.
P._(Metamärk): Sobitub mis tahes väärtusega, kuid ei seo seda. Kasutage seda, kui väärtus peab olemas olema, kuid te ei kasuta seda.P.select()(Kohatäitja): Sobitub mis tahes väärtusega ja seob selle kasutamiseks käsitlejas.
match(data)
.with(['SUCCESS', P._, P.select()], (message) => `Õnnestus sõnumiga: ${message}`)
// Siin ignoreerime teist elementi, kuid pĂĽĂĽame kinni kolmanda.
.otherwise(() => 'Õnnestumise sõnum puudub');
Valveklauslid: tingimusloogika lisamine .when()-ga
Mõnikord ei piisa kuju sobitamisest. Võib-olla peate lisama täiendava tingimuse. Siin tulevad appi valveklauslid. ts-pattern'is tehakse seda .when() meetodi või P.when() predikaadiga.
Kujutage ette tellimuste töötlemist. Soovite suure väärtusega tellimusi käsitleda erinevalt.
function getOrderStatus(order) {
return match(order)
.with({ status: 'shipped', total: P.when(t => t > 1000) }, () => 'Suure väärtusega tellimus on teele pandud.')
.with({ status: 'shipped' }, () => 'Standardtellimus on teele pandud.')
.with({ status: 'processing', items: P.when(items => items.length === 0) }, () => 'Hoiatus: Töödeldakse tühja tellimust.')
.with({ status: 'processing' }, () => 'Tellimust töödeldakse.')
.with({ status: 'cancelled' }, () => 'Tellimus on tĂĽhistatud.')
.otherwise(() => 'Tundmatu tellimuse staatus.');
}
console.log(getOrderStatus({ status: 'shipped', total: 1500 })); // "Suure väärtusega tellimus on teele pandud."
console.log(getOrderStatus({ status: 'shipped', total: 50 })); // "Standardtellimus on teele pandud."
console.log(getOrderStatus({ status: 'processing', items: [] })); // "Hoiatus: Töödeldakse tühja tellimust."
Pange tähele, kuidas spetsiifilisem muster (koos .when() valvega) peab tulema enne üldisemat. Esimene muster, mis edukalt sobitub, võidab.
TĂĽĂĽbi ja predikaadi mustrid
Saate sobitada ka andmetüüpide või kohandatud predikaatfunktsioonide vastu, pakkudes veelgi suuremat paindlikkust.
function describeValue(x) {
return match(x)
.with(P.string, () => 'See on string.')
.with(P.number, () => 'See on number.')
.with({ message: P.string }, () => 'See on veaobjekt.')
.with(P.instanceOf(Date), (d) => `See on kuupäeva objekt aastale ${d.getFullYear()}.`)
.otherwise(() => 'See on mingi muu tüüpi väärtus.');
}
Praktilised kasutusjuhud kaasaegses veebiarenduses
Teooria on tore, kuid vaatame, kuidas mustrisobitus lahendab reaalseid probleeme globaalse arendajaskonna jaoks.
Keerukate API vastuste käsitlemine
See on klassikaline kasutusjuht. API-d tagastavad harva ühtset, kindla kujuga vastust. Nad tagastavad edukaid objekte, erinevaid veaobjekte või laadimise olekuid. Mustrisobitus teeb selle kõik ilusti korda.
Viga: Soovitud ressurssi ei leitud. Ilmnes ootamatu viga: ${err.message}// Oletame, et see on andmete toomise hook'i olek
const apiState = { status: 'error', error: { code: 403, message: 'Forbidden' } };
function renderUI(state) {
return match(state)
.with({ status: 'loading' }, () => '
.with({ status: 'success', data: P.select() }, (users) => `${users.map(u => `
`)
.with({ status: 'error', error: { code: 404 } }, () => '
.with({ status: 'error', error: P.select() }, (err) => `
.exhaustive(); // Tagab, et kõik meie olekutüübi juhud on käsitletud
}
// document.body.innerHTML = renderUI(apiState);
See on palju loetavam ja robustsem kui pesastatud if (state.status === 'success') kontrollid.
Olekuhaldus funktsionaalsetes komponentides (nt React)
Olekuhaldusteekides nagu Redux või kasutades Reacti useReducer hook'i, on teil sageli reducer-funktsioon, mis tegeleb erinevate tegevustüüpidega. switch-lause `action.type`'i järgi on levinud, kuid mustrisobitus kogu `action`-objekti peal on parem.
// Enne: TĂĽĂĽpiline reducer switch-lausega
function classicReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_VALUE':
return { ...state, count: action.payload };
default:
return state;
}
}
// Pärast: Reducer, mis kasutab mustrisobitust
function patternMatchingReducer(state, action) {
return match(action)
.with({ type: 'INCREMENT' }, () => ({ ...state, count: state.count + 1 }))
.with({ type: 'DECREMENT' }, () => ({ ...state, count: state.count - 1 }))
.with({ type: 'SET_VALUE', payload: P.select() }, (value) => ({ ...state, count: value }))
.otherwise(() => state);
}
Mustrisobituse versioon on deklaratiivsem. See hoiab ära ka levinud vead, näiteks juurdepääsu `action.payload`'ile, kui seda teatud tegevustüübi puhul ei pruugi eksisteerida. Muster ise tagab, et `payload` peab olemas olema `'SET_VALUE'` juhu jaoks.
Lõplike olekumasinate (FSM) implementeerimine
Lõplik olekumasin on arvutusmudel, mis võib olla ühes piiratud arvu olekutest. Mustrisobitus on ideaalne tööriist nende olekute vaheliste üleminekute defineerimiseks.
// Olekud: { status: 'idle' } | { status: 'loading' } | { status: 'success', data: T } | { status: 'error', error: E }
// SĂĽndmused: { type: 'FETCH' } | { type: 'RESOLVE', data: T } | { type: 'REJECT', error: E }
function stateMachine(currentState, event) {
return match([currentState, event])
.with([{ status: 'idle' }, { type: 'FETCH' }], () => ({ status: 'loading' }))
.with([{ status: 'loading' }, { type: 'RESOLVE', data: P.select() }], (data) => ({ status: 'success', data }))
.with([{ status: 'loading' }, { type: 'REJECT', error: P.select() }], (error) => ({ status: 'error', error }))
.with([{ status: 'error' }, { type: 'FETCH' }], () => ({ status: 'loading' }))
.otherwise(() => currentState); // Kõigi teiste kombinatsioonide puhul jää praegusesse olekusse
}
See lähenemine muudab kehtivad olekuüleminekud selgesõnaliseks ja kergesti mõistetavaks.
Kasu koodi kvaliteedile ja hooldatavusele
Mustrisobituse kasutuselevõtt ei ole ainult nutika koodi kirjutamine; sellel on käegakatsutav kasu kogu tarkvaraarenduse elutsüklile.
- Loetavus ja deklaratiivne stiil: Mustrisobitus sunnib teid kirjeldama, milline teie andmestik välja näeb, mitte imperatiivseid samme selle kontrollimiseks. See muudab teie koodi kavatsuse teistele arendajatele selgemaks, sõltumata nende kultuurilisest või keelelisest taustast.
- Muutumatus ja puhtad funktsioonid: Mustrisobituse avaldisele orienteeritud olemus sobib ideaalselt funktsionaalse programmeerimise põhimõtetega. See julgustab teid võtma andmeid, neid teisendama ja tagastama uue väärtuse, selle asemel et otse olekut muuta. See viib vähemate kõrvalmõjude ja ettearvatavama koodini.
- Ammendavuse kontroll: See on usaldusväärsuse seisukohalt mängumuutev. TypeScripti kasutamisel saavad teegid nagu `ts-pattern` kompileerimise ajal tagada, et olete käsitlenud kõiki unioonitüübi võimalikke variante. Kui lisate uue oleku- või tegevustüübi, annab kompilaator vea seni, kuni lisate oma `match`-avaldisesse vastava käsitleja. See lihtne funktsioon kõrvaldab terve klassi käitusaja vigu.
- Vähendatud tsüklomaatiline keerukus: See tasandab sügavalt pesastatud `if/else` struktuurid üheks lineaarseks ja kergesti loetavaks blokiks. Madalama keerukusega koodi on lihtsam testida, siluda ja hooldada.
Kuidas mustrisobitusega täna alustada
Olete valmis proovima? Siin on lihtne ja teostatav plaan:
- Valige oma tööriist: Soovitame tungivalt
ts-pattern'it selle robustse funktsioonide komplekti ja suurepärase TypeScripti toe tõttu. See on täna JavaScripti ökosüsteemi kuldstandard. - Paigaldamine: Lisage see oma projekti eelistatud paketihalduriga.
npm install ts-pattern
võiyarn add ts-pattern - Refaktoreerige väike koodijupp: Parim viis õppida on tehes. Leidke oma koodibaasist keeruline `switch`-lause või segane `if/else` ahel. See võib olla komponent, mis renderdab erinevat kasutajaliidest vastavalt propsidele, funktsioon, mis parsib API andmeid, või reducer. Proovige seda refaktoreerida.
Märkus jõudluse kohta
Levinud küsimus on, kas mustrisobituse jaoks teegi kasutamine toob kaasa jõudluse languse. Vastus on jah, kuid see on peaaegu alati tühine. Need teegid on kõrgelt optimeeritud ja lisakulu on enamiku veebirakenduste jaoks minimaalne. Tohutu kasu arendajate produktiivsuses, koodi selguses ja vigade ennetamises kaalub üles mikrosekundite tasemel jõudluskulu. Ärge optimeerige enneaegselt; eelistage selge, korrektse ja hooldatava koodi kirjutamist.
Tulevik: sisseehitatud mustrisobitus ECMAScriptis
Nagu mainitud, töötab TC39 komitee mustrisobituse lisamise kallal sisseehitatud funktsioonina. Süntaksi üle veel vaieldakse, kuid see võib välja näha umbes nii:
// Potentsiaalne tuleviku sĂĽntaks!
let httpMessage = match (response) {
when { status: 200, body: b } -> `Õnnestus, sisu: ${b}`,
when { status: 404 } -> `Ei leitud`,
when { status: 5.. } -> `Serveri viga`,
else -> `Muu HTTP vastus`
};
Õppides täna kontseptsioone ja mustreid teekidega nagu ts-pattern, ei paranda te ainult oma praeguseid projekte; te valmistute JavaScripti keele tulevikuks. Vaimsed mudelid, mida te ehitate, kanduvad otse üle, kui need funktsioonid muutuvad sisseehitatuks.
Kokkuvõte: paradigmavahetus JavaScripti tingimuslausetes
Mustrisobitus on palju enamat kui süntaktiline suhkur switch-lause jaoks. See esindab fundamentaalset nihet deklaratiivsema, robustsema ja funktsionaalsema stiili suunas tingimusloogika käsitlemisel JavaScriptis. See julgustab teid mõtlema oma andmete kujule, mis viib koodini, mis ei ole mitte ainult elegantsem, vaid ka vastupidavam vigadele ja aja jooksul lihtsamini hooldatav.
Arendusmeeskondadele üle maailma võib mustrisobituse kasutuselevõtt viia järjepidevama ja väljendusrikkama koodibaasini. See pakub ühist keelt keerukate andmestruktuuride käsitlemiseks, mis ületab meie traditsiooniliste tööriistade lihtsaid kontrolle. Soovitame teil seda oma järgmises projektis uurida. Alustage väikesest, refaktoreerige keeruline funktsioon ja kogege selgust ja jõudu, mida see teie koodile toob.