आजकल, बहुत कम लोग पर्ल में लिखते हैं, लेकिन लैरी वॉल की प्रसिद्ध कहावत "सरल चीजों को आसान और कठिन चीज़ों को संभव रखें" प्रभावी प्रौद्योगिकी के लिए आम तौर पर स्वीकार किए गए सूत्र बन गए हैं। यह न केवल कार्यों की जटिलता, बल्कि दृष्टिकोण के पहलू में भी व्याख्या की जा सकती है: एक आदर्श प्रौद्योगिकी, एक तरफ, मध्यम और छोटे अनुप्रयोगों के तेजी से विकास ("केवल-लेखन" सहित) की अनुमति दें, दूसरी ओर, विचारशील विकास के लिए उपकरण प्रदान करें। जटिल अनुप्रयोग, जहां विश्वसनीयता, स्थिरता और संरचितता सर्वोपरि है। या यहां तक कि, मानव विमान में अनुवाद करना: जोन्स के लिए सुलभ होना, और साथ ही साथ हस्ताक्षरकर्ताओं के अनुरोधों को पूरा करना।
अब लोकप्रिय संपादकों की दोनों ओर से आलोचना की जा सकती है - कम से कम इस तथ्य को लें कि प्रारंभिक कार्यक्षमता लिखने के परिणामस्वरूप कई फ़ाइलों में कई पंक्तियाँ हो सकती हैं - लेकिन हम गहराई तक नहीं गए, क्योंकि इस बारे में पहले ही बहुत कुछ कहा जा चुका है।
"आप एक कमरे में सभी तालिकाओं और दूसरे में कुर्सियाँ रखने वाले हैं"
- जूहा पननन, संपादक के बारे में बेकन.जेएस पुस्तकालय की निर्माता
आज जिस तकनीक पर चर्चा की जाएगी, वह चांदी की गोली नहीं है, बल्कि इन मानदंडों के अनुरूप होने का दावा है।
Mrr एक कार्यात्मक प्रतिक्रियाशील पुस्तकालय है जो "सब कुछ प्रवाह है" के सिद्धांत को मानता है। Mrr में कार्यात्मक-प्रतिक्रियाशील दृष्टिकोण द्वारा प्रदान किए गए मुख्य लाभ कोड की स्पष्टता, साथ ही साथ तुल्यकालिक और अतुल्यकालिक डेटा परिवर्तनों के लिए एकीकृत दृष्टिकोण हैं।
पहली नज़र में, यह एक ऐसी तकनीक की आवाज़ नहीं है जो शुरुआती लोगों के लिए आसानी से सुलभ होगी: एक धारा की अवधारणा को समझना मुश्किल हो सकता है, यह फ्रंट-एंड पर इतना व्यापक नहीं है, मुख्य रूप से इस तरह के गूंगे पुस्तकालयों के साथ आरएक्स के रूप में जुड़ा हुआ है। और सबसे महत्वपूर्ण बात, यह पूरी तरह से स्पष्ट नहीं है कि मूल "एक्शन-रिएक्शन-अपडेट डोम" योजना के आधार पर प्रवाह को कैसे समझाया जाए। लेकिन ... हम प्रवाह के बारे में सारगर्भित बात नहीं करेंगे! आइए अधिक समझने योग्य चीजों के बारे में बात करें: घटनाएं, स्थिति।
नुस्खा के अनुसार खाना बनाना
एफआरपी के विकलों में उतरे बिना, हम विषय क्षेत्र को औपचारिक बनाने के लिए एक सरल योजना का पालन करेंगे:
- डेटा की एक सूची बनाएं जो पृष्ठ की स्थिति का वर्णन करता है और उपयोगकर्ता इंटरफ़ेस में, साथ ही साथ उनके प्रकार भी उपयोग किए जाएंगे।
- पृष्ठ पर उपयोगकर्ता द्वारा घटित या उत्पन्न होने वाली घटनाओं की एक सूची बनाएं, और उनके साथ प्रसारित किए जाने वाले डेटा के प्रकार
- पृष्ठ पर होने वाली प्रक्रियाओं की एक सूची बनाएँ
- उनके बीच अन्योन्याश्रितताओं का निर्धारण करें।
- उपयुक्त ऑपरेटरों का उपयोग करके अन्योन्याश्रितताओं का वर्णन करें।
इसके साथ ही हमें पुस्तकालय के ज्ञान की भी बहुत अंतिम अवस्था में आवश्यकता है।
तो, चलो एक वेब स्टोर का एक सरलीकृत उदाहरण लेते हैं, जिसमें श्रेणी के अनुसार पृष्ठांकन और फ़िल्टरिंग के साथ उत्पादों की एक सूची है, साथ ही एक टोकरी भी है।
इंटरफ़ेस का निर्माण किस आधार पर किया जाएगा:
- माल की सूची (सरणी)
- चयनित श्रेणी (लाइन)
- माल के साथ पृष्ठों की संख्या (संख्या)
- उन उत्पादों की सूची जो टोकरी (सरणी) में हैं
- वर्तमान पृष्ठ (संख्या)
- टोकरी में उत्पादों की संख्या (संख्या)
घटनाएँ ("घटनाओं" से उनका मतलब केवल क्षणिक घटनाएँ हैं। कुछ समय के लिए होने वाली क्रियाएँ - प्रक्रियाएँ - अलग-अलग घटनाओं में विघटित होने की आवश्यकता होती हैं):
- प्रारंभिक पृष्ठ (शून्य)
- श्रेणी चयन (स्ट्रिंग)
- टोकरी में सामान जोड़ना (वस्तु "माल")
- टोकरी से माल निकालना (हटाए जाने वाले सामान की आईडी)
- उत्पाद सूची के अगले पृष्ठ पर जाएं (संख्या - पृष्ठ संख्या)
प्रक्रियाएँ: ये ऐसी क्रियाएँ हैं जो शुरू होती हैं और फिर एक बार या कुछ समय बाद विभिन्न घटनाओं के साथ समाप्त हो सकती हैं। हमारे मामले में, यह सर्वर से उत्पाद डेटा को लोड करना होगा, जो दो घटनाओं को पूरा कर सकता है: एक त्रुटि के साथ सफल समापन और पूर्णता।
घटनाओं और डेटा के बीच निर्भरता। उदाहरण के लिए, उत्पादों की सूची घटना पर निर्भर करेगी: "उत्पादों की सूची का सफल लोडिंग।" और "माल की सूची लोड करना शुरू करें" - "एक पृष्ठ खोलने" से, "वर्तमान पृष्ठ का चयन करने से", "एक श्रेणी का चयन करने से"। प्रपत्र [तत्व] की सूची बनाएं: [... निर्भरता]:
{ requestGoods: ['page', 'category', 'pageLoaded'], goods: ['requestGoods.success'], page: ['goToPage', 'totalPages'], totalPages: ['requestGoods.success'], cart: ['addToCart', 'removeFromCart'], goodsInCart: ['cart'], category: ['selectCategory'] }
ओह ... लेकिन यह लगभग श्री के लिए कोड है!

