Användning av Immutable.JS med Redux#

Innehållsförteckning#

  • Varför ska jag använda ett bibliotek med fokus på oföränderliga filer som Immutable.JS?
  • Varför ska jag välja Immutable.JS som ett bibliotek för oföränderliga filer?
  • Vilka problem finns det med att använda Immutable.JS?
  • Är Immutable.JS värt besväret?
  • Vad är några välgrundade bästa metoder för att använda Immutable.JS med Redux?

Varför ska jag använda ett bibliotek med inriktning på immutabilitet som Immutable.JS?#

Bibliotek med inriktning på immutabilitet som Immutable.JS har utformats för att övervinna de problem med oföränderlighet som är inneboende i JavaScript och ger alla fördelar med oföränderlighet med den prestanda som din app kräver.

Om du väljer att använda ett sådant bibliotek, eller om du håller dig till vanlig JavaScript, beror på hur bekväm du är med att lägga till ytterligare ett beroende till din app, eller hur säker du är på att du kan undvika de fallgropar som är inneboende i JavaScript:s sätt att hantera oföränderlighet.

Vilket alternativ du än väljer, se till att du är bekant med begreppen oföränderlighet, sidoeffekter och mutation. Se särskilt till att du har en djup förståelse för vad JavaScript gör vid uppdatering och kopiering av värden för att skydda dig mot oavsiktliga mutationer som försämrar appens prestanda eller förstör den helt och hållet.

Ytterligare information#

Dokumentation

  • Recept: immutability, side effects and mutation

Artiklar

  • Introduction to Immutable.js and Functional Programming Concepts
  • Pros and Cons of using immutability with React.js

Varför ska jag välja Immutable.JS som ett immutabelt bibliotek?#

Immutable.JS utformades för att tillhandahålla immutabilitet på ett prestandamässigt sätt i ett försök att övervinna begränsningarna av immutabilitet med JavaScript. De viktigaste fördelarna är:

Garanterad oföränderlighet#

Data som är inkapslade i ett Immutable.JS-objekt muteras aldrig. En ny kopia returneras alltid. Detta står i kontrast till JavaScript, där vissa operationer inte muterar dina data (t.ex. vissa Array-metoder, inklusive map, filter, concat, forEach etc.), men andra gör det (Arrays pop, push, splice etc.).

Rikligt API#

Immutable.JS tillhandahåller en rik uppsättning oföränderliga objekt för att kapsla in dina data (t.ex.t.ex. kartor, listor, uppsättningar, poster osv.) och en omfattande uppsättning metoder för att manipulera dem, inklusive metoder för att sortera, filtrera och gruppera data, vända dem, platta dem och skapa delmängder.

Prestanda#

Immutable.JS gör mycket arbete bakom kulisserna för att optimera prestandan. Detta är nyckeln till dess styrka, eftersom användning av oföränderliga datastrukturer kan innebära mycket dyr kopiering. I synnerhet kan oföränderlig hantering av stora, komplexa datamängder, till exempel ett nästlat Redux-statsträd, generera många mellanliggande kopior av objekt, som förbrukar minne och sänker prestandan när webbläsarens garbage collector kämpar för att städa upp.

Immutable.JS undviker detta genom att på ett smart sätt dela datastrukturer under ytan, vilket minimerar behovet av att kopiera data. Det gör det också möjligt att utföra komplexa kedjor av operationer utan att skapa onödiga (och kostsamma) klonade mellandata som snabbt slängs.

Det här ser du förstås aldrig – de data du ger till ett Immutable.JS-objekt muteras aldrig. Det är snarare de mellanliggande data som genereras i Immutable.JS från en kedjad sekvens av metodanrop som är fria att muteras. Du får därför alla fördelar med oföränderliga datastrukturer utan (eller med mycket lite) av de potentiella prestandaslagen.

Ytterligare information#

Artiklar

  • Immutable.js, persistenta datastrukturer och strukturell delning
  • PDF: JavaScript Immutability – Don’t go changing

Bibliotek

  • Immutable.js

