2015-11-17 9 views
7

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

मुझे अपने प्रतिक्रिया/रेडक्स आर्किटेक्चर में एक समस्या मिली है जिसे मुझे कई उदाहरण नहीं मिल रहे हैं।

मेरा आवेदन प्रदर्शन के लिए अपरिवर्तनीयता, संदर्भ समानता और PureRenderMixin पर निर्भर करता है। लेकिन मुझे इस तरह के वास्तुकला के साथ अधिक सामान्य और पुन: प्रयोज्य घटकों को बनाना मुश्किल लगता है।

चलें कहते हैं कि मैं एक सामान्य ListView इस तरह है:

const ListView = React.createClass({ 
    mixins: [PureRenderMixin], 

    propTypes: { 
     items: arrayOf(shape({ 
      icon: string.isRequired, 
      title: string.isRequired, 
      description: string.isRequired, 
     })).isRequired, 
    }, 

    render() { 
     return (
      <ul> 
       {this.props.items.map((item, idx) => (
        <li key={idx}> 
         <img src={item.icon} /> 
         <h3>{item.title}</h3> 
         <p>{item.description}</p> 
        </li> 
       )} 
      </ul> 
     ); 
    }, 
}); 

और फिर मैं एक redux की दुकान से उपयोगकर्ताओं को प्रदर्शित करने के लिए सूची दृश्य का उपयोग करना चाहते। उनके पास यह संरचना है:

const usersList = [ 
    { name: 'John Appleseed', age: 18, avatar: 'generic-user.png' }, 
    { name: 'Kate Example', age: 32, avatar: 'kate.png' }, 
]; 

यह मेरे अनुप्रयोगों में अब तक एक बहुत ही सामान्य सेनारियो है। मैं आमतौर पर इसे पसंद प्रस्तुत करना:

<ListView items={usersList.map(user => ({ 
    title: user.name, 
    icon: user.avatar, 
    description: `Age: ${user.age}`, 
}))} /> 

समस्या है कि map संदर्भ समानता टूट जाता है। usersList.map(...) का नतीजा हमेशा नई सूची वस्तुएं होगी। तो जब भी usersList पिछले प्रस्तुत करने के बाद से नहीं बदला है, ListView यह नहीं पता कि यह अभी भी items -list के बराबर है।

मैं इसके लिए तीन समाधान देख सकता हूं, लेकिन मुझे उन्हें बहुत अच्छा नहीं लगता है।

समाधान 1: एक पैरामीटर के रूप में एक transformItem समारोह दर्रा और कच्चे वस्तुओं

const ListView = React.createClass({ 
    mixins: [PureRenderMixin], 

    propTypes: { 
     items: arrayOf(object).isRequired, 
     transformItem: func, 
    }, 

    getDefaultProps() { 
     return { 
      transformItem: item => item, 
     } 
    }, 

    render() { 
     return (
      <ul> 
       {this.props.items.map((item, idx) => { 
        const transformedItem = this.props.transformItem(item); 

        return (
         <li key={idx}> 
          <img src={transformedItem.icon} /> 
          <h3>{transformedItem.title}</h3> 
          <p>{transformedItem.description}</p> 
         </li> 
        ); 
       })} 
      </ul> 
     ); 
    }, 
}); 

यह बहुत अच्छी तरह से काम करेंगे की एक सरणी items रहने दो। मैं तो इस तरह मेरे उपयोगकर्ता सूची प्रदान कर सकते हैं:

<ListView 
    items={usersList} 
    transformItem={user => ({ 
     title: user.name, 
     icon: user.avatar, 
     description: `Age: ${user.age}`, 
    })} 
/> 

और ListView केवल rerender जब usersList परिवर्तन होगा। लेकिन मैं रास्ते पर प्रोप प्रकार सत्यापन खो दिया।

समाधान 2:

const UsersList = React.createClass({ 
    mixins: [PureRenderMixin], 

    propTypes: { 
     items: arrayOf(shape({ 
      name: string.isRequired, 
      avatar: string.isRequired, 
      age: number.isRequired, 
     })), 
    }, 

    render() { 
     return (
      <ListView 
       items={this.props.items.map(user => ({ 
        title: user.name, 
        icon: user.avatar, 
        description: user.age, 
       }))} 
      /> 
     ); 
    } 
}); 

लेकिन वह एक बहुत अधिक कोड है: एक विशेष घटक है कि ListView लपेटता बनाओ! बेहतर होगा अगर मैं एक स्टेटलेस घटक का उपयोग कर सकता हूं, लेकिन मुझे पता नहीं चला है कि वे PureRenderMixin के साथ सामान्य घटकों की तरह काम करते हैं।

const UsersList = ({ users }) => (
    <ListView 
     items={users.map(user => ({ 
      title: user.name, 
      icon: user.avatar, 
      description: user.age, 
     }))} 
    /> 
); 

समाधान 3 (राज्यविहीन घटकों उदासी अभी भी प्रतिक्रिया का एक गैर-दस्तावेजी सुविधा है):

const ParameterTransformer = React.createClass({ 
    mixin: [PureRenderMixin], 

    propTypes: { 
     component: object.isRequired, 
     transformers: arrayOf(func).isRequired, 
    }, 

    render() { 
     let transformed = {}; 

     this.props.transformers.forEach((transformer, propName) => { 
      transformed[propName] = transformer(this.props[propName]); 
     }); 

     return (
      <this.props.component 
       {...this.props} 
       {...transformed} 
      > 
       {this.props.children} 
      </this.props.component> 
     ); 
    } 
}); 

एक सामान्य शुद्ध रूपांतरित होने वाले घटक बना सकते हैं और तरह

<ParameterTransformer 
    component={ListView} 
    items={usersList} 
    transformers={{ 
     items: user => ({ 
      title: user.name, 
      icon: user.avatar, 
      description: `Age: ${user.age}`, 
     }) 
    }} 
/> 

हैं इसका इस्तेमाल ये एकमात्र समाधान हैं, या क्या मुझे कुछ याद आया है?

इनमें से कोई भी समाधान उस घटक के साथ बहुत अच्छी तरह फिट बैठता है जिस पर मैं वर्तमान में काम कर रहा हूं। नंबर एक सबसे अच्छा फिट बैठता है, लेकिन मुझे लगता है कि वह ट्रैक है जिसे मैं आगे बढ़ाना चुनता हूं, फिर मेरे द्वारा बनाए गए सभी प्रकार के जेनेरिक घटकों में इन transform गुण होना चाहिए। और यह भी एक प्रमुख नकारात्मक पक्ष है कि मैं ऐसे घटकों को बनाते समय प्रतिक्रिया प्रोप प्रकार सत्यापन का उपयोग नहीं कर सकता।

समाधान 2 शायद सबसे अधिक अर्थपूर्ण सही है, कम से कम इस ListView उदाहरण के लिए। लेकिन मुझे लगता है कि यह बहुत verbose है, और यह भी मेरे असली उपयोग के साथ बहुत अच्छी तरह से फिट नहीं है।

समाधान 3 बहुत जादू और अपठनीय है।

उत्तर

7

वहाँ एक और समाधान है कि आमतौर पर Redux के साथ प्रयोग किया है: memoized चयनकर्ताओं

पुस्तकालय Reselect की तरह एक और समारोह है कि कॉल के बीच संदर्भ समानता के लिए तर्क की जाँच करता है, और एक कैश्ड मान देता है यदि तर्क में कोई बदलाव नहीं कर रहे हैं में एक समारोह (अपने नक्शे कॉल की तरह) रैप करने के लिए एक तरह से प्रदान करते हैं।

आपका परिदृश्य के लिए एक आदर्श उदाहरण है एक memoized चयनकर्ता-जब से तुम अचल स्थिति को लागू कर रहे हैं, तो आप इस धारणा है कि जब तक आपके usersList संदर्भ के रूप में एक ही है, map करने के लिए अपने कॉल से उत्पादन एक ही होगा बना सकते हैं। आपके usersList संदर्भ परिवर्तन तक चयनकर्ता map फ़ंक्शन के वापसी मूल्य को कैश कर सकता है।

एक ज्ञापन चयनकर्ता बनाने के लिए अचयनित करने का उपयोग करके, आपको पहले एक (या अधिक) इनपुट चयनकर्ताओं और परिणाम फ़ंक्शन की आवश्यकता होती है। इनपुट चयनकर्ता आमतौर पर अभिभावक राज्य वस्तु से बाल वस्तुओं का चयन करने के लिए उपयोग किए जाते हैं; आपके मामले में, आप सीधे उस ऑब्जेक्ट से चयन कर रहे हैं, जिससे आप पहचान फ़ंक्शन को पहले तर्क के रूप में पास कर सकते हैं। दूसरा तर्क, परिणाम फ़ंक्शन, वह फ़ंक्शन है जो चयनकर्ता द्वारा कैश किए जाने वाले मान को उत्पन्न करता है।

var createSelector = require('reselect').createSelector; // ES5 
import { createSelector } from 'reselect'; // ES6 

var usersListItemSelector = createSelector(
    ul => ul, // input selector 
    ul => ul.map(user => { // result selector 
     title: user.name, 
     icon: user.avatar, 
     description: `Age: ${user.age}`, 
    }) 
); 

// ... 

<ListView items={usersListItemSelector(usersList)} /> 

अब, हर बार प्रतिक्रिया अपने ListView घटक प्रस्तुत हुई है, अपने memoized चयनकर्ता बुलाया जाएगा, और यदि एक अलग usersList में पारित हो जाता है अपने map परिणाम समारोह केवल बुलाया जाएगा।

+0

Memoization, हाँ, निश्चित रूप से ! मैं इसके बारे में कैसे सोच नहीं सकता? या, विचार ने मुझे स्ट्रोक किया है, लेकिन मैंने इसके बारे में नहीं सोचा (वास्तव में पहले कभी भी उपयोगी कुछ भी याद रखने की आवश्यकता नहीं है), इसलिए मैंने सोचा कि इसके परिणामस्वरूप सभी पिछली चयनित सूचियों का एक बड़ा कैश होगा। लेकिन cource के, सबसे हाल के परिणाम से अधिक पकड़ने और उसके खिलाफ जांच करने की जरूरत नहीं है। पुस्तकालय में खींचने के बजाय खुद को लागू करने के लिए ज्ञापन संभवतः आसान है। –

+0

निश्चित रूप से, आप इसे स्वयं लागू कर सकते हैं-चयन रद्द करें [कोड की केवल 82 पंक्तियां] (https://github.com/rackt/reselect/blob/master/src/index.js#L82)। –

संबंधित मुद्दे

 संबंधित मुद्दे