Что такое react-afc

c3f75f63e88b065d8c6dfe18d91b35d3

react-afc— библиотека для более простого (чем в простом react) уменьшения количества ненужных ререндеров дочерних компонентов.

Задачи и применение

В обычном react функциональный компонент вызывается каждый раз когда изменяется его состояние или пропсы, что вызывает повторное создание всех callback’ов и переменных.
Так как передаваемые данные из предыдущего и текущего рендера не равны, это порождает ререндер дочерних компонентов.

пример

Функционал компонента не несёт конкретного смысла. Просто пример.

import { useState } from 'react'
import Title from 'title-lib'
import HardCalcHeader from './header'
import NameInput from './name-input'
import AgeInput from './age-input'

function App() {
  const [name, setName] = useState('')
  const [age, setAge] = useState(1)

  const onChangeName = value => setName(value)
  const onChangeAge = value => setAge(value)
  const closeWindow = () => window.close()

  const titleArgs = {
    color: 'blue',
    size: 20
  }
  
  return (
    <>
      
      <HardCalcHeader onExit={closeWindow} />
        
      <NameInput value={name} onChange={onChangeName} />
      <AgeInput value={age} onChange={onChangeAge} />
    </>
  )
}</code></pre><p>При изменении имени происходит перерисовка <strong>Title</strong>, <strong>NameInput</strong>, <strong>AgeInput</strong>, а также <strong>HardCalcHeader</strong>, что приводит к зависанию приложения.</p></div>

<p>Для избежания этого поведения мы разбиваем логику на множество компонентов (не всегда удобно), либо используем <code>useCallback</code> и <code>useMemo</code> (стоит дополнительных затрат при многократном вызове, ухудшает читаемость кода и требует отслеживания зависимостей функции).</p><p>пример с использованием хуков</p>

<div><pre><code class="javascript">import { useState, useCallback, useMemo } from 'react'
import Title from 'title-lib'
import HardCalcHeader from './header'
import NameInput from './name-input'
import AgeInput from './age-input'

function App() {
  const [name, setName] = useState('')
  const [age, setAge] = useState(1)

  const onChangeName = useCallback(value => setName(value), [])
  const onChangeAge = useCallback(value => setAge(value), [])
  const closeWindow = useCallback(() => window.close(), [])

  const titleArgs = useMemo(() => ({
    color: 'blue',
    size: 20
  }), [])
  
  return (
    <>
      <Title text='Amazing app' args={titleArgs} />
      <HardCalcHeader onExit={closeWindow} />
        
      <NameInput value={name} onChange={onChangeName} />
      <AgeInput value={age} onChange={onChangeAge} />
    </>
  )
}</code></pre></div>

<p>Вся суть работы библиотеки лежит в одном, но значительном изменении структуры функционального компонента — добавлении <strong>аналога конструктора</strong> классового компонента.</p><p>сравнение</p>

<div><pre><code class="javascript">import { afc } from 'react-afc'

// обычный компонент
function CommonComponent(props) {
  // вызывается каждый рендер
  // ...react-хуки
  return <p>обычный компонент</p>
}

