Мне хотелось поделиться собственными размышлениями и способом построения структуры приложения с использованием react-redux. Так как я относительно недавно стал писать код на JS и поэтому не претендую на истину и на действительно верный способ разработки. Надеюсь эта статья поможет начинающим разработчикам писать меньше костылей которые я усердно плодил в своем проекте. Продвинутым гуру в JS я думаю не скажу ничего нового, но пишите в комментариях все что думаете по этому поводу.

Основные принципы и советы по JS


  • Не считаю что чем больше кода, тем лучше. Более короткий код, на мой взгляд, легче читается и выглядит более изящным. Если что то можно написать короче, при условии что оно работает таким же образом, то я делаю это.
  • Ничего плохого в том чтобы писать кучу условий на проверку тех или параметров, но если условия никогда не будут работать это плохо и это лишний код, который загрязняет проект.

Пример:

checkImg(user) {
 if (!user) return;
	...
 var src = user && user.profileImageURL;
	...
 return(
	...)
}

Взглянув на этот код можно заметить, что условие user && — никогда не сработает правильно, потому что условие выше выбросит из функции раньше. Обычно такие костыли возникают, когда человек делает вставки или торопиться (как это делал я).

  • Оператор && очень удобен для проверки переменной user на наличие объекта, а затем обращении к свойству объекта. Нельзя обращаться к свойствам объекта, если он равен undefined.
  • Избегать чрезмерной вложенности условий. Для этого можно использовать такое правило в коде. Я бы назвал это правило метод выброса.

Пример:

мainCalc(obj){ 
if(!obj) return; // условие выхода из функции
...
... какой-то код
...
if(...) return; // след. условие выхода 
...
...
if (...) return; // каждый след. часть кода зависит от предыдущих частей
...
...
return; // таким образом производится допуск к коду без вложенности
}

— Простой пример уменьшения кода, где я написал функцию проверки и вывода картинки. В случае если значение свойства равнялось null или пустой строке, то вывести картинку по умолчанию.

BAD

checkImg(user) {
  if (user){
    if (user.profileImageURL !== undefined){
      if (user.profileImageURL === null) {
        return (<DefultImage />)
      }
      else{
        if (user.profileImageURL.length>0){
          return (<img src={user.profileImageURL} className="avatar photo" alt="" />)
        }else{
          return (<DefultImage />);
        }
      } 
    } 
    else {
      return;
    }
  }
  return;
}

GOOD

checkImg(user) {
  var u = user&&user.profileImageURL;
  if (u===undefined) return;
  if (!u) return (<DefultImage />);
  return (<img src={u} className="avatar photo" alt=""/>);
},

Важно отметить, что необходимость проверки зависит от разных условий. Если из примера выше мы знаем, что в user у нас всегда попадает значение типа объект, то проверять его нет смысла, так как в JS даже пустой объект всегда true. Поэтому будет правильней обратиться сразу к свойству объекта, которое там должно быть.

