React: RFClarification: `सेटस्टेट` अतुल्यकालिक क्यों है?

को निर्मित 11 नव॰ 2017  ·  31टिप्पणियाँ  ·  स्रोत: facebook/react

काफी समय से मैंने यह समझने की कोशिश की है कि setState अतुल्यकालिक क्यों है। और अतीत में इसका उत्तर खोजने में असफल होने पर, मैं इस निष्कर्ष पर पहुंचा कि यह ऐतिहासिक कारणों से था और शायद अब इसे बदलना मुश्किल है। हालाँकि @gaearon ने संकेत दिया कि एक स्पष्ट कारण है, इसलिए मैं यह जानने के लिए उत्सुक हूँ :)

वैसे भी, ये कारण हैं जो मैं अक्सर सुनता हूं, लेकिन मुझे लगता है कि वे सब कुछ नहीं हो सकते क्योंकि उनका मुकाबला करना बहुत आसान है

Async प्रतिपादन के लिए Async सेटस्टेट आवश्यक है

कई लोग शुरू में सोचते हैं कि यह रेंडर दक्षता के कारण है। लेकिन मुझे नहीं लगता कि इस व्यवहार के पीछे यही कारण है, क्योंकि सेटस्टेट सिंक को एसिंक्स रेंडरिंग के साथ रखना मेरे लिए तुच्छ लगता है, कुछ इस तरह से:

Component.prototype.setState = (nextState) => {
  this.state = nextState
  if (!this.renderScheduled)
     setImmediate(this.forceUpdate)
}

वास्तव में, उदाहरण के लिए mobx-react वेधशालाओं को सिंक्रोनस असाइनमेंट की अनुमति देता है और फिर भी प्रतिपादन की एसिंक प्रकृति का सम्मान करता है

Async सेटस्टेट को यह जानने की जरूरत है कि कौन सा राज्य _rendered_ था

दूसरा तर्क जो मैं कभी-कभी सुनता हूं वह यह है कि आप उस राज्य के बारे में तर्क करना चाहते हैं जो _rendered_ था, न कि वह राज्य जो _requested_ था। लेकिन मुझे यकीन नहीं है कि इस सिद्धांत में बहुत योग्यता है। संकल्पनात्मक रूप से यह मुझे अजीब लगता है। प्रतिपादन एक साइड इफेक्ट है, राज्य तथ्यों के बारे में है। आज, मैं 32 वर्ष का हूं, और अगले वर्ष मैं 33 वर्ष का हो जाऊंगा, भले ही मालिक घटक इस वर्ष फिर से प्रस्तुत करने का प्रबंधन करता हो या नहीं :)।

एक (शायद अच्छा नहीं) समानांतर आकर्षित करने के लिए: यदि आप एक स्व-लिखित शब्द दस्तावेज़ के अपने अंतिम संस्करण को तब तक _read_ करने में सक्षम नहीं होंगे, जब तक कि आप इसे मुद्रित नहीं करते, यह बहुत अजीब होगा। मुझे नहीं लगता कि उदाहरण के लिए गेम इंजन आपको फीडबैक देते हैं कि गेम की किस स्थिति को बिल्कुल प्रस्तुत किया गया था और कौन से फ्रेम को गिरा दिया गया था।

एक दिलचस्प अवलोकन: 2 वर्षों में mobx-react किसी ने भी मुझसे यह सवाल नहीं पूछा: मुझे कैसे पता चलेगा कि मेरे अवलोकन योग्य हैं? यह प्रश्न बहुत बार प्रासंगिक नहीं लगता है।

मुझे कुछ मामलों का सामना करना पड़ा जहां यह जानना प्रासंगिक था कि कौन सा डेटा प्रदान किया गया था। मुझे जो मामला याद है वह वह था जहां मुझे लेआउट उद्देश्यों के लिए कुछ डेटा के पिक्सेल आयामों को जानने की आवश्यकता थी। लेकिन यह didComponentUpdate का उपयोग करके सुरुचिपूर्ण ढंग से हल किया गया था और वास्तव में setState एसिंक्स होने पर भी भरोसा नहीं करता था। ये मामले इतने दुर्लभ प्रतीत होते हैं कि मुख्य रूप से उनके आसपास एपीआई को डिजाइन करना शायद ही उचित हो। अगर यह किसी तरह किया जा सकता है, तो मुझे लगता है कि यह पर्याप्त है


मुझे इसमें कोई संदेह नहीं है कि रिएक्ट टीम setState की एसिंक प्रकृति के भ्रम से अवगत है, इसलिए मुझे संदेह है कि वर्तमान शब्दार्थ के लिए एक और बहुत अच्छा कारण है। मुझे और बताएँ :)

Discussion

सबसे उपयोगी टिप्पणी

तो यहाँ कुछ विचार हैं। यह किसी भी तरह से पूर्ण प्रतिक्रिया नहीं है, लेकिन हो सकता है कि यह अभी भी कुछ न कहने से ज्यादा मददगार हो।

सबसे पहले, मुझे लगता है कि हम सहमत हैं कि बैच अपडेट के लिए सुलह में देरी setState() समकालिक रूप से पुन: प्रतिपादन कई मामलों में अक्षम होगा, और यदि हम जानते हैं कि हमें कई अपडेट मिलेंगे तो बैच अपडेट करना बेहतर होगा।

उदाहरण के लिए, यदि हम एक ब्राउज़र के अंदर हैं click हैंडलर, और दोनों Child और Parent कॉल setState , तो हम फिर से रेंडर नहीं करना चाहते हैं Child दो बार, और इसके बजाय उन्हें गंदा के रूप में चिह्नित करना पसंद करते हैं, और ब्राउज़र ईवेंट से बाहर निकलने से पहले उन्हें एक साथ फिर से प्रस्तुत करना पसंद करते हैं।