Vilka är problemen med att använda Immutable.JS?#

Och även om Immutable.JS är kraftfull måste den användas med försiktighet, eftersom den har sina egna problem. Observera dock att alla dessa problem kan övervinnas ganska enkelt med noggrann kodning.

Svårt att samverka med#

JavaScript tillhandahåller inte oföränderliga datastrukturer. För att Immutable.JS ska kunna ge sina oföränderliga garantier måste dina data kapslas in i ett Immutable.JS-objekt (t.ex. ett Map eller ett List, etc.). När de väl är inneslutna på detta sätt är det svårt för dessa data att sedan samverka med andra vanliga JavaScript-objekt.

Du kommer till exempel inte längre att kunna referera till ett objekts egenskaper med hjälp av vanlig JavaScript-punkt- eller parentes-notation. Istället måste du referera till dem via Immutable.JS:s get()– eller getIn()-metoder, som använder en besvärlig syntax som ger åtkomst till egenskaper via en array av strängar som var och en representerar en egenskapsnyckel.

Istället för myObj.prop1.prop2.prop3 skulle du t.ex. använda myImmutableMap.getIn().

Detta gör det besvärligt att samverka inte bara med din egen kod utan även med andra bibliotek, till exempel lodash eller ramda, som förväntar sig vanliga JavaScript-objekt.

Notera att Immutable.JS-objekten har en toJS()-metod som returnerar data som en vanlig JavaScript-datastruktur, men den här metoden är extremt långsam och om du använder den i stor utsträckning kommer du att förneka de prestandafördelar som Immutable.JS ger

När Immutable.JS väl har använts kommer Immutable.JS att spridas i hela din kodbas#

När du har kapslat in dina data med Immutable.JS måste du använda Immutable.JS:s get() eller getIn() egenskapsaccessorer för att få tillgång till dem.

Detta har effekten att Immutable.JS sprids över hela din kodbas, inklusive potentiellt dina komponenter, där du kanske föredrar att inte ha sådana externa beroenden. Hela din kodbas måste veta vad som är och vad som inte är ett Immutable.JS-objekt. Det gör det också svårt att ta bort Immutable.JS från din app i framtiden, om du någonsin skulle behöva göra det.

Detta problem kan undvikas genom att frikoppla din programlogik från dina datastrukturer, enligt vad som beskrivs i avsnittet om bästa praxis nedan.

Inga destruktions- eller spridningsoperatorer#

Då du måste komma åt dina data via Immutable.JS egna get() och getIn() metoder kan du inte längre använda JavaScript:s destruktionsoperator (eller den föreslagna Object spread-operatorn), vilket gör din kod mer spretig.

Inte lämplig för små värden som ändras ofta#

Immutable.JS fungerar bäst för samlingar av data, och ju större desto bättre. Det kan vara långsamt när dina data består av många små, enkla JavaScript-objekt, där varje objekt består av några nycklar med primitiva värden.

Bemärk dock att detta inte gäller Redux tillståndsträd, som (vanligtvis) representeras som en stor samling data.

Svårt att felsöka#

Immutable.JS-objekt, såsom Map, List, osv, kan vara svåra att felsöka, eftersom inspektion av ett sådant objekt avslöjar en hel hierarki av Immutable.JS-specifika egenskaper som du inte bryr dig om, medan dina faktiska data som du bryr dig om är inkapslade flera lager djupt ner.

För att lösa det här problemet kan du använda ett webbläsartillägg, till exempel Immutable.js Object Formatter, som visar dina data i Chrome Dev Tools och döljer Immutable.JS:s egenskaper när du inspekterar dina data.

Bryter objektreferenser och orsakar dålig prestanda#

En av de viktigaste fördelarna med oföränderlighet är att det möjliggör ytlig jämlikhetskontroll, vilket dramatiskt förbättrar prestandan.

Om två olika variabler refererar till samma oföränderliga objekt räcker det med en enkel jämlikhetskontroll av de två variablerna för att avgöra att de är likvärdiga och att objektet de båda refererar till är oförändrat. Jämlikhetskontrollen behöver aldrig kontrollera värdena för någon av objektets egenskaper, eftersom det naturligtvis är oföränderligt.