if (user.profileImageURL !== undefined){... 

— Условия проверки можно писать более коротким способом:

if (user.profileImageURL){...

Это работает одинаково, а наша задача уменьшить код как это возможно.

— Но часто бывают ситуации, когда у нас вложенность выше, чем просто объект –> свойство, допустим, есть вложенный объект и обращаться к его свойствам напрямую уже нельзя, поэтому нужна проверка вложенного объекта

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

Переходим к react-redux


1. Для начинающих делать проект на JS с использованием react-redux я советую не начинать проект с настройки WebPack, Babel, linter. Ответ на вопрос почему, находится в этой статье.
Ден Абрамов – создатель redux — также советует использовать Create React App.

2. Структура построения архитектуры приложения это тоже отдельная тема и поэтому я решил изучить этот вопрос подробнее после исследования десятков статей мне попалась статья разработчика. Который советует строить структуру не так как это сделано в большинстве примеров. Для больших проектов это огромный плюс, если следовать этим правилам.

Вот структура компонентов моего проекта и в каждом разделе свои actions и reducers:

image

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

image

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

3. Вот ссылка на мой написанный проект — там не все идеально, но в нем можно посмотреть структуру приложения. Но так уж сложилось, что в этом проекте я больше не участвую. Пусть этот код не лежит без дела.

4. При помощи PropTypes мы можем определить, какие параметры должен принимать компонент и в случае передачи в компонент неверных параметров будет выведена ошибка или предупреждение, а также можем установить значения по умолчанию. Но без этой штуки можно вполне обойтись и не добавлять их в проект по одной простой причине вы можете проверить данные до передачи их в компонент:

{this.state.visibleID&&(<ViewIcons location={this.state.location} visibleID={this.state.visibleID} />)}

Важное дополнение, компонент ViewIcons будет рендерится только после того как в переменную visibleID попадет какое либо значение, плюс если в доп. компоненте идет обращение к API то это поможет нам получить эти параметры в процедуре componentDidMount() в которой и следует делать все запросы. В свою очередь значения по умолчанию для компонента можно сделать через state. Ну если вам кажется что стоит использовать PropTypes, то используйте, потому что цель этой статьи уменьшать код а не увеличивать.

5. Так как в redux store у меня получает данные с сервера, то до момента пока данные не придут он равен пустому объекту, но допустим с сервера придет не объект, а массив объектов и обратится к какому либо свойству уже нельзя, для этого можно проверить первый элемент массива что он не равен undefined.

{this.props.Menu[0]&&this.createMenu()}

6. Локальный state компонента можно использовать для того чтобы сначала получить значения из store, но это мне кажется не всегда оправданно, тут как грань двух концов:

  • в первом случае придется делать переменные локального state и присваивать им значения затем проверять их в функции render;
  • во втором случае если не использовать локальный state, мы будем обращаться к свойствам объектов или элементу массива которые находятся в store;
  • и третий, лучший на мой взгляд, это присваивать по умолчанию для элементов redux store пустую строку или ноль и тогда не будет необходимости обращаться к свойствам объектов или первым элементам массива (undefined назначить элементу store нельзя).

    const menu = (state='', action) => {
    switch(action.type) {
    case "MENU_TREE":
    

Это просто моменты, с которыми я столкнулся на практике. Если кто-то знает как сделать изящней, то непременно пишите, буду рад.

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


  1. gogolor
    27.02.2018 18:52
    +1

    1. У вас в GOOD/BAD по разному функции работают. Можно проверить на checkImg({ profileImageURL: null })
    2. {this.props.Menu[0]&&this.createMenu()} — поговаривают, что это что-то типа антипаттерна. Такая конструкция очень усложняет чтение кода.


    1. rovkin1 Автор
      28.02.2018 00:12

      по 1 заметил уже поправил. а вот на счет 2 то я в конце писал как этого избежать там три варианта знаете лучше напишите.


      1. gogolor
        28.02.2018 08:23

        Чтобы уж совсем попридираться, у вас в BAD код упадет при checkImg({ profileImageURL: 0 }), а в GOOD вернет <DefultImage />


        а вот на счет 2 то я в конце писал как этого избежать там три варианта знаете лучше напишите.

        Честно говоря, я даже и половины не понял из того что вы написали.


        1. rovkin1 Автор
          28.02.2018 08:42

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


          1. gogolor
            28.02.2018 09:16

            Нет, мне непонятно, что вы хотели написать. Возьмем тот же пункт 6


            Локальный state компонента можно использовать для того чтобы сначала получить значения из store

            Тут я не понял, как state можно использовать для получения значения? Вы мб хотели сказать что-то вроде "Значения, полученные из store, можно записать в локальный state"?


            тут как грань двух концов

            Мб "палка о двух концах"? Почему? В чем плюсы и минусы этого решения? Мб пример приведете?


            делать переменные локального state и присваивать им значения

            Какие значения? Полученные из redux? Зачем?


            и третий, лучший на мой взгляд, это присваивать по умолчанию для элементов redux store пустую строку или ноль и тогда не будет необходимости обращаться к свойствам объектов или первым элементам массива (undefined назначить элементу store нельзя).

            Зачем необходимо обращаться к свойствам объектов или первым элементам массива? Приведите адекватный пример.


            1. rovkin1 Автор
              28.02.2018 09:46

              да вы первое правильно поняли это и хотел сказать! Второе палка или грань без разницы.

              Зачем необходимо обращаться к свойствам объектов или первым элементам массива? Приведите адекватный пример.

              для удобной проверки значения! Потому что есть необходимость проверить есть ли у нас данные в редакс сторе


              1. gogolor
                28.02.2018 09:55

                Так забейте туда null. Как получите значение — забейте туда значение. Если этот, очевидный, вариант не подходит, то приведите пример, где он не подходит.


                1. rovkin1 Автор
                  28.02.2018 10:19

                  У меня была ошибка. Мог конечно ошибиться но если у вас все работает с null (начальным состоянием redux стора), то без проблем используйте с null.


        1. rovkin1 Автор
          28.02.2018 08:45

          При нуле и не нужно в этом примере в параметр передавался либо пустой объект либо заполненшый.


          1. gogolor
            28.02.2018 09:20

            { profileImageURL: 0 } — вполне заполненный объект. Вы переписали функцию и изменили ее поведение. Плохой пример.


  1. DexterHD
    27.02.2018 19:37

    2. Структура построения архитектуры приложения это тоже отдельная тема и поэтому я решил изучить этот вопрос подробнее после исследования десятков статей мне попалась статья разработчика. Который советует строить структуру не так как это сделано в большинстве примеров. Для больших проектов это огромный плюс, если следовать этим правилам.

    Вот уже за это я ставлю +. Респект.


  1. serf
    27.02.2018 20:48

    Чем этот код хороший?

    checkImg(user) {
      var u = user&&user.profileImageURL;
      if (!u) return;
      if (u === null || u.length === 0) return (<DefultImage />);
      return (<img src={u} className="avatar photo" alt=""/>);
    },


    Проверка на пустую строку 3 раза, причем не самым лучшим образом (почему например просто !u вместо u.length === 0 не использовать)?

    Вот вариант более краткий:
    checkImg({profileImageURL}) {
      return profileImageURL ? <img src={profileImageURL} className="avatar photo" alt=""/> : <DefultImage />;
    },
    


    Вот структура компонентов моего проекта и в каждом разделе свои actions и reducers:
    Такой подход к структурирования файлов многими используется, называется grouping by feature. Помимо прочего это может быть полезно например при lazy загрузках, по очевидным причинам.

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

    Вместо путей вида .../.../.../../some/module/etc можно использовать алиасы.


    1. okhn
      01.03.2018 13:01

      checkImg({profileImageURL}) {
        return profileImageURL ? <img src={profileImageURL} className="avatar photo" alt=""/> : <DefultImage />;
      },

      а если на вход в функцию checkImg отправят user который undefined, то у вас код упадет на ошибке Cannot read property 'profileImageURL' of undefined.

      я бы сделал вот так вот
      const checkImg = (user) => {
        return user && user.profileImageURL ?
          <img src={user.profileImageURL} className="avatar photo" alt=""/> : <DefultImage />;
      };


      1. rovkin1 Автор
        01.03.2018 13:09

        посмотрите код внимательней:

        checkImg(user) {
          var u = user&&user.profileImageURL;
          if (u===undefined) return;
          if (!u) return (<DefultImage />);
          return (<img src={u} className="avatar photo" alt=""/>);
        },

        если user равен undefined в переменную u ничего не запишется и она тоже будет undefined и след условие нас выбросит из функции. никакой ошибки не будет


        1. apapacy
          01.03.2018 14:26

          Ваша функция возвращает в этом случае undefined а для построения дерева компонентов нужен или компонент или null.


          1. rovkin1 Автор
            01.03.2018 14:57

            ошибок в консоли у меня с этим не было. Но возможно присваивать нулл это правильно не задумывался над этим.


            1. apapacy
              01.03.2018 18:42

              не было наверное потому что условие не срабатывало. попробуйте вернуть прямо сразу undefined. Задумываться об этом не имеет смысло. Просто React так работает. Если бы в React было предусмотрено в этом случае возвращать undefined или пустую строку то так бы и слеодвало поступать. Но Нужен компонент или nullю Или в последних версиях добавился еще массив из этих элементов. Что наконец избавило разработчиков от необзодимости все заворачивать в бесконечное количество div для каждого компонента не имеющего одного родительского элемента.


              1. rovkin1 Автор
                01.03.2018 23:45

                Вы говорите на самом деле немного о других вещах. Возвращение null или компонента необходимо только для самих компонентов реакта. Это функция не является компонентом она использовалась в цикле где в параметр передавался текущий объект! Скажу вам более возвращение null нулл в данной ситуации привело бы к ошибке.


                1. apapacy
                  02.03.2018 13:00

                  Как используется эта функция по вашему коду определить не получается. Я предположил что если в ряде случаев функция возвращает компонент то это и есть компонент. Если что то это кажется логичным чтобы функция имела возвращаемое значение одного типа вне зависимости от га входных данных.


      1. serf
        01.03.2018 18:59

        или так

        checkImg({profileImageURL} = {}) {
          return profileImageURL ? <img src={profileImageURL} className="avatar photo" alt=""/> : <DefultImage />;
        },
        


  1. rovkin1 Автор
    28.02.2018 00:18

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


  1. apapacy
    28.02.2018 04:44

    checkImg(user) {
      var u = user&&user.profileImageURL;
      if (u===undefined) return;
      if (!u) return (<DefultImage />);
      return (<img src={u} className="avatar photo" alt=""/>);
    },


    При некоторых условиях функция вернет вместо компонента undefined. Это будет ошибка при построении дерева компонентов. Нужно возвращать null. см. reactjs.org/docs/conditional-rendering.html

    Поэтому будет правильней обратиться сразу к свойству объекта, которое там должно быть.

    Где-то я читал что до сих пор обращение к несуществующему объекту первая или вторая по распространенности «ошибка на миллион баксов». Она в таком коде конечно не исключается.

    Можно порекомендовать использовать линтер для анализа кода. КОд приведенный статье навскидку проверку такую не пройдет. А там много есть чего в линтере хорошего.