आप पूछ रहे हैं: हम वही सटीक काम (बैचिंग) क्यों नहीं कर सकते हैं, लेकिन सुलह के अंत की प्रतीक्षा किए बिना setState अपडेट को तुरंत this.state पर लिख दें। मुझे नहीं लगता कि एक स्पष्ट उत्तर है (या तो समाधान में ट्रेडऑफ हैं) लेकिन यहां कुछ कारण हैं जिनके बारे में मैं सोच सकता हूं।

आंतरिक संगति की गारंटी

भले ही state समकालिक रूप से अपडेट किया गया हो, props नहीं हैं। (जब तक आप मूल घटक को फिर से प्रस्तुत नहीं करते हैं, तब तक आप props नहीं जान सकते हैं, और यदि आप इसे समकालिक रूप से करते हैं, तो बैचिंग विंडो से बाहर हो जाती है।)

अभी रिएक्ट ( state , props , refs ) द्वारा प्रदान की गई वस्तुएं आंतरिक रूप से एक दूसरे के

जब आप केवल राज्य का उपयोग करते हैं, यदि यह समकालिक रूप से

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

हालांकि, मान लें कि इस स्थिति को कुछ घटकों में साझा करने के लिए उठाया जाना चाहिए ताकि आप इसे माता-पिता के पास ले जा सकें:

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

मैं इस बात पर प्रकाश डालना चाहता हूं कि विशिष्ट रिएक्ट ऐप्स में जो setState() पर भरोसा करते हैं, यह रिएक्ट-विशिष्ट रिफैक्टरिंग का सबसे सामान्य प्रकार है जो आप दैनिक आधार पर करेंगे

हालाँकि, यह हमारे कोड को तोड़ देता है!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

ऐसा इसलिए है, क्योंकि आपके द्वारा प्रस्तावित मॉडल में, this.state को तुरंत फ्लश कर दिया जाएगा, लेकिन this.props को नहीं। और हम माता-पिता को फिर से प्रस्तुत किए बिना तुरंत this.props फ्लश नहीं कर सकते, जिसका अर्थ है कि हमें बैचिंग को छोड़ना होगा (जो, मामले के आधार पर, प्रदर्शन को बहुत कम कर सकता है)।

यह कैसे टूट सकता है, इसके और भी सूक्ष्म मामले हैं, उदाहरण के लिए यदि आप एक नया राज्य बनाने के लिए props (अभी तक फ्लश नहीं किया गया) और state (तुरंत फ्लश किया जाना प्रस्तावित) से डेटा मिला रहे हैं : https://github.com/facebook/react/issues/122#issuecomment -81856416। रेफरी एक ही समस्या प्रस्तुत करते हैं: https://github.com/facebook/react/issues/122#issuecomment -22659651।

ये उदाहरण बिल्कुल सैद्धांतिक नहीं हैं। वास्तव में रिएक्ट रेडक्स बाइंडिंग में इस तरह की समस्या हुआ करती थी क्योंकि वे रिएक्ट प्रॉप्स को नॉन-रिएक्ट स्टेट के साथ मिलाते हैं: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ Reactjs/react-redux/pull/99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github. com/reactjs/react-redux/मुद्दों/525.

मुझे नहीं पता कि MobX उपयोगकर्ता इससे क्यों नहीं टकराए, लेकिन मेरा अंतर्ज्ञान यह है कि वे ऐसे परिदृश्यों में टकरा सकते हैं लेकिन उन्हें अपनी गलती मानते हैं। या हो सकता है कि वे props से ज्यादा नहीं पढ़ते हैं और इसके बजाय सीधे MobX म्यूटेबल ऑब्जेक्ट्स से पढ़ते हैं।

तो रिएक्ट आज इसे कैसे हल करता है? रिएक्ट में, this.state और this.props ही सुलह और फ्लशिंग के बाद ही अपडेट होते हैं, इसलिए आप 0 को रिफैक्टरिंग से पहले और बाद में प्रिंट होते हुए देखेंगे। यह उठाने की स्थिति को सुरक्षित बनाता है।

हां, कुछ मामलों में यह असुविधाजनक हो सकता है। विशेष रूप से अधिक ओओ पृष्ठभूमि से आने वाले लोगों के लिए जो एक ही स्थान पर एक पूर्ण राज्य अद्यतन का प्रतिनिधित्व करने के तरीके के बारे में सोचने के बजाय कई बार राज्य को बदलना चाहते हैं। मैं इसके साथ सहानुभूति कर सकता हूं, हालांकि मुझे लगता है कि राज्य के अपडेट को केंद्रित रखना डिबगिंग के नजरिए से स्पष्ट है: https://github.com/facebook/react/issues/122#issuecomment -19888472।

फिर भी, आपके पास उस स्थिति को स्थानांतरित करने का विकल्प है जिसे आप तुरंत कुछ बग़ल में परिवर्तनशील वस्तु में पढ़ना चाहते हैं, खासकर यदि आप इसे प्रतिपादन के लिए सत्य के स्रोत के रूप में उपयोग नहीं करते हैं। MobX आपको बहुत कुछ ऐसा करने देता है।

यदि आप जानते हैं कि आप क्या कर रहे हैं तो आपके पास पूरे पेड़ को ReactDOM.flushSync(fn) कहा जाता है। मुझे नहीं लगता कि हमने इसे अभी तक प्रलेखित किया है, लेकिन हम निश्चित रूप से 16.x रिलीज चक्र के दौरान किसी बिंदु पर ऐसा करेंगे। ध्यान दें कि यह वास्तव में कॉल के अंदर होने वाले अपडेट के लिए पूर्ण री-रेंडरिंग को बाध्य करता है, इसलिए आपको इसे बहुत कम उपयोग करना चाहिए। इस तरह यह props , state , और refs बीच आंतरिक स्थिरता की गारंटी को नहीं तोड़ता है।

संक्षेप में, रिएक्ट मॉडल हमेशा सबसे संक्षिप्त कोड की ओर नहीं ले जाता है, लेकिन यह आंतरिक रूप से सुसंगत है और यह सुनिश्चित करता है कि राज्य को ऊपर उठाना सुरक्षित है

समवर्ती अद्यतन सक्षम करना