यह केवल उन कार्यों को जोड़ने के लिए बनी हुई है जो रिश्ते का वर्णन करेंगे। आपके पास mrr में विभिन्न ईवेंट होने के लिए अपेक्षित ईवेंट, डेटा, प्रक्रियाएँ हो सकती हैं - लेकिन नहीं, यह सब धागे हैं! हमारा काम उन्हें सही ढंग से जोड़ना है।
जैसा कि आप देख सकते हैं, हमारे पास दो प्रकार की निर्भरताएं हैं: "इवेंट" से "डेटा" (उदाहरण के लिए, goToPage से पेज) और "डेटा" से "डेटा" (कार्ट से सामान)। उनमें से प्रत्येक के लिए उपयुक्त दृष्टिकोण हैं।
सबसे आसान तरीका "डेटा से डेटा" है: यहां हम केवल एक शुद्ध फ़ंक्शन, "सूत्र" जोड़ते हैं:
goodsInCart: [arr => arr.length, 'cart'],
हर बार जब गाड़ी की सरणी बदली जाती है, तो माल का मूल्य फिर से शुरू हो जाएगा।
यदि हमारा डेटा एक घटना पर निर्भर करता है, तो सब कुछ काफी सरल है:
category: 'selectCategory', goods: [resp => resp.data, 'requestGoods.success'], totalPages: [resp => resp.totalPages, 'requestGoods.success'],
प्रपत्र का डिजाइन [कार्य, ... धागा-तर्क] mrr का आधार है। सहज ज्ञान युक्त समझ के लिए, एक्सेल के साथ एक सादृश्य आरेखण, mrr में प्रवाह को भी सेल कहा जाता है, और जिन कार्यों से उनकी गणना की जाती है, उन्हें सूत्र कहा जाता है।
यदि हमारा डेटा कई घटनाओं पर निर्भर करता है, तो हमें उनके मूल्यों को व्यक्तिगत रूप से बदलना होगा, और फिर मर्ज ऑपरेटर का उपयोग करके उन्हें एक स्ट्रीम में संयोजित करना होगा:
page: ['merge', [a => a, 'goToPage'], [(a, prev) => a < prev ? a : prev, 'totalPages', '-page'] ], cart: ['merge', [(item, arr) => [...arr, item], 'addToCart', '-cart'], [(id, arr) => arr.filter(item => item.id !== id), 'removeFromCart', '-cart'], ],
दोनों मामलों में, हम सेल के पिछले मूल्य को संदर्भित करते हैं। एक अनंत लूप को रोकने के लिए, हम कार्ट और पृष्ठ कोशिकाओं को निष्क्रिय रूप से संदर्भित करते हैं (सेल नाम के सामने माइनस साइन): उनके मूल्यों को सूत्र में प्रतिस्थापित किया जाएगा, लेकिन यदि वे बदलते हैं, तो पुनर्गणना शुरू नहीं होगी।
सभी थ्रेड्स या तो अन्य थ्रेड्स के आधार पर बनाए जाते हैं या DOM से उत्सर्जित होते हैं। लेकिन "शुरुआती पृष्ठ" स्ट्रीम के बारे में क्या? सौभाग्य से, आपको कंपोनेंट का उपयोग नहीं करना होगा। इसके अलावा: एमआरआर में एक विशेष स्ट्रीम $ स्टार्ट है, जो संकेत देता है कि कंपोनेंट बनाया और माउंट किया गया है।
"प्रक्रिया" की गणना अतुल्यकालिक रूप से की जाती है, जबकि हम उनसे कुछ घटनाओं का उत्सर्जन करते हैं, "नेस्टेड" ऑपरेटर हमें यहां मदद करेगा:
requestGoods: ['nested', (cb, page, category) => { fetch("...") .then(res => cb('success', res)) .catch(e => cb('error', e)); }, 'page', 'category', '$start'],
नेस्टेड ऑपरेटर का उपयोग करते समय, कुछ घटनाओं के उत्सर्जन के लिए पहला तर्क हमें कॉलबैक फ़ंक्शन में दिया जाएगा। इस मामले में, बाहर से वे रूट सेल के नाम स्थान के माध्यम से सुलभ होंगे, उदाहरण के लिए,
cb('success', res)
RequestGoods सूत्र के अंदर, requestGoods.success सेल का अद्यतन परिणाम देगा।
हमारे डेटा की गणना करने से पहले पृष्ठ को सही ढंग से प्रस्तुत करने के लिए, आप उनके प्रारंभिक मान निर्दिष्ट कर सकते हैं:
{ goods: [], page: 1, cart: [], },
मार्कअप जोड़ें। हम withMrr फ़ंक्शन का उपयोग करके एक रिएक्ट घटक बनाते हैं, जो एक प्रतिक्रियाशील लिंक आरेख और रेंडर फ़ंक्शन को स्वीकार करता है। एक मान को एक स्ट्रीम में "डालने" के लिए, हम $ फ़ंक्शन का उपयोग करते हैं, जो इवेंट हैंडलर बनाता है (और कैश करता है)। अब हमारा पूरी तरह से काम करने वाला आवेदन इस तरह दिखता है:
import { withMrr } from 'mrr'; const App = withMrr({ $init: { goods: [], cart: [], page: 1, }, requestGoods: ['nested', (cb, page = 1, category = 'all') => { fetch('https://reqres.in/api/products?page=', page, category).then(r => r.json()) .then(res => cb('success', res)) .catch(e => cb('error', e)) }, 'page', 'selectCategory', '$start'], goods: [res => res.data, 'requestGoods.success'], page: ['merge', 'goToPage', [(a, prev) => a < prev ? a : prev, 'totalPages', '-page']], totalPages: [res => res.total_pages, 'requestGoods.success'], category: 'selectCategory', cart: ['merge', [(item, arr) => [...arr, item], 'addToCart', '-cart'], [(id, arr) => arr.filter(item => item.id !== id), 'removeFromCart', '-cart'], ], }, (state, props, $) => { return (<section> <h2>Shop</h2> <div> Category: <select onChange={$('selectCategory')}> <option>All</option> <option>Electronics</option> <option>Photo</option> <option>Cars</option> </select> </div> <ul className="goods"> { state.goods.map((item, i) => { const cartI = state.cart.findIndex(a => a.id === item.id); return (<li key={i}> { item.name } <div> { cartI === -1 && <button onClick={$("addToCart", item)}>Add to cart</button> } { cartI !== -1 && <button onClick={$("removeFromCart", item.id)}>Remove from cart</button> } </div> </li>); }) } </ul> <ul className="pages"> { new Array(state.totalPages).fill(true).map((_, p) => { const page = Number(p) + 1; return ( <li className="page" onClick={$('goToPage', page)} key={p}> { page } </li> ); }) } </ul> </section> <section> <h2>Cart</h2> <ul> { state.cart.map((item, i) => { return (<li key={i}> { item.name } <div> <button onClick={$("removeFromCart", item.id)}>Remove from cart</button> </div> </li>); }) } </ul> </section>); }); export default App;
डिज़ाइन
<select onChange={$('selectCategory')}>
इसका मतलब यह है कि जब क्षेत्र को बदल दिया जाता है, तो मान को "चयनित" श्रेणी में धकेल दिया जाएगा। लेकिन क्या अर्थ है? डिफ़ॉल्ट रूप से, यह event.target.value है, लेकिन अगर हमें कुछ और पुश करने की आवश्यकता है, तो हम इसे दूसरे तर्क के साथ निर्दिष्ट करते हैं, जैसे कि:
<button onClick={$("addToCart", item)}>
यहाँ सब कुछ - घटनाएँ, डेटा और प्रक्रियाएँ - ये प्रवाह हैं। किसी घटना के ट्रिगर होने पर उसके आधार पर डेटा या घटनाओं का पुनरावर्तन होता है, और इसी तरह श्रृंखला पर भी। आश्रित धारा के मूल्य की गणना एक सूत्र का उपयोग करके की जाती है जो एक मूल्य, या एक वादा (फिर mrr अपने संकल्प की प्रतीक्षा करेगा) का उपयोग कर सकता है।
Mrr एपीआई बहुत संक्षिप्त और संक्षिप्त है - ज्यादातर मामलों के लिए, हमें केवल 3-4 बुनियादी ऑपरेटरों की आवश्यकता होती है, और कई चीजें उनके बिना की जा सकती हैं। उत्पादों की सूची सफलतापूर्वक लोड नहीं होने पर एक त्रुटि संदेश जोड़ें, जिसे एक सेकंड के लिए प्रदर्शित किया जाएगा:
hideErrorMessage: [() => new Promise(res => setTimeout(res, 1000)), 'requestGoods.error'], errorMessageShown: [ 'merge', [() => true, 'requestGoods.error'], [() => false, 'hideErrorMessage'], ],
नमक, काली मिर्च स्वाद के लिए चीनी
Mrr में सिंटैक्टिक शुगर भी है, जो विकास के लिए वैकल्पिक है, लेकिन इसे गति प्रदान कर सकता है। उदाहरण के लिए, टॉगल ऑपरेटर:
errorMessageShown: ['toggle', 'requestGoods.error', [() => new Promise(res => setTimeout(res, 1000)), 'showErrorMessage']],
पहले तर्क में एक परिवर्तन सेल को सही पर सेट करेगा, और दूसरे को असत्य में।
सफलता और त्रुटि सबस्कूल में एक अतुल्यकालिक कार्य के परिणामों को "विघटित करने" के साथ दृष्टिकोण भी इतना व्यापक है कि आप विशेष वादा ऑपरेटर का उपयोग कर सकते हैं (जो स्वचालित रूप से दौड़ की स्थिति को समाप्त कर देता है):
requestGoods: [ 'promise', (page = 1, category = 'all') => fetch('https://reqres.in/api/products?page=', page, category).then(r => r.json()), 'page', 'selectCategory', '$start' ],
बहुत सारी कार्यक्षमता बस दो दर्जन लाइनों में फिट होती है। हमारा सशर्त जून संतुष्ट है - वह वर्किंग कोड लिखने में कामयाब रहा, जो काफी कॉम्पैक्ट निकला: सभी तर्क एक फाइल में और एक स्क्रीन पर फिट होते हैं। लेकिन हस्ताक्षर करने वाले ने अविश्वसनीय रूप से संकेत दिया: एका अनदेखी है ... आप इसे हुक / पुन: प्रस्ताव / आदि पर लिख सकते हैं।
हाँ, वास्तव में यह है! बेशक, कोड और भी अधिक कॉम्पैक्ट और संरचित होने की संभावना नहीं है, लेकिन यह बात नहीं है। आइए कल्पना करें कि परियोजना विकसित हो रही है, और हमें कार्यक्षमता को दो अलग-अलग पृष्ठों में विभाजित करने की आवश्यकता है: उत्पादों की एक सूची और एक टोकरी। इसके अलावा, टोकरी डेटा, जाहिर है, दोनों पृष्ठों के लिए विश्व स्तर पर संग्रहीत करने की आवश्यकता है।
एक दृष्टिकोण, एक इंटरफ़ेस
यहां हम प्रतिक्रिया विकास की एक और समस्या पर आते हैं: स्थानीय रूप से राज्य के प्रबंधन के लिए विषम दृष्टिकोण का अस्तित्व (एक घटक के भीतर) और वैश्विक रूप से पूरे आवेदन के स्तर पर। कई, मुझे यकीन है, एक दुविधा का सामना करना पड़ा: स्थानीय स्तर पर या विश्व स्तर पर कुछ तर्क को लागू करने के लिए? या एक और स्थिति: यह पता चला कि स्थानीय डेटा के कुछ टुकड़े को विश्व स्तर पर सहेजने की आवश्यकता है, और आपको कार्यक्षमता के हिस्से को फिर से लिखना होगा, पुन: प्रस्ताव से संपादक तक ...
इसके विपरीत, बेशक, कृत्रिम है, और श्री में यह नहीं है: यह समान रूप से अच्छा है, और सबसे महत्वपूर्ण बात है - समान! - स्थानीय और वैश्विक राज्य प्रबंधन दोनों के लिए उपयुक्त। सामान्य तौर पर, हमें किसी भी वैश्विक स्थिति की आवश्यकता नहीं है, हमारे पास बस घटकों के बीच डेटा का आदान-प्रदान करने की क्षमता है, इसलिए मूल घटक की स्थिति "वैश्विक" होगी।
हमारे आवेदन की योजना अब इस प्रकार है: टोकरी में सामानों की सूची वाला रूट घटक, और दो उप-घटक: सामान और टोकरी, और वैश्विक घटक "पुर्जे में जोड़ें" को "सुनकर टोकरी में जोड़ें" और बाल घटकों के साथ "टोकरी से हटा दें"।
const App = withMrr({ $init: { cart: [], currentPage: 'goods', }, cart: ['merge', [(item, arr) => [...arr, item], 'addToCart', '-cart'], [(id, arr) => arr.filter(item => item.id !== id), 'removeFromCart', '-cart'], ], }, (state, props, $, connectAs) => { return ( <div> <menu> <li onClick={$('currentPage', 'goods')}>Goods</li> <li onClick={$('currentPage', 'cart')}>Cart{ state.cart && state.cart.length ? '(' + state.cart.length + ')' : '' }</li> </menu> <div> { state.currentPage === 'goods' && <Goods {...connectAs('goods', ['addToCart', 'removeFromCart'], ['cart'])}/> } { state.currentPage === 'cart' && <Cart {...connectAs('cart', { 'removeFromCart': 'remove' }, ['cart'])}/> } </div> </div> ); })
const Goods = withMrr({ $init: { goods: [], page: 1, }, goods: [res => res.data, 'requestGoods.success'], requestGoods: [ 'promise', (page = 1, category = 'all') => fetch('https://reqres.in/api/products?page=', page, category).then(r => r.json()), 'page', 'selectCategory', '$start' ], page: ['merge', 'goToPage', [(a, prev) => a < prev ? a : prev, 'totalPages', '-page']], totalPages: [res => res.total, 'requestGoods.success'], category: 'selectCategory', errorShown: ['toggle', 'requestGoods.error', [cb => new Promise(res => setTimeout(res, 1000)), 'requestGoods.error']], }, (state, props, $) => { return (<div> ... </div>); });
const Cart = withMrr({}, (state, props, $) => { return (<div> <h2>Cart</h2> <ul> { state.cart.map((item, i) => { return (<div> { item.name } <div> <button onClick={$('remove', item.id)}>Remove from cart</button> </div> </div>); }) } </ul> </div>); });
यह आश्चर्यजनक है कि कैसे थोड़ा बदल गया है! हमने केवल प्रवाह को संबंधित घटकों में रखा और उनके बीच "पुल" बिछाया! MrrConnect फ़ंक्शन का उपयोग करके घटकों को कनेक्ट करके, हम डाउनस्ट्रीम और अपस्ट्रीम प्रवाह के लिए मैपिंग निर्दिष्ट करते हैं:
connectAs( 'goods', ['addToCart', 'removeFromCart'], ['cart'] )
यहां, चाइल्ड कंपोनेंट से AddToCart और removeFromCart स्ट्रीम अभिभावक के पास जाएंगे, और कार्ट स्ट्रीम वापस आ जाएगी। हमें समान स्ट्रीम नामों का उपयोग करने की आवश्यकता नहीं है - यदि वे मेल नहीं खाते हैं, तो हम मैपिंग का उपयोग करते हैं:
connectAs('cart', { 'removeFromCart': 'remove' })
चाइल्ड कंपोनेंट से रिमूव स्ट्रीम पैरेंट में रिमूवल एफर्ट के लिए सोर्स होगा।
जैसा कि आप देख सकते हैं, mrr के मामले में डेटा स्टोरेज लोकेशन चुनने की समस्या पूरी तरह से दूर हो गई है: आप डेटा को स्टोर करते हैं जहाँ यह लॉग इन निर्धारित होता है।
यहाँ फिर से, कोई भी संपादक की खामी को नोट करने में विफल हो सकता है: इसमें आपको सभी डेटा को एक ही केंद्रीय भंडार में सहेजना होगा। यहां तक कि डेटा जो केवल एक अलग घटक या उसके उपप्रकार द्वारा अनुरोध और उपयोग किया जा सकता है! यदि हमने "संपादकीय शैली" में लिखा है, तो हम सामानों की लोडिंग और पेजिंग को भी वैश्विक स्तर पर ले जाएंगे (निष्पक्षता में - यह दृष्टिकोण, mrr के लचीलेपन के लिए धन्यवाद, यह भी संभव है और जीवन का अधिकार है, स्रोत कोड )।
हालाँकि, यह आवश्यक नहीं है। लोड किए गए सामान का उपयोग केवल सामान के घटक में किया जाता है, इसलिए, उन्हें वैश्विक स्तर पर ले जाना, हम केवल वैश्विक राज्य को रोकते और फुलाते हैं। इसके अलावा, हमें पुराने डेटा (उदाहरण के लिए, पृष्ठांकन पृष्ठ) को साफ़ करना होगा, जब उपयोगकर्ता फिर से उत्पाद पृष्ठ पर लौटता है। डेटा भंडारण का सही स्तर चुनना, हम स्वचालित रूप से ऐसी समस्याओं से बचते हैं।
इस दृष्टिकोण का एक और लाभ यह है कि एप्लिकेशन लॉजिक को प्रेजेंटेशन के साथ जोड़ दिया जाता है, जो हमें व्यक्तिगत प्रतिक्रिया घटकों को "गूंगा" टेम्प्लेट के बजाय पूरी तरह कार्यात्मक विजेट के रूप में पुन: उपयोग करने की अनुमति देता है। इसके अलावा, वैश्विक स्तर पर न्यूनतम जानकारी रखना (आदर्श रूप से, यह केवल सत्र डेटा है) और अधिकांश तर्क को अलग-अलग पृष्ठ घटकों में निकालते हुए, हम कोड की सुसंगतता को कम करते हैं। बेशक, यह दृष्टिकोण सार्वभौमिक रूप से लागू नहीं है, लेकिन बड़ी संख्या में ऐसे कार्य हैं जहां वैश्विक स्थिति बहुत छोटी है और व्यक्तिगत रूप से "स्क्रीन" एक दूसरे से लगभग पूरी तरह से स्वतंत्र हैं: उदाहरण के लिए, विभिन्न प्रकार के प्रवेश, आदि। संपादक के विपरीत, जो हमें वह सब कुछ लेने के लिए उकसाता है जो वैश्विक स्तर पर आवश्यक और आवश्यक नहीं है, mrr आपको अलग-अलग उपप्रकारों में डेटा संग्रहीत करने, प्रोत्साहित करने और एन्कैप्सुलेशन संभव बनाने की अनुमति देता है, इस प्रकार हमारे आवेदन को एक अखंड "पाई" से "स्तरित" केक "में बदल देता है।
यह ध्यान देने योग्य है: निश्चित रूप से, प्रस्तावित दृष्टिकोण में क्रांतिकारी नया कुछ भी नहीं है! घटक, स्व-निहित विगेट्स, जेएस फ्रेमवर्क के आगमन के बाद से उपयोग किए जाने वाले बुनियादी तरीकों में से एक हैं। एकमात्र महत्वपूर्ण अंतर यह है कि mrr घोषणात्मक सिद्धांत का पालन करता है: घटक केवल अन्य घटकों के प्रवाह को सुन सकते हैं, लेकिन उन्हें प्रभावित नहीं कर सकते हैं (नीचे-ऊपर की दिशा, या ऊपर-नीचे की दिशा से क्या करना है, जो प्रवाह से भिन्न होता है) -Suitable)। स्मार्ट घटक जो केवल अंतर्निहित और मूल घटकों के साथ संदेशों का आदान-प्रदान कर सकते हैं, फ्रंट-एंड डेवलपमेंट में लोकप्रिय लेकिन अल्पज्ञात अभिनेता मॉडल के अनुरूप हैं (फ्रंट-एंड पर अभिनेताओं और थ्रेड्स का उपयोग करने का विषय अच्छी तरह से लेख में शामिल है) प्रतिक्रियात्मक प्रोग्रामिंग का परिचय ।
बेशक, यह अभिनेताओं के कैनोनिकल कार्यान्वयन से बहुत दूर है, लेकिन सार बिल्कुल यही है: एमपीपी धाराओं के माध्यम से संदेशों का आदान-प्रदान करने वाले घटकों द्वारा अभिनेताओं की भूमिका निभाई जाती है; एक घटक (घोषित रूप से!) वर्चुअल डोम और रिएक्ट के लिए चाइल्ड कंपोनेंट एक्टर्स का निर्माण और हटा सकता है: रेंडर फ़ंक्शन, संक्षेप में, चाइल्ड एक्टर्स की संरचना को निर्धारित करता है।
प्रतिक्रिया के लिए मानक स्थिति के बजाय, जब हम मूल घटक को प्रॉप्स के माध्यम से एक निश्चित कॉलबैक में "ड्रॉप" करते हैं, तो हमें माता-पिता से बच्चे के घटक के प्रवाह को सुनना चाहिए। यही बात विपरीत दिशा में है, माता-पिता से बच्चे तक। उदाहरण के लिए, आप पूछ सकते हैं: कार्ट टोकरी डेटा को स्ट्रीम के रूप में कार्ट कंपोनेंट में स्थानांतरित करें, यदि हम आगे की हलचल के बिना, बस प्रॉपर के रूप में उन्हें पास कर सकते हैं? अंतर क्या है? वास्तव में, इस दृष्टिकोण का उपयोग भी किया जा सकता है, लेकिन केवल तब तक जब तक कि प्रॉप्स में परिवर्तन का जवाब देने की आवश्यकता न हो। यदि आपने कभी घटकWillReceiveProps विधि का उपयोग किया है, तो आप जानते हैं कि यह क्या है। यह "गरीबों के लिए प्रतिक्रियाशीलता" का एक प्रकार है: आप बिल्कुल सभी बदलावों को सुनते हैं, यह निर्धारित करते हैं कि क्या बदल गया है, और प्रतिक्रिया करें। लेकिन यह विधि जल्द ही रिएक्ट से गायब हो जाएगी, और "ऊपर से संकेत" की प्रतिक्रिया की आवश्यकता उत्पन्न हो सकती है।
Mrr में, प्रवाह "प्रवाह" न केवल ऊपर है, बल्कि घटक पदानुक्रम भी नीचे है, ताकि घटक स्वतंत्र रूप से राज्य परिवर्तनों का जवाब दे सकें। ऐसा करने में, आप mrr प्रतिक्रियाशील उपकरणों की पूरी शक्ति का उपयोग कर सकते हैं।
const Cart = withMrr({ foo: [items => {
थोड़ी नौकरशाही जोड़ें
परियोजना बढ़ रही है, प्रवाह के नामों पर नज़र रखना मुश्किल हो जाता है, जो हैं - ओह, हॉरर! - पंक्तियों में संग्रहीत हैं। खैर, हम स्ट्रीम नामों के लिए स्थिरांक का उपयोग कर सकते हैं, साथ ही mrr कथनों के लिए भी। अब एक छोटा टाइपो बनाने से एप्लिकेशन को तोड़ना कठिन हो जाता है।
import { withMrr } from 'mrr'; import { merge, toggle, promise } from 'mrr/operators'; import { cell, nested, $start$, passive } from 'mrr/cell'; const goods$ = cell('goods'); const page$ = cell('page'); const totalPages$ = cell('totalPages'); const category$ = cell('category'); const errorShown$ = cell('errorShown'); const addToCart$ = cell('addToCart'); const removeFromCart$ = cell('removeFromCart'); const selectCategory$ = cell('selectCategory'); const goToPage$ = cell('goToPage'); const Goods = withMrr({ $init: { [goods$]: [], [page$]: 1, }, [goods$]: [res => res.data, requestGoods$.success], [requestGoods$]: promise((page, category) => fetch('https://reqres.in/api/products?page=', page).then(r => r.json()), page$, category$, $start$), [page$]: merge(goToPage$, [(a, prev) => a < prev ? a : prev, totalPages$, passive(page$)]), [totalPages$]: [res => res.total, requestGoods$.success], [category$]: selectCategory$, [errorShown$]: toggle(requestGoods$.error, [cb => new Promise(res => setTimeout(res, 1000)), requestGoods$.error]), }, ...);
ब्लैक बॉक्स में क्या है?
परीक्षण के बारे में क्या? Mrr घटक में वर्णित तर्क को टेम्पलेट से अलग करना आसान है, और फिर परीक्षण करें।
चलिए हमारी फाइल से अलग से mrr स्ट्रक्चर बनाते हैं।
const GoodsStruct = { $init: { [goods$]: [], [page$]: 1, }, ... } const Goods = withMrr(GoodsStruct, (state, props, $) => { ... }); export { GoodsStruct }
और फिर हम इसे अपने परीक्षणों में आयात करते हैं। एक साधारण आवरण के साथ हम कर सकते हैं
मान को स्ट्रीम में रखें (जैसे कि यह DOM से किया गया था), और फिर उस पर निर्भर अन्य थ्रेड्स के मूल्यों की जाँच करें।
import { simpleWrapper} from 'mrr'; import { GoodsStruct } from '../src/components/Goods'; describe('Testing Goods component', () => { it('should update page if it\'s out of limit ', () => { const a = simpleWrapper(GoodsStruct); a.set('page', 10); assert.equal(a.get('page'), 10); a.set('requestGoods.success', {data: [], total: 5}); assert.equal(a.get('page'), 5); a.set('requestGoods.success', {data: [], total: 10}); assert.equal(a.get('page'), 5); }) })
प्रतिक्रिया की चमक और गरीबी
यह ध्यान देने योग्य है कि संपादक में घटनाओं के आधार पर राज्य के "मैनुअल" गठन की तुलना में प्रतिक्रियाशीलता उच्च स्तर की एक अमूर्तता है। विकास की सुविधा, एक ओर, यह अपने आप को पैर में गोली मारने के अवसर पैदा करता है। इस परिदृश्य पर विचार करें: उपयोगकर्ता पृष्ठ संख्या 5 पर जाता है, फिर फ़िल्टर "श्रेणी" स्विच करता है। हमें पांचवें पृष्ठ पर चयनित श्रेणी के उत्पादों की सूची लोड करनी चाहिए, लेकिन यह पता चल सकता है कि इस श्रेणी के सामान में केवल तीन पृष्ठ होंगे। "बेवकूफ" बैकएंड के मामले में, हमारे कार्यों की एल्गोरिथ्म निम्नानुसार है:
- अनुरोध डेटा पृष्ठ = 5 और श्रेणी =% श्रेणी%
- उत्तर से पृष्ठों की संख्या का मान लें
- यदि रिकॉर्ड की एक शून्य संख्या लौटा दी जाती है, तो सबसे बड़े उपलब्ध पृष्ठ का अनुरोध करें
यदि हम संपादक पर इसे लागू करते हैं, तो हमें वर्णित तर्क के साथ एक बड़ी अतुल्यकालिक कार्रवाई बनानी होगी। Mrr पर प्रतिक्रियाशीलता के मामले में, इस परिदृश्य का अलग से वर्णन करने की आवश्यकता नहीं है। सब कुछ पहले से ही इन पंक्तियों में निहित है:
[requestGoods$]: ['nested', (cb, page, category) => { fetch('https://reqres.in/api/products?page=', page).then(r => r.json()) .then(res => cb('success', res)) .catch(e => cb('error', e)) }, page$, category$, $start$], [totalPages$]: [res => res.total, requestGoods$.success], [page$]: merge(goToPage$, [(a, prev) => a < prev ? a : prev, totalPages$, passive(page$)]),
यदि नया TotalPages मान वर्तमान पृष्ठ से कम है, तो हम पृष्ठ मूल्य को अपडेट करेंगे और इस तरह सर्वर के लिए दूसरा अनुरोध शुरू करेंगे।
लेकिन अगर हमारा फ़ंक्शन समान मान लौटाता है, तो इसे अभी भी पृष्ठ स्ट्रीम में परिवर्तन के रूप में माना जाएगा, इसके बाद सभी निर्भर धाराओं की पुनरावृत्ति होगी। इससे बचने के लिए, श्री का एक विशेष अर्थ है - छोड़ें। इसे वापस करते हुए, हम संकेत देते हैं: कोई परिवर्तन नहीं हुआ है, कुछ भी अद्यतन करने की आवश्यकता नहीं है।
import { withMrr, skip } from 'mrr'; [requestGoods$]: nested((cb, page, category) => { fetch('https://reqres.in/api/products?page=', page).then(r => r.json()) .then(res => cb('success', res)) .catch(e => cb('error', e)) }, page$, category$, $start$), [totalPages$]: [res => res.total, requestGoods$.success], [page$]: merge(goToPage$, [(a, prev) => a < prev ? a : skip, totalPages$, passive(page$)]),
इस प्रकार, एक छोटी सी गलती हमें एक अनंत लूप में ले जा सकती है: यदि हम "स्किप" नहीं बल्कि "प्रचलित" लौटते हैं, तो पेज सेल बदल जाएगा और दूसरा अनुरोध होगा, और इसी तरह एक सर्कल में। इस तरह की स्थिति की बहुत संभावना, निश्चित रूप से, FRP या mrr का "दोषपूर्ण दोष" नहीं है, क्योंकि अनंत पुनरावृत्ति या लूप की संभावना संरचनात्मक प्रोग्रामिंग के त्रुटिपूर्ण विचारों को इंगित नहीं करती है। हालांकि, यह समझा जाना चाहिए कि mrr को अभी भी प्रतिक्रियाशीलता के तंत्र की कुछ समझ की आवश्यकता है। चाकू के प्रसिद्ध रूपक पर लौटना, श्री एक बहुत तेज चाकू है जो कार्य कुशलता में सुधार करता है, लेकिन एक अयोग्य कार्यकर्ता को भी घायल कर सकता है।
वैसे, किसी भी एक्सटेंशन को इंस्टॉल किए बिना mrr पर डिबेट करना बहुत आसान है:
const GoodsStruct = { $init: { ... }, $log: true, ... }
बस $ लॉग जोड़ें: mrr संरचना के लिए सही है, और कक्षों में सभी परिवर्तन कंसोल के लिए आउटपुट होंगे, इसलिए आप देख सकते हैं कि क्या परिवर्तन और कैसे।
निष्क्रिय सुनने या छोड़ने के अर्थ जैसी अवधारणाएं विशिष्ट "बैसाखी" नहीं हैं: वे प्रतिक्रियाशीलता की संभावनाओं का विस्तार करते हैं ताकि यह अनिवार्य दृष्टिकोण का सहारा लिए बिना आसानी से आवेदन के पूरे तर्क का वर्णन कर सके। इसी तरह के तंत्र, उदाहरण के लिए, Rx.js में हैं, लेकिन वहां उनका इंटरफ़ेस कम सुविधाजनक है। : Mrr: FRP
.
परिणाम
- FRP, mrr ,
- : ,
- , ,
- , , - ( - !)
- mrr : " , !"
- ,
- , , ( ). !
- : , , , TMTOWTDI: , - .
पुनश्च
. , mrr , , :
import useMrr from 'mrr/hooks'; function Foo(props){ const [state, $, connectAs] = useMrr(props, { $init: { counter: 0, }, counter: ['merge', [a => a + 1, '-counter', 'incr'], [a => a - 1, '-counter', 'decr'] ], }); return ( <div> Counter: { state.counter } <button onClick={ $('incr') }>increment</button> <button onClick={ $('decr') }>decrement</button> <Bar {...connectAs('bar')} /> </div> ); }