// afc компонент
const AdvancedComponent = afc(props => {
  // вызывается один раз за весь жизненный цикл компонента
  // afc-хуки

  return () => {
    // render-функция, вызывается каждый рендер
    // ...react-хуки (только по необходимости)
    return <p>afc</p>
  }
}</code></pre></div>

<p>Данное изменение позволяет нам во многих случаях не использовать <code>useCallback</code> и <code>useMemo</code>, а также не думать о зависимостях.</p><p>пример с использованием библиотеки</p>

<div><pre><code class="javascript">import { afc, useState } from 'react-afc'
import Title from 'title-lib'
import HardCalcHeader from './header'
import NameInput from './name-input'
import AgeInput from './age-input'

const App = afc(() => {
  const [getName, setName] = useState('')
  const [getAge, setAge] = useState(1)

  const onChangeName = value => setName(value)
  const onChangeAge = value => setAge(value)
  const closeWindow = () => window.close()

  const titleArgs = {
    color: 'blue',
    size: 20
  }
  
  return () => (
    <>
      <Title text='Amazing app' args={titleArgs} />
      <HardCalcHeader onExit={closeWindow} />
        
      <NameInput value={getName()} onChange={onChangeName} />
      <AgeInput value={getAge()} onChange={onChangeAge} />
    </>
  )
})</code></pre><p>Работает аналогично примеру с хуками, никаких лишних перерисовок.</p></div>

<p>Побочным эффектом является то, что мы больше не нуждаемся в использовании <code>useRef</code> для передачи данных между рендерами.</p><p>пример</p>

<div><pre><code class="javascript">import { useRef } from 'react'
import { afc } from 'react-afc'

// обычный компонент
function CommonComponent() {
  const renderCount = useRef(0)
  renderCount.current++

  return (
    <p>
      Рендер вызван {renderCount.current} раз
    </p>
  )
}

// afc компонент
const AdvancedComponent = afc(() => {
  let renderCount = 0

  return () => {
    renderCount++
    return (
      <p>
        Рендер вызван {renderCount} раз
      </p>
    )
  }
})</code></pre></div>

<p><em>Примечание: </em>в библиотеке имеются аналоги для <code>useState</code>, <code>useRef</code>, <code>useMemo</code>, <code>useEffect</code>, <code>memo</code>. Их применение узкоспециализировано, читайте доку.<br />Пример работы можете найти на codesandbox.</p>

<h2>Принцип работы</h2>

<p>При первом рендере библиотека вызывает переданную в <code>afc</code> функцию, определяет какие хуки используются и сохраняет возвращённую рендер-функцию.</p>

<p>При последующих рендерах обновляются свойства в объекте пропсов (если они изменились), выполняются определённые ранее react-хуки и вызывается рендер-функция.</p>

<p>Принцип прост как пробка и требует незначительных вычислений только при первом рендере.</p>
    
            <p class="copyrights"><span class="source">© <a target="_blank" rel="nofollow" href="https://habr.com/ru/articles/784326/?utm_source=habrahabr&amp;utm_medium=rss&amp;utm_campaign=784326">Habrahabr.ru</a></span></p>
                    </div>
                                                    
            <br>
            <!--<div align="left">
                <script type="text/topadvert">
                load_event: page_load
                feed_id: 12105
                pattern_id: 8187
                tech_model:
                </script><script type="text/javascript" charset="utf-8" defer="defer" async="async" src="//loader.topadvert.ru/load.js"></script>
            </div>
            <br>-->

            <div style="padding-left: 20px;">
                <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-2514821055276660"
                        crossorigin="anonymous"></script>
                <!-- PCNews 336x280 -->
                <ins class="adsbygoogle"
                     style="display:block"
                     data-ad-client="ca-pub-2514821055276660"
                     data-ad-slot="1200562049"
                     data-ad-format="auto"></ins>
                <script>
                    (adsbygoogle = window.adsbygoogle || []).push({});
                </script>
            </div>
            <!-- comments -->
                            <noindex>
                    <div style="margin: 25px;" id="disqus_thread"></div>
                    <script type="text/javascript">
                        var disqus_shortname = 'pcnewsru';
                        var disqus_identifier = '1353626';
                        var disqus_title = 'Что такое react-afc';
                        var disqus_url = 'http://pcnews.ru/blogs/cto_takoe_react_afc-1353626.html';

                        (function() {
                            var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
                            dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
                            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
                        })();
                    </script>
                    <!--<noscript>Please enable JavaScript to view the <a rel="nofollow" href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>-->
                    <!--<a href="http://disqus.com" rel="nofollow" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>-->
                </noindex>
            
        </div>

        <br class="clearer"/>
    </div>
    <br class="clearer"/>

    

        <div id="footer-2nd"></div>

        <div id="footer">
            <br/><br/>
            <ul class="horz-menu">
                <li class="about"><a href="/info/about.html" title="О проекте">О
                        проекте</a></li>
                <li class="additional-menu"><a href="/archive.html" title="Архив материалов">Архив</a>
                </li>
                <li class="additional-menu"><a href="/info/reklama.html"
                                               title="Реклама" class="menu-item"><strong>Реклама</strong></a>
                    <a href="/info/partners.html" title="Партнёры"
                       class="menu-item">Партнёры</a>
                    <a href="/info/legal.html" title="Правовая информация"
                       class="menu-item">Правовая информация</a>
                    <a href="/info/contacts.html" title="Контакты"
                       class="menu-item">Контакты</a>
                    <a href="/feedback.html" title="Обратная связь" class="menu-item">Обратная
                        связь</a></li>
                <li class="email"><a href="mailto:pcnews@pcnews.ru" title="Пишите нам на pcnews@pcnews.ru"><img
                                src="/media/i/email.gif" alt="e-mail"/></a></li>
                <li style="visibility: hidden">
                    <noindex>
                        <!-- Rating@Mail.ru counter -->
                        <script type="text/javascript">
                            var _tmr = window._tmr || (window._tmr = []);
                            _tmr.push({id: "93125", type: "pageView", start: (new Date()).getTime()});
                            (function (d, w, id) {
                                if (d.getElementById(id)) return;
                                var ts = d.createElement("script");
                                ts.type = "text/javascript";
                                ts.async = true;
                                ts.id = id;
                                ts.src = (d.location.protocol == "https:" ? "https:" : "http:") + "//top-fwz1.mail.ru/js/code.js";
                                var f = function () {
                                    var s = d.getElementsByTagName("script")[0];
                                    s.parentNode.insertBefore(ts, s);
                                };
                                if (w.opera == "[object Opera]") {
                                    d.addEventListener("DOMContentLoaded", f, false);
                                } else {
                                    f();
                                }
                            })(document, window, "topmailru-code");
                        </script>
                        <noscript>
                            <div style="position:absolute;left:-10000px;">
                                <img src="//top-fwz1.mail.ru/counter?id=93125;js=na" style="border:0;" height="1"
                                     width="1" alt="Рейтинг@Mail.ru"/>
                            </div>
                        </noscript>
                        <!-- //Rating@Mail.ru counter -->

                    </noindex>
                </li>
            </ul>
        </div>

        <!--[if lte IE 7]>
        <iframe id="popup-iframe" frameborder="0" scrolling="no"></iframe>
        <![endif]-->
        <!--<div id="robot-image"><img class="rbimg" src="i/robot-img.png" alt="" width="182" height="305" /></div>-->
        <!--[if IE 6]>
        <script>DD_belatedPNG.fix('#robot-image, .rbimg');</script><![endif]-->

    </div>

<!--[if lte IE 7]>
<iframe id="ie-popup-iframe" frameborder="0" scrolling="no"></iframe>
<![endif]-->


    <div id="footer-adlinks"></div>

    
    
    
        <noindex>


            <!--LiveInternet counter--><script type="text/javascript">
                document.write("<a rel='nofollow' href='//www.liveinternet.ru/click' "+
                    "target=_blank><img src='//counter.yadro.ru/hit?t45.6;r"+
                    escape(document.referrer)+((typeof(screen)=="undefined")?"":
                        ";s"+screen.width+"*"+screen.height+"*"+(screen.colorDepth?
                            screen.colorDepth:screen.pixelDepth))+";u"+escape(document.URL)+
                    ";"+Math.random()+
                    "' alt='' title='LiveInternet' "+
                    "border='0' width='1' height='1'><\/a>")
            </script><!--/LiveInternet-->

            <!-- Rating@Mail.ru counter -->
            <script type="text/javascript">
                var _tmr = window._tmr || (window._tmr = []);
                _tmr.push({id: "93125", type: "pageView", start: (new Date()).getTime()});
                (function (d, w, id) {
                    if (d.getElementById(id)) return;
                    var ts = d.createElement("script"); ts.type = "text/javascript"; ts.async = true; ts.id = id;
                    ts.src = "https://top-fwz1.mail.ru/js/code.js";
                    var f = function () {var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ts, s);};
                    if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); }
                })(document, window, "topmailru-code");
            </script><noscript><div>
                    <img src="https://top-fwz1.mail.ru/counter?id=93125;js=na" style="border:0;position:absolute;left:-9999px;" alt="Top.Mail.Ru" />
                </div></noscript>
            <!-- //Rating@Mail.ru counter -->



            <!-- Yandex.Metrika counter -->
            <script type="text/javascript">
                (function (d, w, c) {
                    (w[c] = w[c] || []).push(function () {
                        try {
                            w.yaCounter23235610 = new Ya.Metrika({
                                id: 23235610,
                                clickmap: true,
                                trackLinks: true,
                                accurateTrackBounce: true,
                                webvisor: true,
                                trackHash: true
                            });
                        } catch (e) {
                        }
                    });

                    var n = d.getElementsByTagName("script")[0],
                        s = d.createElement("script"),
                        f = function () {
                            n.parentNode.insertBefore(s, n);
                        };
                    s.type = "text/javascript";
                    s.async = true;
                    s.src = "https://mc.yandex.ru/metrika/watch.js";

                    if (w.opera == "[object Opera]") {
                        d.addEventListener("DOMContentLoaded", f, false);
                    } else {
                        f();
                    }
                })(document, window, "yandex_metrika_callbacks");
            </script>
            <noscript>
                <div><img src="https://mc.yandex.ru/watch/23235610" style="position:absolute; left:-9999px;" alt=""/>
                </div>
            </noscript>
            <!-- /Yandex.Metrika counter -->

            <!-- Default Statcounter code for PCNews.ru http://pcnews.ru-->
            <script type="text/javascript">
                var sc_project=9446204;
                var sc_invisible=1;
                var sc_security="14d6509a";
            </script>
            <script type="text/javascript"
                    src="https://www.statcounter.com/counter/counter.js"
                    async></script>
            <!-- End of Statcounter Code -->

            <script>
                (function (i, s, o, g, r, a, m) {
                    i['GoogleAnalyticsObject'] = r;
                    i[r] = i[r] || function () {
                            (i[r].q = i[r].q || []).push(arguments)
                        }, i[r].l = 1 * new Date();
                    a = s.createElement(o),
                        m = s.getElementsByTagName(o)[0];
                    a.async = 1;
                    a.src = g;
                    m.parentNode.insertBefore(a, m)
                })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

                ga('create', 'UA-46280051-1', 'pcnews.ru');
                ga('send', 'pageview');

            </script>

            <script async="async" src="/assets/uptolike.js?pid=49295"></script>

        </noindex>
    



<!--<div id="AdwolfBanner40x200_842695" ></div>-->
<!--AdWolf Asynchronous Code Start -->

<script type="text/javascript" src="https://pcnews.ru/js/blockAdblock.js"></script>

<script type="text/javascript" src="/assets/jquery.min.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/jquery/jquery.json.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/jquery/jquery.form.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/jquery/jquery.easing.1.2.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/jquery/effects.core.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/browser-sniff.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/scripts.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-utils.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-auth.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-fiximg.js"></script>
<script type="text/javascript" src="/assets/a70a9c7f/js/pcnews-infobox.js"></script>
</body>
</html>