|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903 |
- <!doctype html><!-- This is a valid HTML5 document. -->
- <!-- Screen readers, SEO, extensions and so on. -->
- <html lang=fr>
- <!-- Has to be within the first 1024 bytes, hence before the <title>
- See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
- <meta charset=utf-8>
- <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
- <!-- The viewport meta is quite crowded and we are responsible for that.
- See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
- <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
- <!-- Required to make a valid HTML5 document. -->
- <title>JavaScript rĂ©duit â David Larlet</title>
- <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
- <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
- <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
- <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
- <link rel="manifest" href="/manifest.json">
- <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
- <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
- <meta name="apple-mobile-web-app-title" content="David Larlet">
- <meta name="application-name" content="David Larlet">
- <meta name="msapplication-TileColor" content="#da532c">
- <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
- <meta name="theme-color" content="#f0f0ea">
- <!-- That good ol' feed, subscribe :p. -->
- <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
-
- <!-- Canonical URL for SEO purposes -->
- <link rel="canonical" href="https://larlet.fr/david/blog/2016/javascript-reduit/">
- <!-- SEO/Semantic metadata -->
- <meta name="description" content="Tout ce quâon rend tĂ©lĂ©chargeable au dĂ©veloppeur, on lâempĂȘche de lâinventer ou de le dĂ©couvrir." />
- <meta name="twitter:description" property="og:description" itemprop="description" content="Tout ce quâon rend tĂ©lĂ©chargeable au dĂ©veloppeur, on lâempĂȘche de lâinventer ou de le dĂ©couvrir." />
- <meta name="twitter:title" property="og:title" itemprop="name" content="JavaScript réduit" />
- <meta name="twitter:card" content="summary" />
- <meta name="twitter:creator" content="@davidbgk" />
- <meta name="twitter:url" property="og:url" content="https://larlet.fr/david/blog/2016/javascript-reduit/" />
- <meta property="og:type" content="article" />
- <meta property="og:site_name" content="David Larlet (@davidbgk)" />
-
- <meta name="twitter:image" property="og:image" itemprop="image" content="https://larlet.fr/static/david/blog/2016/javascript-reduit.jpg" />
-
-
- <style>
- /* http://meyerweb.com/eric/tools/css/reset/ */
- html, body, div, span,
- h1, h2, h3, h4, h5, h6, p, blockquote, pre,
- a, abbr, address, big, cite, code,
- del, dfn, em, img, ins,
- small, strike, strong, tt, var,
- dl, dt, dd, ol, ul, li,
- fieldset, form, label, legend,
- table, caption, tbody, tfoot, thead, tr, th, td,
- article, aside, canvas, details, embed,
- figure, figcaption, footer, header, hgroup,
- menu, nav, output, ruby, section, summary,
- time, mark, audio, video {
- margin: 0;
- padding: 0;
- border: 0;
- font-size: 100%;
- font: inherit;
- vertical-align: baseline;
- }
- /* HTML5 display-role reset for older browsers */
- article, aside, details, figcaption, figure,
- footer, header, hgroup, menu, nav, section { display: block; }
- body { line-height: 1; }
- blockquote, q { quotes: none; }
- blockquote:before, blockquote:after,
- q:before, q:after {
- content: '';
- content: none;
- }
- table {
- border-collapse: collapse;
- border-spacing: 0;
- }
-
- /* http://practicaltypography.com/equity.html */
- /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
- /* https://www.filamentgroup.com/lab/js-web-fonts.html */
- @font-face {
- font-family: 'EquityTextB';
- src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
- url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
- font-weight: 300;
- font-style: normal;
- font-display: swap;
- }
- @font-face {
- font-family: 'EquityTextB';
- src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
- url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
- font-weight: 300;
- font-style: italic;
- font-display: swap;
- }
- @font-face {
- font-family: 'EquityTextB';
- src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
- url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
- font-weight: 700;
- font-style: normal;
- font-display: swap;
- }
-
- @font-face {
- font-family: 'ConcourseT3';
- src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
- url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
- font-weight: 300;
- font-style: normal;
- font-display: swap;
- }
-
-
- /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
- body {
- /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
- font-family: "EquityTextB", Palatino, serif;
- background-color: #f0f0ea;
- color: #07486c;
- font-kerning: normal;
- -moz-osx-font-smoothing: grayscale;
- -webkit-font-smoothing: subpixel-antialiased;
- text-rendering: optimizeLegibility;
- font-variant-ligatures: common-ligatures contextual;
- font-feature-settings: "kern", "liga", "clig", "calt";
- }
- pre, code, kbd, samp, var, tt {
- font-family: 'TriplicateT4c', monospace;
- }
- em {
- font-style: italic;
- color: #323a45;
- }
- strong {
- font-weight: bold;
- color: black;
- }
- nav {
- background-color: #323a45;
- color: #f0f0ea;
- display: flex;
- justify-content: space-around;
- padding: 1rem .5rem;
- }
- nav:last-child {
- border-bottom: 1vh solid #2d7474;
- }
- nav a {
- color: #f0f0ea;
- }
- nav abbr {
- border-bottom: 1px dotted white;
- }
-
- h1 {
- border-top: 1vh solid #2d7474;
- border-bottom: .2vh dotted #2d7474;
- background-color: #e3e1e1;
- color: #323a45;
- text-align: center;
- padding: 5rem 0 4rem 0;
- width: 100%;
- font-family: 'ConcourseT3';
- display: flex;
- flex-direction: column;
- }
- h1.single {
- padding-bottom: 10rem;
- }
- h1 span {
- position: absolute;
- top: 1vh;
- left: 20%;
- line-height: 0;
- }
- h1 span a {
- line-height: 1.7;
- padding: 1rem 1.2rem .6rem 1.2rem;
- border-radius: 0 0 6% 6%;
- background: #2d7474;
- font-size: 1.3rem;
- color: white;
- text-decoration: none;
- }
- h2 {
- margin: 4rem 0 1rem;
- border-top: .2vh solid #2d7474;
- padding-top: 1vh;
- }
- h3 {
- text-align: center;
- margin: 3rem 0 .75em;
- }
- hr {
- height: .4rem;
- width: .4rem;
- border-radius: .4rem;
- background: #07486c;
- margin: 2.5rem auto;
- }
- time {
- display: bloc;
- margin-left: 0 !important;
- }
- ul, ol {
- margin: 2rem;
- }
- ul {
- list-style-type: square;
- }
- a {
- text-decoration-skip-ink: auto;
- text-decoration-thickness: 0.05em;
- text-underline-offset: 0.09em;
- }
- article {
- max-width: 50rem;
- display: flex;
- flex-direction: column;
- margin: 2rem auto;
- }
- article.single {
- border-top: .2vh dotted #2d7474;
- margin: -6rem auto 1rem auto;
- background: #f0f0ea;
- padding: 2rem;
- }
- article p:last-child {
- margin-bottom: 1rem;
- }
- p {
- padding: 0 .5rem;
- margin-left: 3rem;
- }
- p + p,
- figure + p {
- margin-top: 2rem;
- }
-
- blockquote {
- background-color: #e3e1e1;
- border-left: .5vw solid #2d7474;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 1rem;
- margin: 1.5rem;
- }
- blockquote cite {
- font-style: italic;
- }
- blockquote p {
- margin-left: 0;
- }
-
- figure {
- border-top: .2vh solid #2d7474;
- background-color: #e3e1e1;
- text-align: center;
- padding: 1.5rem 0;
- margin: 1rem 0 0;
- font-size: 1.5rem;
- width: 100%;
- }
- figure img {
- max-width: 250px;
- max-height: 250px;
- border: .5vw solid #323a45;
- padding: 1px;
- }
- figcaption {
- padding: 1rem;
- line-height: 1.4;
- }
- aside {
- display: flex;
- flex-direction: column;
- background-color: #e3e1e1;
- padding: 1rem 0;
- border-bottom: .2vh solid #07486c;
- }
- aside p {
- max-width: 50rem;
- margin: 0 auto;
- }
-
- /* https://fvsch.com/code/css-locks/ */
- p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
- font-size: 1rem;
- line-height: calc( 1.5em + 0.2 * 1rem );
- }
- h1 {
- font-size: 1.9rem;
- line-height: calc( 1.2em + 0.2 * 1rem );
- }
- h2 {
- font-size: 1.6rem;
- line-height: calc( 1.3em + 0.2 * 1rem );
- }
- h3 {
- font-size: 1.35rem;
- line-height: calc( 1.4em + 0.2 * 1rem );
- }
- @media (min-width: 20em) {
- /* The (100vw - 20rem) / (50 - 20) part
- resolves to 0-1rem, depending on the
- viewport width (between 20em and 50em). */
- p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
- font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
- margin-left: 0;
- }
- h1 {
- font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
- }
- h2 {
- font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
- }
- h3 {
- font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
- }
- }
- @media (min-width: 50em) {
- /* The right part of the addition *must* be a
- rem value. In this example we *could* change
- the whole declaration to font-size:2.5rem,
- but if our baseline value was not expressed
- in rem we would have to use calc. */
- p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
- font-size: calc( 1rem + .6 * 1rem );
- line-height: 1.5em;
- }
- p, li, pre, details {
- margin-left: 3rem;
- }
- h1 {
- font-size: calc( 1.9rem + 1.5 * 1rem );
- line-height: 1.2em;
- }
- h2 {
- font-size: calc( 1.5rem + 1.5 * 1rem );
- line-height: 1.3em;
- }
- h3 {
- font-size: calc( 1.35rem + 1.5 * 1rem );
- line-height: 1.4em;
- }
- figure img {
- max-width: 500px;
- max-height: 500px;
- }
- }
-
- figure.unsquared {
- margin-bottom: 1.5rem;
- }
- figure.unsquared img {
- height: inherit;
- }
-
-
-
- @media print {
- body { font-size: 100%; }
- a:after { content: " (" attr(href) ")"; }
- a, a:link, a:visited, a:after {
- text-decoration: underline;
- text-shadow: none !important;
- background-image: none !important;
- background: white;
- color: black;
- }
- abbr[title] { border-bottom: 0; }
- abbr[title]:after { content: " (" attr(title) ")"; }
- img { page-break-inside: avoid; }
- @page { margin: 2cm .5cm; }
- h1, h2, h3 { page-break-after: avoid; }
- p3 { orphans: 3; widows: 3; }
- img {
- max-width: 250px !important;
- max-height: 250px !important;
- }
- nav, aside { display: none; }
- }
-
- ul.with_columns {
- column-count: 1;
- }
- @media (min-width: 20em) {
- ul.with_columns {
- column-count: 2;
- }
- }
- @media (min-width: 50em) {
- ul.with_columns {
- column-count: 3;
- }
- }
- ul.with_two_columns {
- column-count: 1;
- }
- @media (min-width: 20em) {
- ul.with_two_columns {
- column-count: 1;
- }
- }
- @media (min-width: 50em) {
- ul.with_two_columns {
- column-count: 2;
- }
- }
-
- .gallery {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-around;
- }
- .gallery figure img {
- margin-left: 1rem;
- margin-right: 1rem;
- }
- .gallery figure figcaption {
- font-family: 'ConcourseT3'
- }
-
- footer {
- font-family: 'ConcourseT3';
- display: flex;
- flex-direction: column;
- border-top: 3px solid white;
- padding: 4rem 0;
- background-color: #07486c;
- color: white;
- }
- footer > * {
- max-width: 50rem;
- margin: 0 auto;
- }
- footer a {
- color: #f1c40f;
- }
- footer .avatar {
- width: 200px;
- height: 200px;
- border-radius: 50%;
- float: left;
- -webkit-shape-outside: circle();
- shape-outside: circle();
- margin-right: 2rem;
- padding: 2px 5px 5px 2px;
- background: white;
- border-left: 1px solid #f1c40f;
- border-top: 1px solid #f1c40f;
- border-right: 5px solid #f1c40f;
- border-bottom: 5px solid #f1c40f;
- }
- </style>
-
- <h1 class="single">
- <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
- JavaScript réduit
- <time>Publié le 3 mars 2016</time>
- </h1>
- <article class="single">
- <blockquote>
- <p>The JavaScript ecosystem is thriving and moving quickly, but thereâs finally an end at the light of the tunnel. Best practices are no longer changing constantly, and it is becoming increasingly clear which tools are worth learning.</p>
- <p>The most important thing to remember is to keep it simple and only use what you need.</p>
- <p><cite><em><a href="https://medium.com/@fward/state-of-the-art-javascript-in-2016-ab67fc68eb0b">State of the Art JavaScript in 2016</a></em>Â (<a href="/david/cache/93a8d8a7ec4108b541cbae05021dde66/">cache</a>)</cite></p>
- </blockquote>
- <p>Il y a une certaine ironie Ă arriver Ă cette conclusion aprĂšs avoir citĂ© des lignes et des lignes dâoutils :-). Pour faire suite Ă ma recherche <a href="/david/blog/2016/minimalisme-esthetique/">de minimalisme et dâesthĂ©tique</a>, jâai commencĂ© <a href="https://github.com/davidbgk/justdata">un projet un peu plus consĂ©quent</a> pour voir jusquâoĂč je pouvais aller. Jâai rapidement eu besoin de gĂ©rer des donnĂ©es au niveau du <em>frontend</em> de maniĂšre un peu plus sĂ©rieuse et je me suis penchĂ© sur le <em>pattern</em> remis au goĂ»t du jour par <a href="http://facebook.github.io/flux/docs/overview.html">Flux</a>. Plus particuliĂšrement <a href="http://redux.js.org/docs/introduction/ThreePrinciples.html">les principes qui sont utilisĂ©s dans Redux</a> avec un Ă©tat global mais en lecture seule qui nâest modifiable que par des <em>reducers</em>. Pour bien comprendre les concepts sous-jacents, je ne voulais pas utiliser Redux mais le dĂ©couvrir en lâimplĂ©mentant et rĂ©duire mes dĂ©pendances. Je suis parti <a href="https://gist.github.com/bloodyowl/babd93183ff8581724fe">dâun gist</a> pour avoir la notion de <code>Store</code> :</p>
- <div class="codehilite"><pre><span class="kr">const</span> <span class="nx">createStore</span> <span class="o">=</span> <span class="p">(</span><span class="nx">reducer</span><span class="p">,</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">reducer</span><span class="p">(</span><span class="kc">undefined</span><span class="p">,</span> <span class="p">{}))</span> <span class="o">=></span> <span class="p">{</span>
- <span class="kr">const</span> <span class="nx">subscribers</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Set</span><span class="p">()</span>
- <span class="k">return</span> <span class="p">{</span>
- <span class="nx">dispatch</span><span class="o">:</span> <span class="p">(</span><span class="nx">action</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="nx">state</span> <span class="o">=</span> <span class="nx">reducer</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span>
- <span class="nx">subscribers</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">func</span> <span class="o">=></span> <span class="nx">func</span><span class="p">(</span><span class="nx">action</span><span class="p">))</span>
- <span class="p">},</span>
- <span class="nx">subscribe</span><span class="o">:</span> <span class="p">(</span><span class="nx">func</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="nx">subscribers</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">func</span><span class="p">)</span>
- <span class="k">return</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">subscribers</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">func</span><span class="p">)</span>
- <span class="p">},</span>
- <span class="nx">getState</span><span class="o">:</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">state</span>
- <span class="p">}</span>
- <span class="p">}</span>
- </pre></div>
-
-
- <p>On a ici trois mĂ©thodes qui permettent dâexpĂ©dier des actions, dâabonner des fonctions et de rĂ©cupĂ©rer lâĂ©tat en cours (Ă partir de maintenant je passe aux termes techniques en anglais car ça devient nâimporte quoi). Lorsquâon rĂ©cupĂšre lâĂ©tat de notre <code>Store</code>, on a toutes les donnĂ©es dâun coup que lâon peut explorer, jâavais dĂ©jĂ parlĂ© de lâimportance de pouvoir avoir cette vision avec TinyDB cĂŽtĂ© serveur pour dĂ©velopper efficacement. <strong>En tant que dĂ©veloppeurs web, notre mĂ©tier est de savoir quelles donnĂ©es correspondent Ă quelles URLs.</strong> Il y a bien sĂ»r leur reprĂ©sentation finale et la suite de sĂ©rialisations et dĂ©serialisations quâelles subissent mais pouvoir ĂȘtre sĂ»r des donnĂ©es et des URLs câest dĂ©jĂ un prĂ©-requis rarement atteint. On va maintenant passer aux <code>Reducers</code> qui permettent de transformer lâĂ©tat global avec des fonctions pures (= qui retournent de nouveaux objets). Ici la syntaxe est gĂ©nĂ©ralement de gĂ©rer les nombreux cas avec un <code>switch</code> assez laid, un petit constructeur va nous aider Ă rendre ça plus esthĂ©tique :</p>
- <div class="codehilite"><pre><span class="kr">const</span> <span class="nx">createReducer</span> <span class="o">=</span> <span class="p">(</span><span class="nx">initialState</span><span class="p">,</span> <span class="nx">handlers</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="k">return</span> <span class="p">(</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">initialState</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="k">if</span> <span class="p">(</span><span class="nx">handlers</span><span class="p">.</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">))</span> <span class="p">{</span>
- <span class="k">return</span> <span class="nx">handlers</span><span class="p">[</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">](</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span>
- <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
- <span class="k">return</span> <span class="nx">state</span>
- <span class="p">}</span>
- <span class="p">}</span>
- <span class="p">}</span>
- </pre></div>
-
-
- <p>Par dĂ©faut câest lâĂ©tat en cours qui est retournĂ© si lâaction nâest pas trouvĂ©e. Il nous reste Ă avoir un <a href="http://redux.js.org/docs/recipes/ReducingBoilerplate.html">crĂ©ateur dâactions</a> pour ne pas avoir Ă taper Ă la main la <a href="https://github.com/acdlite/flux-standard-action">logique standardisĂ©e</a> pour chacune dâentre elles :</p>
- <div class="codehilite"><pre><span class="kr">const</span> <span class="nx">createAction</span> <span class="o">=</span> <span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="p">...</span><span class="nx">argNames</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="k">return</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="kd">let</span> <span class="nx">action</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">type</span><span class="p">,</span> <span class="nx">payload</span><span class="o">:</span> <span class="p">{}</span> <span class="p">}</span>
- <span class="nx">argNames</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">arg</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="p">[</span><span class="nx">argNames</span><span class="p">[</span><span class="nx">index</span><span class="p">]]</span> <span class="o">=</span> <span class="nx">args</span><span class="p">[</span><span class="nx">index</span><span class="p">]</span>
- <span class="p">})</span>
- <span class="k">return</span> <span class="nx">action</span>
- <span class="p">}</span>
- <span class="p">}</span>
- </pre></div>
-
-
- <p>Et voilĂ Â ! <strong>En 35 lignes on obtient 80% dâutilitĂ© dâune bibliothĂšque et en plus on lâa comprise.</strong> Il sâagit de mon budget lorsque je passe une soirĂ©e lĂ -dessus, comme jâai pu le faire <a href="/david/blog/2016/programmation-apprehension/">pour jQuery</a>.</p>
- <p>Il sâagit maintenant de le mettre en pratique. ConsidĂ©rons un compteur et une liste, on va avoir les actions pour ajouter et enlever une unitĂ© au compteur ainsi que lâajout dâitems Ă la liste (de courses). On commence par dĂ©finir le nom de nos actions avec des constantes car câest une bonne pratique et on en profite pour crĂ©er nos <code>reducers</code> :</p>
- <div class="codehilite"><pre><span class="kr">const</span> <span class="nx">INCREMENT</span> <span class="o">=</span> <span class="s1">'INCREMENT'</span>
- <span class="kr">const</span> <span class="nx">DECREMENT</span> <span class="o">=</span> <span class="s1">'DECREMENT'</span>
- <span class="kr">const</span> <span class="nx">ADD_ITEM</span> <span class="o">=</span> <span class="s1">'ADD_ITEM'</span>
-
- <span class="kr">const</span> <span class="nx">counter</span> <span class="o">=</span> <span class="nx">createReducer</span><span class="p">({</span> <span class="nx">counter</span><span class="o">:</span> <span class="mi">0</span> <span class="p">},</span> <span class="p">{</span>
- <span class="p">[</span><span class="nx">INCREMENT</span><span class="p">](</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">return</span> <span class="p">{</span> <span class="nx">counter</span><span class="o">:</span> <span class="nx">state</span><span class="p">.</span><span class="nx">counter</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">}</span>
- <span class="p">},</span>
- <span class="p">[</span><span class="nx">DECREMENT</span><span class="p">](</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">return</span> <span class="p">{</span> <span class="nx">counter</span><span class="o">:</span> <span class="nx">state</span><span class="p">.</span><span class="nx">counter</span> <span class="o">-</span> <span class="mi">1</span> <span class="p">}</span>
- <span class="p">}</span>
- <span class="p">})</span>
-
- <span class="kr">const</span> <span class="nx">shoppingList</span> <span class="o">=</span> <span class="nx">createReducer</span><span class="p">([],</span> <span class="p">{</span>
- <span class="p">[</span><span class="nx">ADD_ITEM</span><span class="p">](</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">{</span>
- <span class="k">return</span> <span class="p">[</span> <span class="p">...</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="p">.</span><span class="nx">item</span> <span class="p">]</span>
- <span class="p">}</span>
- <span class="p">})</span>
- </pre></div>
-
-
- <p>Il faut bien faire attention Ă cette Ă©tape Ă bien retourner un nouvel Ă©tat et non Ă modifier lâĂ©tat courant sinon vous perdez complĂštement les avantages liĂ©s. LâĂ©tape suivante est de crĂ©er nos actions liĂ©es Ă ce que lâon vient de crĂ©er :</p>
- <div class="codehilite"><pre><span class="kr">const</span> <span class="nx">increment</span> <span class="o">=</span> <span class="nx">createAction</span><span class="p">(</span><span class="nx">INCREMENT</span><span class="p">)</span>
- <span class="kr">const</span> <span class="nx">decrement</span> <span class="o">=</span> <span class="nx">createAction</span><span class="p">(</span><span class="nx">DECREMENT</span><span class="p">)</span>
- <span class="kr">const</span> <span class="nx">addItem</span> <span class="o">=</span> <span class="nx">createAction</span><span class="p">(</span><span class="nx">ADD_ITEM</span><span class="p">,</span> <span class="s1">'item'</span><span class="p">)</span>
- </pre></div>
-
-
- <p>Ici on reste simple, seule la derniĂšre function a un paramĂštre pour lâexemple. Il ne nous reste plus quâĂ crĂ©er notre <code>store</code> Ă lâaide de la mĂ©thode prĂ©cĂ©demment dĂ©finie :</p>
- <div class="codehilite"><pre><span class="kr">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">createStore</span><span class="p">((</span><span class="nx">state</span> <span class="o">=</span> <span class="p">{},</span> <span class="nx">action</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="k">return</span> <span class="p">{</span>
- <span class="nx">counter</span><span class="o">:</span> <span class="nx">counter</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">counter</span><span class="p">,</span> <span class="nx">action</span><span class="p">),</span>
- <span class="nx">shoppingList</span><span class="o">:</span> <span class="nx">shoppingList</span><span class="p">(</span><span class="nx">state</span><span class="p">.</span><span class="nx">shoppingList</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span>
- <span class="p">}</span>
- <span class="p">})</span>
- </pre></div>
-
-
- <p>Il y aurait la possibilitĂ© de <a href="http://redux.js.org/docs/api/combineReducers.html">combiner les reducers</a> mais je prĂ©fĂšre rester explicite sur ce quâil se passe dans mon <code>store</code>. Notre Ă©tat va avoir deux clĂ©s (<code>counter</code> et <code>shoppingList</code>) qui restent indĂ©pendantes mais que lâon peut facilement parcourir Ă tout moment. Pour finir un exemple possible dâutilisation pour mieux comprendre :</p>
- <div class="codehilite"><pre><span class="nx">console</span><span class="p">.</span><span class="nx">group</span><span class="p">(</span><span class="s1">'Testing Redux concepts'</span><span class="p">)</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'Begin:'</span><span class="p">,</span> <span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">())</span>
- <span class="kr">const</span> <span class="nx">unsubscribe</span> <span class="o">=</span> <span class="nx">store</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">((</span><span class="nx">action</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'After'</span><span class="p">,</span> <span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">,</span> <span class="s1">'action:'</span><span class="p">)</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">table</span><span class="p">(</span><span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">())</span>
- <span class="p">})</span>
- <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">increment</span><span class="p">())</span>
- <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">increment</span><span class="p">())</span>
- <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">decrement</span><span class="p">())</span>
- <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">increment</span><span class="p">())</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">assert</span><span class="p">(</span><span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">().</span><span class="nx">counter</span><span class="p">.</span><span class="nx">counter</span> <span class="o">==</span> <span class="mi">2</span><span class="p">,</span>
- <span class="s1">'Counter is now equal to 2'</span><span class="p">)</span>
- <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">addItem</span><span class="p">(</span><span class="s1">'Apples'</span><span class="p">))</span>
- <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">addItem</span><span class="p">(</span><span class="s1">'Oranges'</span><span class="p">))</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">assert</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">().</span><span class="nx">shoppingList</span><span class="p">)</span>
- <span class="o">==</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">([</span><span class="s1">'Apples'</span><span class="p">,</span> <span class="s1">'Oranges'</span><span class="p">]),</span>
- <span class="s1">'The shopping list has been filled'</span><span class="p">)</span>
- <span class="nx">unsubscribe</span><span class="p">()</span> <span class="c1">// Stop the logging subscription.</span>
- <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">decrement</span><span class="p">())</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'End:'</span><span class="p">,</span> <span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">())</span>
- <span class="nx">console</span><span class="p">.</span><span class="nx">groupEnd</span><span class="p">()</span>
- </pre></div>
-
-
- <p>La seule subtilitĂ© est dâavoir le <code>subscribe</code> qui retourne la fonction permettant de se dĂ©sinscrire une fois le moment venu. Ici ça ajoute un <code>log</code> dans la console avec lâĂ©tat associĂ© ce qui est pratique en <em>debug</em> (au passage, je dĂ©couvre que lâon peut faire un <code>console.assert</code>). Mais qui sâavĂšre Ă©galement trivial si lâon souhaite <a href="https://gist.github.com/DavidBruant/8b628704eee463dfb40e">le stocker/rĂ©cupĂ©rer localement</a>.</p>
- <p><strong>Tout ce quâon rend tĂ©lĂ©chargeable au dĂ©veloppeur, on lâempĂȘche de lâinventer ou de le dĂ©couvrir.</strong> Pour paraphraser Piaget qui lui-mĂȘme paraphrasait Papert. Prendre le temps de rĂ©acquĂ©rir mes savoirs et mes concepts ne me permet pas dâĂȘtre plus rapide ou meilleur mais je prends assurĂ©ment plus de plaisir Ă les manipuler ensuite. Et <a href="http://www.agmweb.ca/2016-03-02-mozpay/">lorsquâon travaille sur des mandalas</a> (<a href="/david/cache/b33ebd3c4dbb1d572e2102539dd2b215/">cache</a>), câest la seule chose qui reste.</p>
- <p><em><troll>Oui, jâai rĂ©ussi Ă caser « plaisir » dans un billet sur JavaScriptâŠ</em></p>
- </article>
-
-
- <figure class="image" property="schema:image">
- <img src="/static/david/blog/2016/javascript-reduit.jpg" alt="" />
- </figure>
-
-
-
- <nav id="jumpto">
- <p>
- <a rel=prev href="/david/blog/2016/opendata-liens-casses/">â Opendata et liens cassĂ©s</a> | <a href="/david/blog/" title="Retour Ă la liste des expĂ©riences">â</a> | <a rel=next href="/david/blog/2016/donnees-responsabilite/">DonnĂ©es et responsabilitĂ©Â â</a>
- </p>
- </nav>
-
- <footer>
- <div>
- <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
- <p>
- Bonjour/Hi!
- Je suis <a href="/david/" title="Profil public">David Larlet</a>, je vis actuellement Ă MontrĂ©al et jâalimente cet espace depuis 15 ans. <br>
- Si tu as apprĂ©ciĂ© cette lecture, nâhĂ©site pas Ă poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="ExpĂ©riences bienveillantes">rĂ©flexions bimestrielles</a>, la <a href="/david/stream/2019/" title="PensĂ©es (dĂ©s)articulĂ©es">veille hebdomadaire</a> ou en tâabonnant au <a href="/david/log/" title="Sâabonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens câest quoi un flux RSS ?">so 2005</a>).
- </p>
- <p>
- Je mâintĂ©resse Ă la place que je peux avoir dans ce monde. En tant quâhumain, en tant que membre dâune famille et en tant quâassociĂ© dâune coopĂ©rative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutĂŽt la place aux autres">jâen parle</a>.
- </p>
-
- <p>
- Les derniÚres publications hebdomadaires sont :
- </p>
- <ul class="with_columns">
-
- <li>
- <a href="/david/stream/2019/12/31/">Merci</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/12/27/">Intemporels</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/12/24/">Outils</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/12/17/">Origines</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/12/10/">Publier</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/12/03/">En forĂȘt</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/11/26/">Ecocentric</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/11/19/">Se livrer</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/11/12/">DĂ©pendances</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/11/05/">Positif</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/10/29/">Dettes</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/10/22/">PrivilĂšges</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/10/15/">Discrétion</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/10/08/">Désespérance</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/10/01/">Présent</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/09/24/">Manifester</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/09/17/">Arpenter</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/09/10/">Nostalgie</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/09/03/">DĂ©construire</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/08/27/">Documenter</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/08/20/">Frustration</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/08/13/">Holisme</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/08/06/">1%</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/07/30/">Exemplarité</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/07/23/">Timelines</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/07/16/">Ăcoute</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/07/02/">Anxiété</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/06/21/">Ă lier</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/06/14/">Pauvreté</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/06/07/">Amateur</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/05/31/">Pollution</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/05/24/">Apaisement</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/05/10/">Folie</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/05/03/">Sympathie</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/04/12/">PĂ©remption</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/04/05/">DĂ©finitions</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/03/29/">Acceptation</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/03/22/">Dissonance</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/03/15/">Reconnaissance</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/03/08/">Lecture</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/03/01/">Journaux</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/02/22/">Ăcriture</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/02/15/">Kyriarchie</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/02/08/">Mots-serrures</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/02/01/">Sans voie</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/01/25/">Auto-diagnostic</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/01/18/">Agilité</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/01/11/">MĂ©taphores</a>
- </li>
-
- <li>
- <a href="/david/stream/2019/01/04/">Balbutiements</a>
- </li>
-
- </ul>
-
- <p>
- Voici quelques articles choisis :
- <a href="/david/blog/2019/faire-equipe/" title="AccĂ©der Ă lâarticle complet">Faire Ă©quipe</a>,
- <a href="/david/blog/2018/bivouac-automnal/" title="AccĂ©der Ă lâarticle complet">Bivouac automnal</a>,
- <a href="/david/blog/2018/commodite-effondrement/" title="AccĂ©der Ă lâarticle complet">CommoditĂ© et effondrement</a>,
- <a href="/david/blog/2017/donnees-communs/" title="AccĂ©der Ă lâarticle complet">Des donnĂ©es aux communs</a>,
- <a href="/david/blog/2016/accompagner-enfant/" title="AccĂ©der Ă lâarticle complet">Accompagner un enfant</a>,
- <a href="/david/blog/2016/senior-developer/" title="AccĂ©der Ă lâarticle complet">Senior developer</a>,
- <a href="/david/blog/2016/illusion-sociale/" title="AccĂ©der Ă lâarticle complet">Lâillusion sociale</a>,
- <a href="/david/blog/2016/instantane-scopyleft/" title="AccĂ©der Ă lâarticle complet">InstantanĂ© Scopyleft</a>,
- <a href="/david/blog/2016/enseigner-web/" title="AccĂ©der Ă lâarticle complet">Enseigner le Web</a>,
- <a href="/david/blog/2016/simplicite-defaut/" title="AccĂ©der Ă lâarticle complet">SimplicitĂ© par dĂ©faut</a>,
- <a href="/david/blog/2016/minimalisme-esthetique/" title="AccĂ©der Ă lâarticle complet">Minimalisme et esthĂ©tique</a>,
- <a href="/david/blog/2014/un-web-omni-present/" title="AccĂ©der Ă lâarticle complet">Un web omni-prĂ©sent</a>,
- <a href="/david/blog/2014/manifeste-developpeur/" title="AccĂ©der Ă lâarticle complet">Manifeste de dĂ©veloppeur</a>,
- <a href="/david/blog/2013/confort-convivialite/" title="AccĂ©der Ă lâarticle complet">Confort et convivialitĂ©</a>,
- <a href="/david/blog/2013/testament-numerique/" title="AccĂ©der Ă lâarticle complet">Testament numĂ©rique</a>,
- et <a href="/david/blog/" title="AccĂ©der aux archives">bien dâautresâŠ</a>
- </p>
- <p>
- On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">Ă©changer par courriel</a>. Si Ă©ventuellement tu souhaites que lâon travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dĂ©diĂ© Ă mon activitĂ© professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="SociĂ©tĂ© coopĂ©rative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au prĂ©alable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coĂ»te un site</a> et pourquoi je suis plutĂŽt favorable Ă une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
- </p>
- <p>
- Je ne traque pas ta navigation mais mon
- <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
- conserve des logs dâaccĂšs.
- </p>
- </div>
- </footer>
- <script type="text/javascript">
- ;(_ => {
- const jumper = document.getElementById('jumper')
- jumper.addEventListener('click', e => {
- e.preventDefault()
- const anchor = e.target.getAttribute('href')
- const targetEl = document.getElementById(anchor.substring(1))
- targetEl.scrollIntoView({behavior: 'smooth'})
- })
- })()
- </script>
-
- <script>
- /* Service workers */
- if (navigator.serviceWorker) {
- window.addEventListener('load', function () {
- var selector = 'a[href^="/david/cache/"], a[rel=prev], a[rel=next]'
- function sendLinks (selector) {
- var links = [].slice.call(document.querySelectorAll(selector)).map(function (link) {
- return link.getAttribute('href')
- })
- links.push(location.pathname) // Put the current page in cache too.
- navigator.serviceWorker.controller.postMessage({ links: links })
- }
- navigator.serviceWorker.getRegistration()
- .then(function (registration) {
- if (!registration || !navigator.serviceWorker.controller) {
- return navigator.serviceWorker.register('/serviceworker.js')
- .then(navigator.serviceWorker.ready)
- .then(function () {
- console.log('[ServiceWorker] Ready to go!')
- })
- .catch(console.error.bind(console))
- } else {
- console.log('[ServiceWorker] Send links via registration')
- sendLinks(selector)
- }
- })
- navigator.serviceWorker.addEventListener('controllerchange', function () {
- console.log('[ServiceWorker] Send links via controller change')
- sendLinks(selector)
- })
- navigator.serviceWorker.addEventListener('message', function (event) {
- var link = document.querySelector('a[href="' + event.data.link + '"]')
- if (event.data.status && link) {
- link.style.backgroundColor = '#2d7474'
- link.style.color = '#f0f0ea'
-
- link.setAttribute('title', 'En cache pour consultation sans connexion')
-
- }
- })
- })
- } else {
- console.warn('[ServiceWorker] No cache for old browsers.')
- }
- </script>
|