Den ytliga kontrollen fungerar dock inte om dina data som är inkapslade i ett Immutable.JS-objekt i sig är ett objekt. Detta beror på att Immutable.JS toJS()-metoden, som returnerar de data som finns i ett Immutable.JS-objekt som ett JavaScript-värde, kommer att skapa ett nytt objekt varje gång den anropas och därmed bryta referensen med de inkapslade data.

Om du till exempel anropar toJS() två gånger och tilldelar resultatet till två olika variabler kommer en jämlikhetskontroll av de två variablerna att misslyckas, även om objektvärdena i sig inte har ändrats.

Det här är ett särskilt problem om du använder toJS() i en inplastad komponents mapStateToProps-funktion, eftersom React-Redux ytligt jämför varje värde i det returnerade props-objektet. Det värde som refereras av todos-prop som returneras från mapStateToProps nedan kommer till exempel alltid att vara ett annat objekt och kommer därför att misslyckas med en ytlig jämlikhetskontroll.

// AVOID .toJS() i mapStateToProps
function mapStateToProps(state) {
return {
todos: state.get(’todos’).toJS() // Alltid ett nytt objekt
}
}

Copy

När den ytliga kontrollen misslyckas kommer React-Redux att få komponenten att återskapas. Om du använder toJS() i mapStateToProps på det här sättet kommer komponenten därför alltid att göras om, även om värdet aldrig ändras, vilket påverkar prestandan kraftigt.

Detta kan förhindras genom att använda toJS() i en komponent av högre ordning, vilket diskuteras i avsnittet Best Practices nedan.

Ytterligare information#

Artiklar

  • Immutable.js, persistenta datastrukturer och strukturell delning
  • Immutabla datastrukturer och JavaScript
  • React.js pure render performance anti-pattern
  • Building Efficient UI with React and Redux

Chrome Extension

  • Immutable Object Formatter

Är det värt besväret att använda Immutable.JS?#

Fler gånger, ja. Det finns olika kompromisser och åsikter att ta hänsyn till, men det finns många goda skäl att använda Immutable.JS. Underskatta inte svårigheten att försöka spåra en egenskap i ditt tillståndsträd som oavsiktligt har muterats.

Komponenter kommer både att renderas på nytt när de inte ska göra det och vägra att rendera när de ska göra det, och det är svårt att spåra felet som orsakar renderingsproblemet, eftersom komponenten som renderas felaktigt inte nödvändigtvis är den vars egenskaper muteras oavsiktligt.

Det här problemet orsakas främst av att man återvänder ett muterat tillståndsobjekt från en Redux Reducer. Med Immutable.JS existerar det här problemet helt enkelt inte, vilket innebär att du tar bort en hel klass av buggar från din app.

Detta, tillsammans med dess prestanda och rika API för datamanipulering, är anledningen till att Immutable.JS är värt ansträngningen.

Ytterligare information#