वैचारिक रूप से, रिएक्ट ऐसा व्यवहार करता है जैसे कि उसके पास प्रति घटक एक एकल अद्यतन कतार हो। यही कारण है कि चर्चा बिल्कुल समझ में आती है: हम चर्चा करते हैं कि अपडेट को this.state तुरंत लागू करना है या नहीं क्योंकि हमें इसमें कोई संदेह नहीं है कि अपडेट उसी सटीक क्रम में लागू होंगे। हालाँकि, ऐसा नहीं होना चाहिए ( हाहा )।

हाल ही में, हम "async रेंडरिंग" के बारे में बहुत बात कर रहे हैं। मैं मानता हूं कि हमने इसका अर्थ बताने में बहुत अच्छा काम नहीं किया है, लेकिन यह आर एंड डी की प्रकृति है: आप एक ऐसे विचार के बाद जाते हैं जो अवधारणात्मक रूप से आशाजनक लगता है, लेकिन आप वास्तव में इसके साथ पर्याप्त समय बिताने के बाद ही इसके प्रभाव को समझते हैं।

"एसिंक रेंडरिंग" की व्याख्या करने का एक तरीका यह है कि रिएक्ट setState() कॉल के लिए अलग-अलग प्राथमिकताएं दे सकता है, यह

उदाहरण के लिए, यदि आप कोई संदेश टाइप कर रहे हैं, तो TextBox घटक में setState() कॉलों को तुरंत फ़्लश करने की आवश्यकता है। हालाँकि, यदि आप टाइप करते समय एक नया संदेश प्राप्त MessageBubble को एक निश्चित सीमा (जैसे एक सेकंड) तक प्रस्तुत करने में देरी करना बेहतर है, बजाय इसके कि टाइपिंग को अवरुद्ध करने के कारण टाइपिंग को ठप हो जाए। धागा।

अगर हम कुछ अपडेट को "निम्न प्राथमिकता" देते हैं, तो हम उनके रेंडरिंग को कुछ मिलीसेकंड के छोटे-छोटे हिस्सों में विभाजित कर सकते हैं ताकि वे उपयोगकर्ता के लिए ध्यान देने योग्य न हों।

मुझे पता है कि इस तरह के प्रदर्शन अनुकूलन बहुत रोमांचक या ठोस नहीं लग सकते हैं। आप कह सकते हैं: "हमें MobX के साथ इसकी आवश्यकता नहीं है, हमारी अपडेट ट्रैकिंग इतनी तेज़ है कि केवल री-रेंडर से बचने के लिए"। मुझे नहीं लगता कि यह सभी मामलों में सच है (उदाहरण के लिए MobX कितना तेज़ है, आपको अभी भी DOM नोड्स बनाना है और नए माउंटेड व्यू के लिए रेंडरिंग करना है)। फिर भी, अगर यह सच था, और यदि आपने जानबूझकर फैसला किया है कि आप हमेशा एक विशिष्ट जावास्क्रिप्ट लाइब्रेरी में वस्तुओं को लपेटने के साथ ठीक हैं जो पढ़ने और लिखने को ट्रैक करता है, तो शायद आपको इन अनुकूलन से उतना लाभ नहीं मिलता है।

लेकिन अतुल्यकालिक प्रतिपादन केवल प्रदर्शन अनुकूलन के बारे में नहीं है।

उदाहरण के लिए, उस मामले पर विचार करें जहां आप एक स्क्रीन से दूसरी स्क्रीन पर नेविगेट कर रहे हैं। आम तौर पर जब आप नई स्क्रीन प्रस्तुत कर रहे होते हैं तो आप एक स्पिनर दिखाते हैं।

हालांकि, अगर नेविगेशन काफी तेज है (एक या दो सेकंड के भीतर), तो स्पिनर को फ्लैश करना और तुरंत छिपाना एक खराब उपयोगकर्ता अनुभव का कारण बनता है। इससे भी बदतर, यदि आपके पास अलग-अलग async निर्भरता (डेटा, कोड, चित्र) के साथ घटकों के कई स्तर हैं, तो आप स्पिनरों के एक कैस्केड के साथ समाप्त होते हैं जो एक-एक करके संक्षेप में फ्लैश करते हैं। यह दोनों दृष्टि से अप्रिय है और सभी डीओएम रीफ्लो के कारण आपके ऐप को अभ्यास में धीमा कर देता है। यह बहुत अधिक बॉयलरप्लेट कोड का स्रोत भी है।

क्या यह अच्छा नहीं होगा यदि आप एक साधारण setState() जो एक अलग दृश्य प्रस्तुत करता है, तो हम अद्यतन दृश्य को "पृष्ठभूमि में" प्रस्तुत करना "शुरू" कर सकते हैं? कल्पना कीजिए कि बिना किसी समन्वय कोड को लिखे, आप एक स्पिनर दिखाना चुन सकते हैं यदि अपडेट एक निश्चित सीमा (जैसे एक सेकंड) से अधिक लेता है, और अन्यथा रिएक्ट को एक सहज संक्रमण करने दें जब पूरे नए सबट्री की एसिंक निर्भरताएं हों संतुष्ट । इसके अलावा, जब हम "प्रतीक्षा" कर रहे होते हैं, तो "पुरानी स्क्रीन" इंटरएक्टिव रहती है (उदाहरण के लिए आप संक्रमण के लिए एक अलग आइटम चुन सकते हैं), और रिएक्ट लागू करता है कि यदि इसमें बहुत अधिक समय लगता है, तो आपको एक स्पिनर दिखाना होगा।

यह पता चला है कि, वर्तमान रिएक्ट मॉडल और जीवनचक्र में कुछ समायोजन के साथ , हम वास्तव में इसे लागू कर सकते हैं! @acdlite पिछले कुछ हफ्तों से इस सुविधा पर काम कर एक RFC पोस्ट करेगा।

