image


С выходом нового React 16.6.0 в документации появился HOOKS (PROPOSAL). Они сейчас доступны в react 17.0.0-alpha и обсуждаются в открытом RFC: React Hooks. Давайте разберемся что это такое и зачем это нужно под катом.


Да это RFC и вы можете повлиять на конечную реализацию обсуждая с создателями react почему они выбрали тот или иной подход.


Давайте взглянем на то как выглядит стандартный хук:


import { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Попробуйте обдумать этот код, это тизер и к концу статьи вы уже будете понимать, что он означает. Первое, что стоит знать, что это не ломает обратную совместимость и возможно их добавят в 16.7 после сбора обратной связи и пожеланий в RFC.


Как уверяют ребята, это не план по выпиливанию классов из реакта.


Так же хуки не заменяют текущие концепции реакта, все на месте props/state/context/refs. Это всего лишь еще один способ использовать их силу.


Мотивация


Хуки решают на первый взгляд не связные проблемы, которые появились при поддержке десятков тысяч компонентов в течении 5 лет у facebook.


Самое сложное это переиспользовать логику в stateful компонентах, у реакта нет способа прикрепить многоразовое поведение к компоненту(например подключить его к хранилищу). Если вы работали с React вам известно понятие HOC(high-order-component) или render props. Это достаточно хорошие паттерны, но иногда они используются чрезмерно, они требуют реструктуризации компонентов, для того, чтобы их можно было использовать, что обычно делает код более громоздким. Стоит посмотреть на типичное реакт приложение и станет понятно о чем идет речь.


image


Это называется wrapped-hell — ад оберток.


Приложение из одних HOC это нормально в текущих реалиях, подключили компонент к стору/теме/локализации/кастомным хокам, я думаю это всем знакомо.


Становится понятно, что реакту необходим другой примитивный механизм для разделения логики.


С помощью хуков мы можем извлекать состояние компонента, так чтобы его можно было тестировать и переиспользовать. Хуки позволяют повторно использовать логику состояния без изменения иерархии компонентов. Это облегчает обмен ссылками между многими компонентами или всей системы в целом. Так же классовые компоненты выглядят достаточно страшно, мы описываем методы жизненного цикла componentDidMount/shouldComponentUpdate/componentDidUpdate, состояние компонента, создаем методы для работы с состоянием/стором, биндим методы для экземпляра компонента и так можно продолжать до бесконечности. Обычно такие компоненты выходят за рамки x строк, где x достаточно сложно для понимания.


Хуки позволяют делать тоже самое разбивая логику между компонентами на маленькие функции и использовать их внутри компонентов.


Классы сложны для людей и для машин


В наблюдении facebook классы являются большим препятствием при изучении React. Вам необходимо понять как работает this, а он не работает так как в остальных языках программирования, так же следует помнить о привязке обработчиков событий. Без стабильных предложений синтаксиса код выглядит очень многословно. Люди прекрасно понимают паттерны props/state и так называемый top-down data flow, но достаточно сложно понимают классы.


Особенно если не ограничиваться шаблонами, не так давно ребята из реакта эксперементировали с компоновкой компонентов c Prepack и увидели многообещающие результаты, но тем не менее компоненты класса позволяют создавать непреднамеренные плохие паттерны, которые заставляют эти оптимизации исчезать, так же классы не очень хорошо мигрируют и при горячей перезагрузке классы делают ее ненадежной. В первую очередь ребятам хотелось предоставить API которое поддерживает все оптимизации и отлично работает с горячей перезагрузкой.


Глянем на хуки


State hook


Код ниже рендерит параграф и кнопку и если мы нажмем на кнопку то значение в параграфе будет инкрементировано.


import { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Отсюда можно сделать вывод что данный хук работает похоже с таким понятием как state.
Немного детальнее метод useState принимает один аргумент, это значение по умолчанию и возвращает кортеж(tuple) в котором есть само значение и метод для его изменения, в отличие от setState, setCount не будет делать merge значений, а просто обновит его. Так же мы можем использовать множественное объявление состояний, например:


function ExampleWithManyStates() {
  // Declare multiple state variables!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

таким образом мы создаем сразу несколько состояний и нам не нужно думать о том, чтобы их как то декомпозировать. Таким образом можно выделить, что хуки это функции которые позволяют "подключаться" к фишкам классовых компонентов, так же хуки не работают внутри классов, это важно запомнить.


Effect hook


Часто в классовых компонентах, мы делаем side effect функции, например подписываемся на события или делаем запросы за данными, обычно для этого мы используем методы componentDidMount/componentDidUpdate


import { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Когда мы вызываем useEffect мы говорим реакту сделать 'side effect' после обновления изменений в DOM дереве. Эффекты объявляются внутри компонента, поэтому имеют доступ к props/state. Причем их мы можем точно так же создавать сколько угодно.


function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...

Сразу же стоит обратить внимание на второй side effect в нем мы возвращаем функцию, делаем мы это для того, чтобы выполнить какие то действия после того как компонент выполняет unmount, в новом api это называют эффекты с очисткой. Остальные эффекты могут возвращать, что угодно.


Правила хуков


Хуки это просто javascript функции, но они требуют всего двух правил:


  • Выполнять хуки следует в самом верху иерархии функции(это означает, что не следует вызывать хуки в условиях и циклах, иначе реакт не может гарантировать порядок выполнения хуков)
  • Вызывать хуки только в React функциях или функциональных компонентах или вызывать хуки из кастомных хуков(это ниже).
    Для следования этим правилам ребята из команды реакта создали линтер плагин который выдаст ошибку если вы вызываете хуки в компонентах класса или в циклах и условиях.

Кастомные хуки


В тоже время нам хочется переиспользовать логику stateful компонентов, обычно для этого используют либо HOC либо render props паттерны, но они создают дополнительный объем нашего приложения.
Например опишем следующую функцию:


import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

Осознайте этот код, это будет кастомный хук, который мы можем вызывать в различных компонентах. Например так:


function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

или так


function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

В любом случае, мы переиспользуем состояние компонента, каждый вызов функции useFriendStatus создает изолированное состояние. Так же стоит отметить, что начало этой функции начинается со слова use это говорит о том, что это хук. Советуем соблюдать этот формат. Вы можете писать кастомные хуки на что угодно, анимации/подписки/таймеры и много многое другое.


Есть еще пара хуков.


useContext


useContext позволяет использовать вместо renderProps обычное возвращаемое значение, в него следует передать контекст который мы хотим извлечь и он нам его вернет, таким образом мы можем избавиться от всех HOC, которые передавали context в props.


function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}

И теперь объект контекста мы можем просто использовать в возвращаемом значении.


useCallback


const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

Как часто вам приходилось создавать компонент класса только для того, чтобы сохранить ссылку на метод? Этого больше не нужно делать, мы можем использовать useCallback и наши компоненты не будут перерисовываться потому что пришла новая ссылка на onClick.


useMemo


Возвращаем мемоизированное значение, мемоизированное значит вычисляется только тогда, когда один из аргументов поменялся, второй раз одно и тоже вычисляться не будет.


const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Да тут приходится дублировать значения в массиве, чтобы хук понял, что они не изменились.


useRef


useRef возвращает мутируемое значение, где поле .current будет инициализировано первым аргументом, объект будет существовать пока существует компонент.
Самый обычный пример при фокусе на input


function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useImperativeMethods


useImperativeMethods кастомизирует значение экземпляра который передается из родителя и использует ref напрямую. Как всегда следует избегать передачу ссылок на прямую и следует использовать forwardRef


function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeMethods(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

В этом примере компонент который рендерит FancyInput может вызывать fancyInputRef.current.focus().


useMutationEffect


useMutationEffect очень похож на useEffect за исключением того что он запускается синхронно на том этапе когда реакт изменяет значения DOM, прежде чем соседние компоненты будут обновлены, этот хук следует использовать для выполнения DOM мутаций.
Лучше предпочитать useEffect чтобы предотвратить блокировку визуальных изменений.


useLayoutEffect


useLayoutEffect так же похож на useEffect за исключением того, что запускается синхронно после всех обновлений DOM и синхронного ре-рендера. Обновления запланированные в useLayoutEffect применяются синхронно, до того как браузер получит возможность отрисовать элементы. Так же следует стараться использовать стандартный useEffect чтобы не блокировать визуальные изменения.


useReducer


useReducer — это хук для создания редюсера который возвращает состояние и возможность диспатчить изменения:


const [state, dispatch] = useReducer(reducer, initialState);

Если вы понимаете как работает Redux, то вы понимаете как работает useReducer. Тот же пример, что был с счетчиком сверху только через useReducer:


const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return initialState;
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'reset'})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

Так же useReducer принимает 3 аргумент, это action который должен выполнятся при инициализации редюсера:


const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return {count: action.payload};
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(
    reducer,
    initialState,
    {type: 'reset', payload: initialCount},
  );

  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

Так же мы можем создать контекст в данным редюсером и через хук useContext использовать его во всем приложении, это остается на домашнее задание.


Подводя итог


Хуки достаточно мощный подход по решению wrapper-hell и решают несколько проблем, но все их можно свети с одному определению передача ссылок. Уже сейчас начинают появляться сборники хуков по использованию или этот сборник. Более подробнее с хуками можно познакомиться в документации.

Комментарии (62)


  1. Sirion
    31.10.2018 12:56
    +1

    Я ещё раз внимательно перечитаю статью, когда выпью чашечку послеобеденного чаю. Пока хочу поделиться первым впечатлением: на вид адище какое-то, право слово.


    1. shadek
      31.10.2018 15:18

      Очень верное впечатление, только выпить захотелось чего покрепче


    1. Sirion
      01.11.2018 08:10

      Перечитал и всё равно не понял, зачем это нужно. Наверное, это потому. что я слишком мало пользуюсь HoC и никогда не сталкивался с HoC hell. Если бы кто-то привёл мне:

      1. Пример вышеупомянутого ада в реалистичных условиях
      2. Как хуки от него спасают
      3. Почему от него не спасает классический подход

      — я был бы, во-первых, очень благодарен, а во-вторых, вероятно, изменил бы своё мнение относительно сабжа.


      1. faiwer
        01.11.2018 08:46

        Это примерно вот так. Обратите внимание на нижнюю часть, где хлебные-крошки. Там есть скролл. Раньше эта панель показывалась без скролла и там было 3-5 строк одних только хлебных крошек. На производительность такая вложенность толком не влияет (она тут "затюнена" до упора). Всё "летает". Но вот отладка страдает, да. Просто тонны визуального мусора. Хотели добавить галочку "скрыть врапперы", и судя по скриншоту из статьи даже добавили, но у меня пока её нет.


        По большей части виной всему этому не HoC-hell, а context-hell. До React16 контекст не создавал новых звеньев в древе, после стал создавать сразу по многу. Частично я решил эту проблему декоратором, но только частично.


        1. vintage
          01.11.2018 09:07

          Ужас какой. Как вы это дебажите? А ведь могли же сделать хорошо.


          1. faiwer
            01.11.2018 10:05
            +2

            Обратите внимание на то, что мы сравниваем тёплое и красное. Я про древо компонентов React, а вы мне про итоговый HTML (а не про древо компонентов $mol). DOM то в итоге выглядит очень даже кратко и опрятно.


            Кстати, а эти жуткие ID-ики у каждой DOM-ноды и тысячи каких-то mol-аттрибутов — это обязательное следствие использования $mol? Эта стена мусора прямо аж обескураживает.


            1. vintage
              01.11.2018 10:22

              Это и дерево компонент тоже. В том-то и дело.

              Айдишники — семантические хлебные крошки. Но нет, они не обязательны.


              1. faiwer
                01.11.2018 10:26

                Это и дерево компонент тоже. В том-то и дело.

                • Т.е. каждый компонент завязан на одну и только одну DOM-ноду, да? Для сравнения в React компонент может быть завязан на 0+ DOM-нод. Это гораздо практичнее. Этого мне сильно не хватает во Vue.
                • Нет возможности обернуть один компонент другим, не добавив лишний уровень DOM-иерархии. Или есть? Если есть, то утверждение что древо компонент = DOM-древу снова ложное.

                Айдишники — семантические хлебные крошки

                А зачем вам столько хлебных крошек? Зачем вообще эта гора мусора? Это не может не мешать дебагу.


  1. kix
    31.10.2018 13:08

    Судя по стилю (отсутствию пунктуации и рассогласованности в некоторых местах), эта статья — перевод. Читать очень тяжело. Можете дать ссылку на оригинал?


    1. merrick_krg Автор
      31.10.2018 13:52

      Оригинал это документация, плюс немного личного опыта.


  1. Fengol
    31.10.2018 13:10
    +1

    В наблюдении facebook классы являются большим препятствием при изучении React

    Прошу прощения, но неужели мир уже деградировал?


    1. sbovyrin
      31.10.2018 15:18

      вероятно в данном высказывании имелось ввиду, что классы JS вводят в заблуждение, ибо это не то что в других ЯП.


      1. Fengol
        31.10.2018 15:24

        Класс, это структура реализующая одновременно и состояние и поведение. Разве не так? Если так, то что из перечисленного классы в js делать не умеют?


        1. VolCh
          01.11.2018 11:04

          Очень специфическая реализация классов в JS из которой сильно торчат ноги прототипного наследования. Ну и работа с this далеко не очевидна.


    1. devlev
      31.10.2018 15:23

      Вам необходимо понять как работает this, а он не работает так как в остальных языках программирования, так же следует помнить о привязке обработчиков событий.
      Вы следующее предложение читали?


      1. faiwer
        31.10.2018 22:10

        Вам необходимо понять как работает this

        А это такой тоже интересный момент. Мне кажется, что если в своих JS задачах вы достигли уровня, когда следует воспользоваться какой-либо библиотекой уровня React (т.е. задачи уже довольно серьёзные), то...


        … то наверное знать язык, на котором вы пишете и правда имеет смысл. Нет? Я не прав?


        Но реалии интереснее. В Slack-е webstandart-ов стал натыкаться на элементарнейшие вопросы в разделе #react. "У меня не работает, почему?". Лезу в код — о боже. Указываю на конкретную проблему… аргументирую… В итоге выясняю, что человек не понимает даже в каком порядке интерпретатор выполняет код. К примеру, что переменную стоило бы объявить и дать ей нужное значение ДО ТОГО, как пытаться что-нибудь из неё прочитать. Ну и прочая такая дичь.


        Приходит человек собеседоваться. В резюме гордая строка "React-developer" (это вообще как? о_О). Проверки на знания языка неизменно приводят к ответу, что человек знает язык только на поверхностном уровне. React-а, разумеется, тоже не знает, но наколабасить что-то простое, что даже заведётся в состоянии. Наверное в этом и заключается гордое звание React-developer. Мне бы вот в жизни бы не пришла мысль называть себя в таком ключе. Backbone-developer? Lodash-developer? jQuery-developer? Mootools-developer? Vue-developer? Вы хто такие все? )


        Но, видимо, это актуально. Актуально учить некий абстрактный React забив даже на JS.


        1. bano-notit
          31.10.2018 23:24

          Но, видимо, это актуально. Актуально учить некий абстрактный React забив даже на JS.

          Да, это та же JQ, только реактивная и от другого разработчика. История имеет свойство повторяться с завидной регулярностью.


        1. nsinreal
          01.11.2018 01:56

          А это такой тоже интересный момент. Мне кажется, что если в своих JS задачах вы достигли уровня, когда следует воспользоваться какой-либо библиотекой уровня React (т.е. задачи уже довольно серьёзные), то...
          Увы, популярная практика «фуллстеков» приводит к тому, что человек знает только одну часть профессии нормально, но с другой части его спрашивают как будто он знает её нормально.

          Но, видимо, это актуально. Актуально учить некий абстрактный React забив даже на JS.
          Это верстальщики и свитчеры, не обращайте внимания.


        1. devlev
          01.11.2018 08:36

          В наблюдении facebook классы являются большим препятствием при изучении React.
          Но реалии интереснее. В Slack-е webstandart-ов стал натыкаться на элементарнейшие вопросы в разделе #react. «У меня не работает, почему?». Лезу в код — о боже. Указываю на конкретную проблему… аргументирую… В итоге выясняю, что человек не понимает даже в каком порядке интерпретатор выполняет код. К примеру, что переменную стоило бы объявить и дать ей нужное значение ДО ТОГО, как пытаться что-нибудь из неё прочитать. Ну и прочая такая дичь.
          Может все таки стоит признать что после наблюдений facebook и собственного опыта мир возможно реально уже деградировал? Судя по вашим результатам собеседования все действительно может быть плохо, и молодым новобранцам тяжело сразу вникнуть в суть. И поэтому идея с хуками имеет место быть так как это небольшой шаг назад чтобы сделать жизнь им (новобранцам, не вам специалистам с опытом) жизнь чуточку проще. Этакий функциональный подход.


          1. mayorovp
            01.11.2018 10:11
            +1

            Это не функциональный подход, а магическо-DSL-подход. В ФП как бы функцию можно вызывать всегда, а не как тут — строго в начале метода и вне любых возможных условных операторов или циклов.


        1. Pappageno
          01.11.2018 11:19
          +1

          … то наверное знать язык, на котором вы пишете и правда имеет смысл. Нет? Я не прав?

          Тут дело не совсем в знать. Это как минимум неудобно и алогично, особенно в контексте других языков. Все эти «знать» находятся под и являются свойствами не классов, а того, что под них мимикрирует. И это «под» ортогонально базовым представления из тех же С++, java.

          Вот мы и получаем, что есть какой-то класс, который даже не класс и с которым можно сделать что угодно. Как минимум у нас нет статической типизации, а как максимум мы имеем то, что класс не является типом. Всё это ломает привычную парадигму, а то, что мы получаем в замен убого и не функционально.

          И зачем нам нужно то, что работает плохо и выглядит ужасно, если проще воспользоваться плюсами этой парадигмы и получит красивую, практичную и удобную магию. К тому же, этот мир(в том числе и реакт) пропитан тем, что не очень привычно людям из других миров. Кому и зачем в мире привычного ООП нужна чистота, иммутабельность и прочие?

          Да и подобные решения не являются чем-то новым — это всё уже есть и используется в том же mobx и иже с ним. Магия является трендом и именно магия куда сложнее для понимания.


          1. faiwer
            01.11.2018 11:39

            Ваш комментарий полон весьма субъективных оценок. Одна сторона у вас и работает плохо, и выглядит ужасно, и вообще непонятная\непривычная\кривая. Преимущества убоги и нефункциональны. Другая же сторона полна магии, которая в тренде, хоть и сложна для понимания, но выглядит красиво, и, вероятно, работает хорошо. Так?


            Боюсь, что мы с вами одни и те же вещи оцениваем совсем по-разному. Очень по-разному. И я остаюсь при своём мнении, что даже если вы используете процедурный подход, знать как устроен this и prototype-chain в JS вам нужно, ибо это одна из центральных частей языка, от которой никуда не деться, и ни за какими React-ми не спрятаться.


            1. Pappageno
              01.11.2018 12:42
              +1

              Одна сторона у вас и работает плохо, и выглядит ужасно, и вообще непонятная\непривычная\кривая.

              Она не работает плохо, она мне не непривычна. Она просто ортогональна базовым концепциям того же реакта, да и много чему ещё. Об этом позже.

              Преимущества убоги и нефункциональны.

              Преимущества есть, но они не здесь. Что, в контексте того же реакта, мне дают классы? Да ничего. Стейт прикручен сбоку, контекст тоже, пропсы тоже. Методы/поля не являются свойствами ТОЛЬКО класса, как в других языках.

              Что я получаю от класса? Да ничего не получаю. Я даже не знаю что могут дать классы и за пределами ректа, ну кроме строгости( что крайне спорно, ведь тут строгостью и не пахнет) и декораторов.

              Другая же сторона полна магии, которая в тренде, хоть и сложна для понимания, но выглядит красиво, и, вероятно, работает хорошо.

              Почти так, и тому есть причины. Зачем нужны тонны бойлерплейта и прочего ахтугда, если можно использовать красивую магию? Это безопасней, быстрее, удобнее. Да, сложнее.

              Так?

              На самом деле мне нравится магия на декораторах и я за классы. Но в данном случае всё не сводится к моим хотелкам. Я вижу что было и что стало, и что стало мне нравится больше.

              что даже если вы используете процедурный подход

              Не использую.

              знать как устроен this и prototype-chain в JS вам нужно, ибо это одна из центральных частей языка, от которой никуда не деться, и ни за какими React-ми не спрятаться.

              Вот видите, в этом и проблема. Мне нужно понимать не классы, не их логику, а логику левых кишков. К тому же, эта логика куда мощнее классов(в контексте жс и его классов). Вот и получается, что у меня есть классы, которые целиком и полностью неюзабельны, а вся логика у меня находится сбоку. Вот и вопрос — зачем нужны такие классы?


  1. ThisMan
    31.10.2018 15:04
    +1

    Пишут, что классы «сложны для понимания», но при этом добавляют ещё одну сущность. Не думаю, что эти прям таки хороший подход. Некоторые примеры, вообще не очевидными кажутся ( например, возвращаемая функция в useEffect )


    1. faiwer
      31.10.2018 21:49
      +2

      но при этом добавляют ещё одну сущность

      Больше всего умиляет то, что для того чтобы избавиться от "сложных и непонятных классов", они добавили скрытый "трекинг" хуков (причём в последовательной манере). Это нехилая такая абстракция. На ровном месте. Чтобы не писать методов в классе. Хотели избавиться от this, и теперь пишем всё в одном замыкании. А для быстродействия надо методы оборачивать в useCallback-и (явно дублируя все зависимости). Ох, мои глаза. Зачем так то?


      А патчем до этого они решили добавить context в старом стиле в классы. Но только для одного consumer-а. Какие-то странные ограничения на ровном месте. Зачем именно так?


      React становится всё более и более… странным. Всё ещё понятным, но уже довольно странным. Даже вот redux встроили в react :) Сразу со switch-case-ми, dispatch-ми и экшнами. Эх.


      1. VolCh
        01.11.2018 11:11

        Есть предложения лучше? Пролемы с трудностями понимания работы this есть точно, ровно как и проблемы с переиспользованием stateful кода.


        1. faiwer
          01.11.2018 11:25

          Есть предложения лучше?

          Нууу, например mixins & auto-bind в конструкторе Component. Чем не решение? На худой конец декоратор для методов класса.


          1. VolCh
            01.11.2018 11:46

            Декоратор классов я бы предложил для переиспользования, но в JS декораторов нет и не понятно будут ли в обозримом будущем.

            Миксинов тоже в JS нет, auto-bind с трудом представляю как можно реализовать.


            1. faiwer
              01.11.2018 11:52

              auto-bind с трудом представляю как можно реализовать.

              Например в constructor-е React.Component класса. Самое простое (чтобы без груды overhead-а) оборачивать все методы начинающиеся на on[A-Z]. Но можно и все вообще.


              но в JS декораторов нет и не понятно будут ли в обозримом будущем.

              Ну дык и JSX тоже нет в JS и не предвидится.


              Говоря про Mixin-ы, я имел ввиду что-то типа старого mixins: [mixin1, mixin2]. Всё равно пришли к тому же самому в итоге.


  1. devlev
    31.10.2018 15:05

    Вообще жизнеспособно. Пока конечно сложно представить какие сложности могут возникнуть при написание компонентов на хуках вместо классов. Это можно будет узнать только в полевых условиях.


  1. Fengol
    31.10.2018 15:31

    Мало того что врут о сложности классов, так ещё и говорят что классы делают что-то ненадежным! При чем тут классы? Это facebook может делать горячую перезагрузку ненадежной, но не классы. Кроме того класс есть класс и работа с ним понятна и логична. А тут на тебе. Классы сложны — используйте хуки, но только всегда держите в своей памяти, что они должны выполняться только при тех условиях, которые очень схожи с условиями пришествия в этот мир сатаны.


    1. merrick_krg Автор
      31.10.2018 16:16

      Классы сложны в том плане, что нужно отдельно изменять контекст методов, заранее определять состояние, если у нас компонент с единственным методом как в первом примере, то чтобы сделать рендер эффективным приходится использовать библиотеки подобные bind либо создавать компонент класса, где бы мы могли передать ссылку на функцию, никто не запрещает использовать классы, если нравятся. Хуки решают проблему wrapper-hell оберток и дают нативную возможность делить состояние между компонентами.


      1. vintage
        31.10.2018 19:16
        -1

        Это не классы сложны, а фейсбук придумал абстракцию, где нет разделения на инициализацию и обновление. В результате чего передача компоненту стрелочной функции ломает эту чудесную абстракцию.


        1. merrick_krg Автор
          31.10.2018 19:23
          +2

          Вы кажется не понимаете о чем говорите, освежите свои знания по тому как ссылки в js работают и возвращайтесь.


          1. vintage
            31.10.2018 19:32
            -1

            Вам сюда: habr.com/post/285540


          1. nsinreal
            31.10.2018 23:54

            Мне кажется, что человек говорит о том, что стрелочные функции можно было бы сделать получше и нужда в классах бы сильно уменьшилась.


            1. vintage
              01.11.2018 08:43

              Нет, человек говорит о том, что реакт можно было бы сделать лучше и стрелочные функции не приводили бы к лишним ререндерам. Правда это был бы уже не реакт, а что-то другое.


              1. faiwer
                01.11.2018 08:50

                Вы бы хоть пояснили как именно стрелочные функции ломают React, как приводят в лишним рендерам, и почему именно стрелочные (а обычные не приводят?). Я примерно догадываюсь про что идёт речь (shallowComparison in PureComputed & event handlers?), но не уверен. Это ведь может быть что-угодно. Телепаты все в отпуске.


                1. vintage
                  01.11.2018 09:17

                  Вы всё правильно догадываетесь. Об этом пишут в каждой статье про оптимизацию приложений на реакте. Странно, что мистер "понимаю о чём говорю" не в курсе этого.


      1. Fengol
        31.10.2018 20:15
        +1

        Во-во. Хочешь избавится от обертки-хелл и нативную поддержку разделенного состояния — откажись от классов! Помните, много лет назад facebook ликовал, что он наконец-то убедил Абрамова в пользе классов? А теперь получается, что один Абрамов переубедил весь facebook! Он покусал их и обратил в свое, особое видение программирования — абромирование.


      1. faiwer
        31.10.2018 21:57

        никто не запрещает использовать классы, если нравятся

        Тут ведь такой момент. Они открытым текстом в F.A.Q. пишут, что компоненты-через-хуки это их новая основная парадигма. Т.е. именно это становится true-react-way. Предлагают новые вещи писать в этом стиле.


        Так что по сути довольно логично ожидать того, что новые фичи и удобства будут в первую (и наверное часто единственную) очередь касаться именно хуко-компонент. Можно, конечно, быть ренегатом, но оправдано ли. Кстати, судя по документации, хуки не работают в class-components. Даже useContext.


        Хуки решают проблему wrapper-hell оберток и дают нативную возможность делить состояние между компонентами.

        Кстати отчасти хуки решают wrapper-hell, который существует исключительно ввиду того, что многие годы HoC пропагандируются как лекарство от всех болезней. Даже context завезли в таком виде, что у меня на проекте до 7 обёрток на 1 <div/> уходит. Это ведь не классовая модель виновата. Это react-way нынче такой. Либа просто не предоставляет низкоуровневых инструментов, чтобы ряд вещей, вроде того же контекста, можно было сделать без HoC. Вот попробуйте реализовать useContext-hook самостоятельно. Не получится.


        В какой-то момент команду React-а похоже задрали эти HoC и было решение сделать ход… конём.


        1. VolCh
          01.11.2018 11:16
          +1

          Больше похоже, что классы вообще развиваться не будут, разве что в каких-то исключительных случаях. Выглядит так, как будто единственная причина необъявления классов deprecated — неохота переписывать свои десятки тысяч компонентов. Вот избавятся от них в стиле «что работает — не трогаем, но если нужно что-то изменить, то переписываем на хуки», тогда объявят :)


  1. bano-notit
    31.10.2018 23:33

    "Мы не делаем фреймворк, мы делаем либу для реактивной отрисовки компонентов" — говорили они...


    Блин, вместо того, чтобы как-то организовать merge этих всех wrapper'ов в виде официального api они начали изобретать mobx, но в своём собственном стиле. Зачем? Просто потому что правят балом именно они, и они это знают.


    Жаль, что они начинают вылезать из своих изначальных "обязанностей", задекларированных раньше. А когда люди начали вокруг этой либы строить нормальные решения, они начали думать: "почему-бы всё же не быть полностью фреймворком, ведь мы сможем сделать всё круче и быстрее остальных!"


    Ну что же, будем ждать, сколько ещё времени проживёт реакт в нормальном виде, до становления полной копии ng.


    1. nsinreal
      01.11.2018 01:57

      *лучшей копии ng


  1. nsinreal
    31.10.2018 23:51

    Я очень рад, что в реакте вводятся хуки. Это снимет много головной боли связанной с типизацией HoC и вообще необходимостью делить компоненты везде, а не только где надо.
    Но вот то, что их в циклах использовать нельзя — это ужасно. Такая себе протекающая абстракция.


    1. nsinreal
      01.11.2018 01:51

      Ой, я наконец-то внимательно прочитал доку. Раньше я думал, что хуки нельзя использовать внутри компонентов которые внутри цикла. А вот оно как на самом деле:

      Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function
      Это решается линтером.


  1. samsdemon
    01.11.2018 11:04
    +1

    Зато можно выкинуть recompose и других ребят и делать всё более-менее однотипно
    Имхо, ни победа, ни поражение, просто движение вбок (да, привет миксинам)


    1. VolCh
      01.11.2018 11:18

      Вот, да, каждый новый проект как будто на чём-то совсем другом написан.


  1. Fragster
    01.11.2018 11:08

    До дружелюбия vuejs все равно очень далеко. Кстати, не понимаю людей, которые тянут эти реактовские штуки в него. Уже выпустили github.com/yyx990803/vue-hooks, примеры зачем-то пишут в рендер-функциях на jsx. Такое ощущение, что подсознательно не могут отказаться от сложного в пользу простого.


    1. VolCh
      01.11.2018 11:20

      Где там jsx в примерах?


      1. Fragster
        01.11.2018 11:26

        На конкретно в этом репозитории (vue-hooks) нет, хотя и там используется затрудняющае понимание рендер функция (зачем? чем плохо иметь template, подсвечиваемый в редакторе, с работающим intellisence, очень похожий на html (или pug)?).


  1. VolCh
    01.11.2018 12:01

    Мне лично идея очень нравится, но вот возврат тупла как-то не очень. Кто-то видит плюсы по сравнению с возвратом объекта с read-only полем значения и методом его установки? а может просто с использованием геттеров.сеттеров нативных?


    1. samsdemon
      01.11.2018 12:18

      github.com/reactjs/rfcs/pull/68#issuecomment-433135283
      В двух словах — проще именовать, ибо setState не знает, как правильно вернуть вашу проперти и какой у вас нэйминг конвеншн. Может у вас CamelCase)


      1. VolCh
        01.11.2018 12:27

        А деструктуризации я не думал, разве что в крайнем случае. Я имел в виду не проще ли делать


        const name = useState('Name');
        return <input value={name.value} onChange={e => name.setValue(e.target.value)} onChange={e => name.value = e.value } />;


    1. faiwer
      01.11.2018 12:19

      Видимо, что не пришлось пользоваться точечной объектной нотацией и не быть привязанным к конкретным полям вроде value | setValue. Пример:


      const color = useState('red');
      <ColorPicker color={color.value} onChange={color.set}/>
      
      const [color, onChange] = useState('red');
      <ColorPicker {...{ color, onChange}}/>

      Предложенный вами вариант сейчас используется в ref-ах, где нужно обращаться к полю current. В итоге в кодовой базе полно строк вида:


      const block = this.blockRef.current;
      // use it


      1. VolCh
        01.11.2018 12:28

        Вот понятно что, чтобы её избежать, но не понятно зачем её избегать, особенно учитывая что this не использовать


        1. faiwer
          01.11.2018 12:35

          Просто так удобнее, ИМХО. К примеру, я обычно пишу код в стиле:


          render(){
            const { var1, var2, var3 } = this.props;
            const { var4, var5 } = this.state;
            const { var6, var7 } = var3;
          
            return // use all of them
          }

          Так меньше всякой мишуры получается в наиболее перегруженной части (непосредственно JSX). Стараюсь поддерживать состояние JSX предельно простым.


          1. VolCh
            01.11.2018 12:39

            Я обычно это использую, чтобы от this избавиться, то есть { props, state } = this


            1. faiwer
              01.11.2018 12:45
              +1

              Мне кажется, что для JSX кода ниже нет особой разницы откуда пришло значение из props или из state. Главное что оно есть. Поэтому логичнее не this убрать, а вообще всё лишнее, т.е. прицелиться уже к конкретным значениям. Скажем разве это принципиально в <ColorPicker color="state.color"/>, что color в state? Моя логика такая. К тому же часто в props-ах какие-нибудь составные объекты — их тоже удобнее разобрать на нужные запчасти до применения. В общем суть — убрать визуальный шум и оставить только саму суть.


              1. VolCh
                01.11.2018 12:49
                +1

                Ну в коде с которым я работаю нередко встречаются одинаковые названия свойств в пропсах и стейте


  1. xadd
    01.11.2018 14:36

    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
      // ...
    }
    


    Разве здесь не должен быть useState(ChaAPI.friendsStatus[friendID].isOnline), и что тогда будет, если придет значение с другим friendID? Поскольку это initialState, то значение isOnline не изменится. Как это разрулить хуками?


    1. VolCh
      01.11.2018 14:57

      Подразумевается, что пока не пришло сообщение от API, на которое мы подписались, то статус нам неизвестен и API уведомит нас когда он станет известен. Может при подписке сразу дернёт колбэк, чтобы сообщить уже известный, а может отправит запрос и дернёт когда ответ придёт. Пока колбэк не дернули наш, у нас статус «лоадинг».

      Хук жёстко привязан к стэку вызовов, включая параметры, как я понимаю. Если придёт новый friendID, то создастся новый стейт и новая подписка