Dokumentation

  • Felsökning:

    Immutable.JS kan ge betydande tillförlitlighet och prestandaförbättringar för din app, men det måste användas på rätt sätt. Om du väljer att använda Immutable.JS (och kom ihåg att du inte är tvungen att göra det och att det finns andra immutabla bibliotek som du kan använda), följ de här bästa metoderna, så kan du få ut det mesta av det utan att snubbla över de problem som det potentiellt kan orsaka.

    Blanda aldrig vanliga JavaScript-objekt med Immutable.JS#

    Låt aldrig ett vanligt JavaScript-objekt innehålla Immutable.JS-egenskaper. Låt heller aldrig ett Immutable.JS-objekt innehålla ett vanligt JavaScript-objekt.

    Ytterligare information#

    Artiklar

    • Immutabla datastrukturer och JavaScript

    Gör hela Redux-tillståndsträdet till ett Immutable.JS-objekt#

    För en Redux-app bör hela tillståndsträdet vara ett Immutable.JS-objekt, utan att några vanliga JavaScript-objekt används alls.

    • Skapa trädet med hjälp av Immutable.JS:s fromJS() funktion.

    • Använd ett Immutable.JS-anpassad version av combineReducers-funktionen, t.ex. den i redux-immutable, eftersom Redux själv förväntar sig att tillståndsträdet är ett vanligt JavaScript-objekt.

    • När du lägger till JavaScript-objekt till en Immutable.JS-karta eller lista med Immutable.JS’s update, merge eller set metoder, se till att objektet som läggs till först konverteras till ett Immutable-objekt med hjälp av fromJS().

    Exempel

    // undvik
    const newObj = { key: value }
    const newState = state.setIn(, newObj)
    // newObj har lagts till som ett vanligt JavaScript-objekt, INTE som en Immutable.JS Map
    // rekommenderat
    const newObj = { key: value }
    const newState = state.setIn(, fromJS(newObj))
    // newObj är nu en Immutable.JS Map

    Kopiera

    Ytterligare information#

    Artiklar

    • Immutabla datastrukturer och JavaScript

    Bibliotek

    • redux-immutable

    Använd Immutable.JS överallt utom i dina dumma komponenter#

    Använd Immutable.JS överallt för att hålla din kod performant. Använd den i dina smarta komponenter, dina selektorer, dina sagor eller thunks, action creators och särskilt dina reducers.

    Använd dock inte Immutable.JS i dina dumma komponenter.

    Ytterligare information#

    Artiklar

    • Immutable Data Structures and JavaScript
    • Smart and Dumb Components in React

    Begränsa din användning av toJS()#

    toJS() är en dyr funktion och förnekar syftet med att använda Immutable.JS. Undvik att använda den.

    Ytterligare information#

    Diskussioner

    • Lee Byron på Twitter: ”Perf tip for #immutablejs…”

    Dina selektorer bör returnera Immutable.JS-objekt#

    Always. Denna praxis har flera fördelar:

    • Det undviker onödiga omkörningar som orsakas av att kalla .toJS() i selektorer (eftersom .toJS() alltid returnerar ett nytt objekt).
      • Det är möjligt att memoozera selektorer där du kallar .toJS(), men det är överflödigt när det räcker med att bara returnera Immutable.js-objekt utan memoozering.
    • Det etablerar ett konsekvent gränssnitt för selektorer; du behöver inte hålla reda på om ett Immutable.js objekt eller ett vanligt JavaScript objekt kommer att returneras.

    Använd Immutable.JS objekt i dina smarta komponenter#

    Smarta komponenter som får tillgång till lagret via React Redux connect funktion måste använda Immutable.JS värden som returneras av dina selektorer. Se till att du undviker de potentiella problem som detta kan orsaka med onödig komponentåtergivning. Memotisera dina selektorer med hjälp av ett bibliotek som reselect om det behövs.

    Ytterligare information#

    Dokumentation

    • Recept: Beräkning av härledda data
    • FAQ: Immutable Data
    • Reselect Documentation: Hur använder jag Reselect med Immutable.js?

    Artiklar

    • Redux-mönster och anti-mönster

    Bibliotek

    • Reselect: Selector library for Redux

    Använd aldrig toJS() i mapStateToProps#

    Omvandling av ett Immutable.JS-objekt till ett JavaScript-objekt med hjälp av toJS() ger ett nytt objekt varje gång. Om du gör detta i mapStateToProps kommer du att få komponenten att tro att objektet har ändrats varje gång tillståndsträdet ändras och på så sätt utlösa en onödig återgivning.

    Ytterligare information#

    Dokumentation

    • FAQ: Immutable Data

    Använd aldrig Immutable.JS i dina dumma komponenter#

    Dina dumma komponenter bör vara rena, det vill säga de bör producera samma resultat med samma indata och inte ha några externa beroenden. Om du ger en sådan komponent ett Immutable.JS-objekt som prop, gör du den beroende av Immutable.JS för att få ut prop-värdet och på annat sätt manipulera det.

    Ett sådant beroende gör komponenten oren, försvårar testningen av komponenten och gör det onödigt svårt att återanvända och refaktorisera komponenten.

    Ytterligare information#

    Artiklar

    • Immutabla datastrukturer och JavaScript
    • Smart and Dumb Components in React
    • Tips for a Better Redux Architecture: Lektioner för företagsskala

    Använd en komponent av högre ordning för att konvertera din smarta komponents Immutable.JS-props till din dumma komponents JavaScript-props#

    Något måste mappa Immutable.JS-propsen i din smarta komponent till de rena JavaScript-propsen som används i din dumma komponent. Detta är en Higher Order Component (HOC) som helt enkelt tar Immutable.JS-props från din Smart Component och omvandlar dem med hjälp av toJS() till rena JavaScript-props, som sedan skickas till din Dumb Component.

    Ett exempel på en sådan HOC följer nedan. En liknande HOC finns tillgänglig som ett NPM-paket för din bekvämlighet: with-immutable-props-to-js.

    import React from ’react’
    import { Iterable } from ’immutable’
    export const toJS = WrappedComponent => wrappedComponentProps => {
    const KEY = 0
    const VALUE = 1
    const propsJS = Object.entries(wrappedComponentProps).reduce(
    (newProps, wrappedComponentProp) => {
    newProps] = Iterable.isIterable(
    wrappedComponentProp
    )
    ? wrappedComponentProp.toJS()
    : wrappedComponentProp
    return newProps
    },
    {}
    )
    return <WrappedComponent {…propsJS} />
    }

    Kopiera

    Och så här skulle du använda den i din smarta komponent:

    import { connect } from ’react-redux’
    import { toJS } from ’./to-js’
    import DumbComponent from ’./dumb.component’
    const mapStateToProps = state => {
    return {
    // obj är ett Immutable-objekt i Smart Component, men det konverteras till ett vanligt
    // JavaScript-objekt av toJS och skickas därför till DumbComponent som ett rent JavaScript-
    // objekt. Eftersom det fortfarande är ett Immutable.JS-objekt här i mapStateToProps,
    // finns det dock inga problem med felaktiga återgivningar.
    obj: getImmutableObjectFromStateTree(state)
    }
    }
    export default connect(mapStateToProps)(toJS(DumbComponent))

    Kopiera

    Genom att konvertera Immutable.JS-objekt till vanliga JavaScript-värden inom en HOC uppnår vi portabilitet för Dumb Component, men utan de prestandaskillnader som användningen av toJS() medför i den smarta komponenten.

    Anm.: Om din app kräver hög prestanda kan du behöva undvika toJS() helt och hållet, och måste därför använda Immutable.JS i dina dumb components. För de flesta appar kommer detta dock inte att vara fallet, och fördelarna med att hålla Immutable.JS borta från dina dumma komponenter (underhållbarhet, portabilitet och enklare testning) kommer vida att väga tyngre än eventuella upplevda prestandaförbättringar av att ha den i.

    Därtill kommer att användningen av toJS i en komponent av högre ordning inte bör ge upphov till någon större, om ens någon, prestandaförlust, eftersom komponenten endast anropas när den anslutna komponentens rekvisita ändras. Som med alla prestandafrågor bör du först utföra prestandakontroller innan du bestämmer dig för vad du ska optimera.

    Ytterligare information#

    Dokumentation

    • React: Higher-Order Components

    Artiklar

    • React Higher Order Components in depth

    Discussions

    • Reddit: acemarke och cpsubrian kommenterar Dan Abramov: Redux är inte en arkitektur eller ett designmönster, det är bara ett bibliotek.

    Gists

    • cpsubrian: React decorators for redux/react-router/immutable ’smart’ components

    Använd Immutable Object Formatter Chrome Extension för att underlätta felsökning#

    Installera Immutable Object Formatter , och inspektera dina Immutable.JS-data utan att se bruset från Immutable.JS:s egna objektegenskaper.

Lämna ett svar

Din e-postadress kommer inte publiceras.