ध्यान दें कि यह केवल इसलिए संभव है क्योंकि this.state तुरंत फ्लश नहीं किया जाता है। यदि इसे तुरंत फ़्लश किया गया था, तो हमारे पास पृष्ठभूमि में दृश्य का "नया संस्करण" प्रस्तुत करना शुरू करने का कोई तरीका नहीं होगा, जबकि "पुराना संस्करण" अभी भी दृश्यमान और इंटरैक्टिव है। उनके स्वतंत्र राज्य के अपडेट टकराएंगे।

मैं इस सब की घोषणा करने के संबंध में @acdlite से गड़गड़ाहट नहीं चुराना चाहता, लेकिन मुझे आशा है कि यह कम से कम थोड़ा रोमांचक करने के लिए धन्यवाद।

सभी 31 टिप्पणियाँ

हम सभी @gaearon का इंतजार कर रहे हैं।

@Kaybarax अरे, यह सप्ताहांत है ;-)

@mweststrate ओह! मेरी गलती। ठंडा।

मैं यहां एक अंग पर बाहर जा रहा हूं और कहता हूं कि यह एक ही टिक में एकाधिक setState s बैच करने के कारण है।

मैं अगले सप्ताह छुट्टी पर जा रहा हूँ लेकिन मैं शायद मंगलवार को जाऊँगा इसलिए मैं सोमवार को उत्तर देने का प्रयास करूँगा।

समारोह enqueueUpdate (घटक) {
सुनिश्चित इंजेक्शन ();

// हमारे कोड के विभिन्न भाग (जैसे ReactCompositeComponent's
// _renderValidatedComponent) मान लें कि रेंडर करने के लिए कॉल नेस्टेड नहीं हैं;
// सत्यापित करें कि यह मामला है। (इसे प्रत्येक शीर्ष-स्तरीय अद्यतन द्वारा कहा जाता है
// फ़ंक्शन, जैसे सेटस्टेट, फोर्सअपडेट, आदि; सृजन और
// रिएक्टमाउंट में शीर्ष-स्तरीय घटकों का विनाश सुरक्षित है।)

अगर (!batchingStrategy.isBatchingUpdates) {
बैचिंगस्ट्रेटी.बैच्डअपडेट्स (एनक्यूयूअपडेट, कंपोनेंट);
वापसी;
}

गंदा कॉम्पोनेंट्स। पुश (घटक);
अगर (घटक._अपडेटबैचनंबर == शून्य) {
घटक._अपडेटबैचनंबर = अपडेटबैचनंबर + 1;
}
}

@mweststrate सिर्फ 2 सेंट: यह बहुत ही वैध प्रश्न है।
मुझे यकीन है कि हम सभी सहमत हैं कि राज्य के बारे में तर्क करना बहुत आसान होगा यदि सेटस्टेट समकालिक होता।
सेटस्टेट को अतुल्यकालिक बनाने के जो भी कारण थे, मुझे यकीन नहीं है कि प्रतिक्रिया टीम अच्छी तरह से तुलना करेगी, जो कि कमियां पेश करेगी, उदाहरण के लिए अब राज्य के बारे में तर्क करने में कठिनाई और डेवलपर्स के लिए भ्रम की स्थिति।

@mweststrate दिलचस्प रूप से मैंने यहाँ वही प्रश्न पूछा: https://discuss.reactjs.org/t/ ऐतिहासिक-reasons-behind-setstate-not-being-immediately-visible/8487

मैंने व्यक्तिगत रूप से इस विषय पर अन्य डेवलपर्स के भ्रम को देखा और देखा है। @gaearon जब आपके पास कुछ समय हो तो इसके लिए स्पष्टीकरण प्राप्त करना बहुत अच्छा होगा :)

क्षमा करें, यह वर्ष का अंत है और हम छुट्टियों से पहले जो कुछ भी काम कर रहे हैं उसे पूरा करने की कोशिश में हम गिटहब आदि पर थोड़ा पीछे रहे हैं।

मैं इस सूत्र पर वापस आने और इस पर चर्चा करने का इरादा रखता हूं। लेकिन यह एक गतिशील लक्ष्य भी है क्योंकि हम वर्तमान में async रिएक्ट सुविधाओं पर काम कर रहे हैं जो सीधे संबंधित हैं कि कैसे और कब this.state अपडेट किया जाता है। मैं कुछ लिखने में बहुत समय नहीं लगाना चाहता, और फिर इसे फिर से लिखना पड़ता है क्योंकि अंतर्निहित धारणाएं बदल गई हैं। तो मैं इसे खुला रखना चाहता हूं, लेकिन मुझे अभी तक पता नहीं है कि मैं एक निश्चित उत्तर कब दे पाऊंगा।

तो यहाँ कुछ विचार हैं। यह किसी भी तरह से पूर्ण प्रतिक्रिया नहीं है, लेकिन हो सकता है कि यह अभी भी कुछ न कहने से ज्यादा मददगार हो।

सबसे पहले, मुझे लगता है कि हम सहमत हैं कि बैच अपडेट के लिए सुलह में देरी setState() समकालिक रूप से पुन: प्रतिपादन कई मामलों में अक्षम होगा, और यदि हम जानते हैं कि हमें कई अपडेट मिलेंगे तो बैच अपडेट करना बेहतर होगा।

उदाहरण के लिए, यदि हम एक ब्राउज़र के अंदर हैं click हैंडलर, और दोनों Child और Parent कॉल setState , तो हम फिर से रेंडर नहीं करना चाहते हैं Child दो बार, और इसके बजाय उन्हें गंदा के रूप में चिह्नित करना पसंद करते हैं, और ब्राउज़र ईवेंट से बाहर निकलने से पहले उन्हें एक साथ फिर से प्रस्तुत करना पसंद करते हैं।

आप पूछ रहे हैं: हम वही सटीक काम (बैचिंग) क्यों नहीं कर सकते हैं, लेकिन सुलह के अंत की प्रतीक्षा किए बिना setState अपडेट को तुरंत this.state पर लिख दें। मुझे नहीं लगता कि एक स्पष्ट उत्तर है (या तो समाधान में ट्रेडऑफ हैं) लेकिन यहां कुछ कारण हैं जिनके बारे में मैं सोच सकता हूं।

आंतरिक संगति की गारंटी

भले ही state समकालिक रूप से अपडेट किया गया हो, props नहीं हैं। (जब तक आप मूल घटक को फिर से प्रस्तुत नहीं करते हैं, तब तक आप props नहीं जान सकते हैं, और यदि आप इसे समकालिक रूप से करते हैं, तो बैचिंग विंडो से बाहर हो जाती है।)

अभी रिएक्ट ( state , props , refs ) द्वारा प्रदान की गई वस्तुएं आंतरिक रूप से एक दूसरे के

जब आप केवल राज्य का उपयोग करते हैं, यदि यह समकालिक रूप से

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

हालांकि, मान लें कि इस स्थिति को कुछ घटकों में साझा करने के लिए उठाया जाना चाहिए ताकि आप इसे माता-पिता के पास ले जा सकें:

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

मैं इस बात पर प्रकाश डालना चाहता हूं कि विशिष्ट रिएक्ट ऐप्स में जो setState() पर भरोसा करते हैं, यह रिएक्ट-विशिष्ट रिफैक्टरिंग का सबसे सामान्य प्रकार है जो आप दैनिक आधार पर करेंगे

हालाँकि, यह हमारे कोड को तोड़ देता है!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

ऐसा इसलिए है, क्योंकि आपके द्वारा प्रस्तावित मॉडल में, this.state को तुरंत फ्लश कर दिया जाएगा, लेकिन this.props को नहीं। और हम माता-पिता को फिर से प्रस्तुत किए बिना तुरंत this.props फ्लश नहीं कर सकते, जिसका अर्थ है कि हमें बैचिंग को छोड़ना होगा (जो, मामले के आधार पर, प्रदर्शन को बहुत कम कर सकता है)।

यह कैसे टूट सकता है, इसके और भी सूक्ष्म मामले हैं, उदाहरण के लिए यदि आप एक नया राज्य बनाने के लिए props (अभी तक फ्लश नहीं किया गया) और state (तुरंत फ्लश किया जाना प्रस्तावित) से डेटा मिला रहे हैं : https://github.com/facebook/react/issues/122#issuecomment -81856416। रेफरी एक ही समस्या प्रस्तुत करते हैं: https://github.com/facebook/react/issues/122#issuecomment -22659651।

ये उदाहरण बिल्कुल सैद्धांतिक नहीं हैं। वास्तव में रिएक्ट रेडक्स बाइंडिंग में इस तरह की समस्या हुआ करती थी क्योंकि वे रिएक्ट प्रॉप्स को नॉन-रिएक्ट स्टेट के साथ मिलाते हैं: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ Reactjs/react-redux/pull/99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github. com/reactjs/react-redux/मुद्दों/525.

मुझे नहीं पता कि MobX उपयोगकर्ता इससे क्यों नहीं टकराए, लेकिन मेरा अंतर्ज्ञान यह है कि वे ऐसे परिदृश्यों में टकरा सकते हैं लेकिन उन्हें अपनी गलती मानते हैं। या हो सकता है कि वे props से ज्यादा नहीं पढ़ते हैं और इसके बजाय सीधे MobX म्यूटेबल ऑब्जेक्ट्स से पढ़ते हैं।

तो रिएक्ट आज इसे कैसे हल करता है? रिएक्ट में, this.state और this.props ही सुलह और फ्लशिंग के बाद ही अपडेट होते हैं, इसलिए आप 0 को रिफैक्टरिंग से पहले और बाद में प्रिंट होते हुए देखेंगे। यह उठाने की स्थिति को सुरक्षित बनाता है।

हां, कुछ मामलों में यह असुविधाजनक हो सकता है। विशेष रूप से अधिक ओओ पृष्ठभूमि से आने वाले लोगों के लिए जो एक ही स्थान पर एक पूर्ण राज्य अद्यतन का प्रतिनिधित्व करने के तरीके के बारे में सोचने के बजाय कई बार राज्य को बदलना चाहते हैं। मैं इसके साथ सहानुभूति कर सकता हूं, हालांकि मुझे लगता है कि राज्य के अपडेट को केंद्रित रखना डिबगिंग के नजरिए से स्पष्ट है: https://github.com/facebook/react/issues/122#issuecomment -19888472।

फिर भी, आपके पास उस स्थिति को स्थानांतरित करने का विकल्प है जिसे आप तुरंत कुछ बग़ल में परिवर्तनशील वस्तु में पढ़ना चाहते हैं, खासकर यदि आप इसे प्रतिपादन के लिए सत्य के स्रोत के रूप में उपयोग नहीं करते हैं। MobX आपको बहुत कुछ ऐसा करने देता है।

यदि आप जानते हैं कि आप क्या कर रहे हैं तो आपके पास पूरे पेड़ को ReactDOM.flushSync(fn) कहा जाता है। मुझे नहीं लगता कि हमने इसे अभी तक प्रलेखित किया है, लेकिन हम निश्चित रूप से 16.x रिलीज चक्र के दौरान किसी बिंदु पर ऐसा करेंगे। ध्यान दें कि यह वास्तव में कॉल के अंदर होने वाले अपडेट के लिए पूर्ण री-रेंडरिंग को बाध्य करता है, इसलिए आपको इसे बहुत कम उपयोग करना चाहिए। इस तरह यह props , state , और refs बीच आंतरिक स्थिरता की गारंटी को नहीं तोड़ता है।

संक्षेप में, रिएक्ट मॉडल हमेशा सबसे संक्षिप्त कोड की ओर नहीं ले जाता है, लेकिन यह आंतरिक रूप से सुसंगत है और यह सुनिश्चित करता है कि राज्य को ऊपर उठाना सुरक्षित है

समवर्ती अद्यतन सक्षम करना

वैचारिक रूप से, रिएक्ट ऐसा व्यवहार करता है जैसे कि उसके पास प्रति घटक एक एकल अद्यतन कतार हो। यही कारण है कि चर्चा बिल्कुल समझ में आती है: हम चर्चा करते हैं कि अपडेट को this.state तुरंत लागू करना है या नहीं क्योंकि हमें इसमें कोई संदेह नहीं है कि अपडेट उसी सटीक क्रम में लागू होंगे। हालाँकि, ऐसा नहीं होना चाहिए ( हाहा )।

हाल ही में, हम "async रेंडरिंग" के बारे में बहुत बात कर रहे हैं। मैं मानता हूं कि हमने इसका अर्थ बताने में बहुत अच्छा काम नहीं किया है, लेकिन यह आर एंड डी की प्रकृति है: आप एक ऐसे विचार के बाद जाते हैं जो अवधारणात्मक रूप से आशाजनक लगता है, लेकिन आप वास्तव में इसके साथ पर्याप्त समय बिताने के बाद ही इसके प्रभाव को समझते हैं।

"एसिंक रेंडरिंग" की व्याख्या करने का एक तरीका यह है कि रिएक्ट setState() कॉल के लिए अलग-अलग प्राथमिकताएं दे सकता है, यह

उदाहरण के लिए, यदि आप कोई संदेश टाइप कर रहे हैं, तो TextBox घटक में setState() कॉलों को तुरंत फ़्लश करने की आवश्यकता है। हालाँकि, यदि आप टाइप करते समय एक नया संदेश प्राप्त MessageBubble को एक निश्चित सीमा (जैसे एक सेकंड) तक प्रस्तुत करने में देरी करना बेहतर है, बजाय इसके कि टाइपिंग को अवरुद्ध करने के कारण टाइपिंग को ठप हो जाए। धागा।

अगर हम कुछ अपडेट को "निम्न प्राथमिकता" देते हैं, तो हम उनके रेंडरिंग को कुछ मिलीसेकंड के छोटे-छोटे हिस्सों में विभाजित कर सकते हैं ताकि वे उपयोगकर्ता के लिए ध्यान देने योग्य न हों।

मुझे पता है कि इस तरह के प्रदर्शन अनुकूलन बहुत रोमांचक या ठोस नहीं लग सकते हैं। आप कह सकते हैं: "हमें MobX के साथ इसकी आवश्यकता नहीं है, हमारी अपडेट ट्रैकिंग इतनी तेज़ है कि केवल री-रेंडर से बचने के लिए"। मुझे नहीं लगता कि यह सभी मामलों में सच है (उदाहरण के लिए MobX कितना तेज़ है, आपको अभी भी DOM नोड्स बनाना है और नए माउंटेड व्यू के लिए रेंडरिंग करना है)। फिर भी, अगर यह सच था, और यदि आपने जानबूझकर फैसला किया है कि आप हमेशा एक विशिष्ट जावास्क्रिप्ट लाइब्रेरी में वस्तुओं को लपेटने के साथ ठीक हैं जो पढ़ने और लिखने को ट्रैक करता है, तो शायद आपको इन अनुकूलन से उतना लाभ नहीं मिलता है।

लेकिन अतुल्यकालिक प्रतिपादन केवल प्रदर्शन अनुकूलन के बारे में नहीं है।

उदाहरण के लिए, उस मामले पर विचार करें जहां आप एक स्क्रीन से दूसरी स्क्रीन पर नेविगेट कर रहे हैं। आम तौर पर जब आप नई स्क्रीन प्रस्तुत कर रहे होते हैं तो आप एक स्पिनर दिखाते हैं।

हालांकि, अगर नेविगेशन काफी तेज है (एक या दो सेकंड के भीतर), तो स्पिनर को फ्लैश करना और तुरंत छिपाना एक खराब उपयोगकर्ता अनुभव का कारण बनता है। इससे भी बदतर, यदि आपके पास अलग-अलग async निर्भरता (डेटा, कोड, चित्र) के साथ घटकों के कई स्तर हैं, तो आप स्पिनरों के एक कैस्केड के साथ समाप्त होते हैं जो एक-एक करके संक्षेप में फ्लैश करते हैं। यह दोनों दृष्टि से अप्रिय है और सभी डीओएम रीफ्लो के कारण आपके ऐप को अभ्यास में धीमा कर देता है। यह बहुत अधिक बॉयलरप्लेट कोड का स्रोत भी है।

क्या यह अच्छा नहीं होगा यदि आप एक साधारण setState() जो एक अलग दृश्य प्रस्तुत करता है, तो हम अद्यतन दृश्य को "पृष्ठभूमि में" प्रस्तुत करना "शुरू" कर सकते हैं? कल्पना कीजिए कि बिना किसी समन्वय कोड को लिखे, आप एक स्पिनर दिखाना चुन सकते हैं यदि अपडेट एक निश्चित सीमा (जैसे एक सेकंड) से अधिक लेता है, और अन्यथा रिएक्ट को एक सहज संक्रमण करने दें जब पूरे नए सबट्री की एसिंक निर्भरताएं हों संतुष्ट । इसके अलावा, जब हम "प्रतीक्षा" कर रहे होते हैं, तो "पुरानी स्क्रीन" इंटरएक्टिव रहती है (उदाहरण के लिए आप संक्रमण के लिए एक अलग आइटम चुन सकते हैं), और रिएक्ट लागू करता है कि यदि इसमें बहुत अधिक समय लगता है, तो आपको एक स्पिनर दिखाना होगा।

यह पता चला है कि, वर्तमान रिएक्ट मॉडल और जीवनचक्र में कुछ समायोजन के साथ , हम वास्तव में इसे लागू कर सकते हैं! @acdlite पिछले कुछ हफ्तों से इस सुविधा पर काम कर एक RFC पोस्ट करेगा।

ध्यान दें कि यह केवल इसलिए संभव है क्योंकि this.state तुरंत फ्लश नहीं किया जाता है। यदि इसे तुरंत फ़्लश किया गया था, तो हमारे पास पृष्ठभूमि में दृश्य का "नया संस्करण" प्रस्तुत करना शुरू करने का कोई तरीका नहीं होगा, जबकि "पुराना संस्करण" अभी भी दृश्यमान और इंटरैक्टिव है। उनके स्वतंत्र राज्य के अपडेट टकराएंगे।

मैं इस सब की घोषणा करने के संबंध में @acdlite से गड़गड़ाहट नहीं चुराना चाहता, लेकिन मुझे आशा है कि यह कम से कम थोड़ा रोमांचक करने के लिए धन्यवाद।

रिएक्ट की वास्तुकला के पीछे के निर्णयों की गहन व्याख्या में अद्भुत। धन्यवाद।

निशान

धन्यवाद, डैन।

मैं ❤️ यह मुद्दा। कमाल का सवाल और कमाल का जवाब। मैंने हमेशा सोचा था कि यह एक खराब डिजाइन निर्णय था, अब मुझे पुनर्विचार करना होगा

धन्यवाद, डैन।

मैं इसे asyncAwesome setState कहता हूं: मुस्कान:

मुझे लगता है कि सब कुछ पहले एसिंक्स को लागू किया जाना चाहिए, और यदि आपको सिंक ऑपरेशन की आवश्यकता मिलती है, तो ठीक है, एसिंक ऑपरेशन को पूरा होने की प्रतीक्षा के साथ लपेटें। एसिंक कोड से सिंक कोड बनाना बहुत आसान है (आपको केवल एक रैपर चाहिए) रिवर्स की तुलना में (जिसे मूल रूप से एक पूर्ण पुनर्लेखन की आवश्यकता होती है, जब तक कि आप थ्रेडिंग तक नहीं पहुंच जाते, जो कि हल्के वजन पर नहीं होता है)।

@gaearon व्यापक स्पष्टीकरण के लिए धन्यवाद! यह मुझे लंबे समय से परेशान कर रहा है ("एक अच्छा कारण होना चाहिए, लेकिन कोई नहीं बता सकता कि कौन सा है")। लेकिन अब यह पूरी तरह से समझ में आता है और मैं देखता हूं कि यह वास्तव में सचेत निर्णय कैसे है :)। व्यापक उत्तर के लिए बहुत बहुत धन्यवाद, वास्तव में इसकी सराहना करते हैं!

या हो सकता है कि वे प्रॉप्स से उतना नहीं पढ़ते हैं और इसके बजाय सीधे MobX म्यूटेबल ऑब्जेक्ट्स से पढ़ते हैं।

मुझे लगता है कि यह वास्तव में काफी सच है, MobX में प्रॉप्स को आमतौर पर सिर्फ कंपोनेंट कॉन्फ़िगरेशन के रूप में उपयोग किया जाता है, और डोमेन डेटा को आमतौर पर प्रॉप्स में कैप्चर नहीं किया जाता है, लेकिन डोमेन संस्थाओं में जो घटकों के बीच से गुजरते हैं।

फिर से, बहुत बहुत धन्यवाद!

@gaearon विस्तृत और महान स्पष्टीकरण के लिए धन्यवाद।
हालाँकि यहाँ अभी भी कुछ कमी है जो मुझे लगता है कि मैं समझता हूँ लेकिन सुनिश्चित करना चाहता हूँ।

जब घटना "आउटसाइड रिएक्ट" पंजीकृत होती है, तो इसका मतलब है कि उदाहरण के लिए रेफरी पर शायद addEventListener माध्यम से। फिर कोई बैचिंग नहीं हो रही है।
इस कोड पर विचार करें:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    this.refBtn.addEventListener("click", this.onClick);
  }

  componentWillUnmount() {
    this.refBtn.removeEventListener("click", this.onClick);
  }

  onClick = () => {
    console.log("before setState", this.state.count);
    this.setState(state => ({ count: state.count + 1 }));
    console.log("after setState", this.state.count);
  };

  render() {
    return (
      <div>
        <button onClick={this.onClick}>React Event</button>
        <button ref={ref => (this.refBtn = ref)}>Direct DOM event</button>
      </div>
    );
  }
}

जब हम "रिएक्ट इवेंट" बटन पर क्लिक करेंगे, तो हम कंसोल में देखेंगे:
"before setState" 0
"after setState" 0
जब दूसरा बटन "डायरेक्ट डोम इवेंट" क्लिक किया जाएगा, तो हम कंसोल में देखेंगे:
"before setState" 0
"after setState" 1

कुछ छोटे शोध और स्रोत कोड के माध्यम से ब्राउज़ करने के बाद, मुझे लगता है कि मुझे पता है कि ऐसा क्यों होता है।
प्रतिक्रिया घटना के प्रवाह को पूरी तरह से नियंत्रित नहीं कर सकती है और यह सुनिश्चित नहीं हो सकता है कि अगली घटना कब और कैसे आग लगेगी, इसलिए "पैनिक-मोड" के रूप में यह तुरंत राज्य परिवर्तन को ट्रिगर करेगा।

इस बारे में आपके विचार क्या हैं? :विचारधारा:

@ sag1v हालांकि थोड़ा संबंधित है, नए प्रश्नों के लिए एक नया मुद्दा खोलना शायद स्पष्ट है। इसे इस से जोड़ने के लिए विवरण में कहीं पर #11527 का उपयोग करें।

@sag1v @gaearon ने मुझे यहां बहुत ही संक्षिप्त जवाब दिया था https://twitter.com/dan_abramov/status/949992957180104704 । मुझे लगता है कि इस पर उनकी राय भी मुझे और अधिक ठोस जवाब देगी।

@mweststrate मैंने एक नया मुद्दा खोलने के बारे में सोचा लेकिन फिर मुझे एहसास हुआ कि यह सीधे आपके प्रश्न से संबंधित है " setState अतुल्यकालिक क्यों है?"।
चूंकि यह चर्चा setState "async" बनाने पर किए गए निर्णयों के बारे में है, ठीक है, मैंने सोचा कि इसे "सिंक" कब और क्यों बनाया जाए।
मुझे एक नया मुद्दा खोलने में कोई आपत्ति नहीं है अगर मैंने आपको यह विश्वास नहीं दिलाया कि मेरी पोस्ट इस मुद्दे से संबंधित है: पलक:

@Kaybarax क्योंकि आपका प्रश्न "_कब है यह सिंक_" था और न कि "_ यह सिंक_ क्यों है"?।
जैसा कि मैंने अपनी पोस्ट में उल्लेख किया है, मुझे लगता है कि मुझे पता है कि क्यों, लेकिन मैं सुनिश्चित होना चाहता हूं और आधिकारिक उत्तर प्राप्त करना चाहता हूं। :मुस्कुराओ:

प्रतिक्रिया घटना के प्रवाह को पूरी तरह से नियंत्रित नहीं कर सकती है और यह सुनिश्चित नहीं हो सकता है कि अगली घटना कब और कैसे आग लगेगी, इसलिए "पैनिक-मोड" के रूप में यह तुरंत राज्य परिवर्तन को ट्रिगर करेगा

की तरह। हालांकि यह this.state अपडेट करने के सवाल से बिल्कुल संबंधित नहीं है।

आप जो पूछ रहे हैं वह यह है कि रिएक्ट बैचिंग को किन मामलों में सक्षम करता है। रिएक्ट वर्तमान में रिएक्ट द्वारा प्रबंधित इवेंट हैंडलर्स के अंदर अपडेट को बैच करता है क्योंकि रिएक्ट शीर्ष कॉल स्टैक फ्रेम पर "बैठता है" और जानता है कि सभी रिएक्ट इवेंट हैंडलर कब चले हैं। उस समय यह अपडेट को फ्लश करता है।

यदि ईवेंट हैंडलर रिएक्ट द्वारा सेट नहीं किया गया है, तो वर्तमान में यह अपडेट को सिंक्रोनस बनाता है। क्योंकि यह नहीं जानता कि प्रतीक्षा करना सुरक्षित है या नहीं, और अन्य अपडेट जल्द ही होंगे या नहीं।

रिएक्ट के भविष्य के संस्करणों में यह व्यवहार बदल जाएगा। योजना डिफ़ॉल्ट रूप से अपडेट को कम प्राथमिकता के रूप में मानने की है ताकि वे तुरंत एक साथ फ़्लश करने के लिए ऑप्ट-इन के साथ एक साथ (उदाहरण के लिए एक सेकंड के भीतर) एकत्रित और बैच हो जाएं। आप यहां और अधिक पढ़ सकते हैं: https://github.com/facebook/react/issues/11171#issuecomment -357945371।

विस्मयकारी!

उस प्रश्न और उत्तर को कहीं अधिक पहुंच योग्य दस्तावेज किया जाना चाहिए। धन्यवाद दोस्तों हमें ज्ञान दे रहे हैं।

बहुत कुछ सीखा । धन्यवाद

अपने दृष्टिकोण को सूत्र में जोड़ने का प्रयास कर रहा हूँ। मैं कुछ महीनों में MobX पर आधारित ऐप पर काम करता हूं, मैं वर्षों से क्लोजरस्क्रिप्ट की खोज कर रहा हूं और अपना खुद का रिएक्ट विकल्प (जिसे रेस्पो कहा जाता है) बनाया है, मैंने शुरुआती दिनों में Redux की कोशिश की, हालांकि बहुत कम समय, और मैं रीज़नएमएल पर दांव लगा रहा हूं।

रिएक्ट और फंक्शनल प्रोग्रामिंग (एफपी) के संयोजन में मुख्य विचार यह है कि आपको डेटा का एक टुकड़ा मिला है जिसे आप एफपी में कानूनों का पालन करने वाले कौशल के साथ एक दृश्य में प्रस्तुत कर सकते हैं। यदि आप केवल शुद्ध कार्यों का उपयोग करते हैं तो आपको कोई दुष्प्रभाव नहीं हुआ है।

प्रतिक्रिया शुद्ध कार्यात्मक नहीं है। घटकों के अंदर स्थानीय राज्यों को गले लगाकर, रिएक्ट में डोम और अन्य ब्राउज़र एपीआई (भी MobX के अनुकूल) से संबंधित विभिन्न पुस्तकालयों के साथ बातचीत करने की शक्ति है, जो इस बीच प्रतिक्रिया को अशुद्ध बनाता है। हालाँकि, मैंने क्लोजरस्क्रिप्ट में कोशिश की, अगर रिएक्ट शुद्ध है, तो यह आपदा हो सकती है क्योंकि इतने सारे मौजूदा पुस्तकालयों के साथ बातचीत करना वास्तव में कठिन है, जिनके दुष्प्रभाव हैं।

तो रेस्पो (मेरा अपना समाधान) में, मेरे दो लक्ष्य थे जो परस्पर विरोधी प्रतीत होते हैं, 1) view = f(store) इसलिए कोई स्थानीय राज्य अपेक्षित नहीं है; 2) मैं वैश्विक रेड्यूसर में सभी घटक यूआई राज्यों को प्रोग्राम करना पसंद नहीं करता क्योंकि इसे बनाए रखना मुश्किल हो सकता है। अंत में, मुझे लगा कि मुझे एक सिंटैक्स चीनी की आवश्यकता है, जो मुझे वैश्विक स्टोर में घटक राज्यों को paths साथ बनाए रखने में मदद करता है, इस बीच मैं क्लोजर मैक्रोज़ के साथ घटक के अंदर राज्य अपडेट लिखता हूं।

तो मैंने जो सीखा: स्थानीय राज्य एक डेवलपर अनुभव सुविधा है, नीचे हम वैश्विक राज्य चाहते हैं जो हमारे इंजनों को गहरे स्तरों में अनुकूलन करने में सक्षम बनाता है। तो, MobX लोग OOP को पसंद करते हैं, क्या यह डेवलपर अनुभव के लिए है, या इंजन के लिए है?

वैसे, मैंने रिएक्ट के भविष्य और इसकी एसिंक सुविधाओं के बारे में बात की, यदि आप इसे याद नहीं करते हैं:
https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

दान, तुम मेरे आदर्श हो..... बहुत-बहुत धन्यवाद।

क्या यह पृष्ठ उपयोगी था?
0 / 5 - 0 रेटिंग्स