9 корисних порад для ознайомлення з React.js [*]


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

Розповідає Кем Джексон


Я використовую React.js вже 6 місяців. Так, звучить, як короткий термін, але для мінливого світу JS-фреймворків це дуже довго! Я вже давав поради новачкам, і тому вирішив, що буде кращею ідеєю зібрати їх разом.

Я припускаю, що Ви розбираєтесь у базових поняттях; якщо слова “component”, “prop” і ” state” Вам ні про що не говорять, то спершу слід ознайомитися з офіційними сторінками  Getting started і Tutorial. Крім того,  я буду використовувати JSX, оскільки є зручний синтаксис для написания компонентів.

1. Це просто бібліотека для роботи з представленнями

Спершу розберемо засади. React – це не черговий MVC-фреймворк або якийсь інший фреймворк. Це просто бібліотека для рендерингу Ваших представлень. Якщо Ви прийшли зі світу MVC, то варто зрозуміти, що React – це тільки “V”, а “M” і “C”, доведеться пошукати десь ще.

2. Компоненти мають бути невеликими

Звучить очевидно, чи не так? Кожен тямущий розробник знає, що маленькі класи / модулі / та що завгодно легше зрозуміти і підтримувати. Моєю помилкою на початку роботи з React було те, що я не зрозумів, наскільки маленькими мають бути мої компоненти. Звичайно, кінцевий розмір залежатиме від багатьох чинників, але взагалі варто робити компоненти значно меншими, ніж Ви плануєте спочатку. Як приклад приведу компонент, що відображає останній запис у моєму блозі на головній сторінці сайту:

const LatestPostsComponent = props => (

Latest posts

{ props.posts.map (post => ) }

);

Сам компонент – це<section> , з двома<div>‘ами всередині. У першому знаходиться заголовок, а другий виводить якісь дані, відображаючи <PostPreview> кожного елемента. Десь такими мають бути Ваші компоненти.

3. Пишіть функціональні компоненти

Раніше були всього два способи визначення React-компонентів, перший – це React.createClass ():

const MyComponent = React.createClass ({ render: function (){ return
; }});

. а другий – класи ES6:

class MyComponent extends React.Component { render (){ return
;}
}

React 0.14 приніс новий синтаксис визначення компонентів як функцій від властивостей:

const MyComponent = props => (
);

Це мій найулюбленіший спосіб. Крім кращого синтаксису цей підхід дає ясно зрозуміти, коли компонент варто розділити. Розглянемо попередній приклад і уявімо, що ми його ще не розділили:

class LatestPostsComponent extends React.Component { render (){ const postPreviews = renderPostPreviews (); return (

Latest posts

{ postPreviews }

); } renderPostPreviews () { return this.props.posts.map (post => this.renderPostPreview (post)); } renderPostPreview (post) { return (

); }}

Цей клас не такий вже і поганий. Ми вже винесли пару методів з методу відмальовки і непогано інкапсулювали саму ідею рендерингу останніх постів. Перепишемо цей код, використовуючи функціональний синтаксис:

const LatestPostsComponent = props => { const postPreviews = renderPostPreviews (props.posts); return (

Latest posts

{ postPreviews }

);};const renderPostPreviews = posts => ( posts.map (post => this.renderPostPreview (post)));const renderPostPreview = post => (

);

Код майже не змінився, правда, тепер у нас є чисті функції, а не методи класу. Проте, це різні речі. У першому прикладі я бачу class LatestPostsComponent { і відразу переглядаю код у пошуках закриваючої дужки, думаючи: “Це кінець класу, тобто і кінець компонента”. Для порівняння, в другому прикладі я бачу const LatestPostsComponent = props => { і шукаю лише кінець цієї функції, думаючи: “Це кінець функції, тобто і кінець модуля. Стоп, але що це за код після мого компонента, в тому ж модулі? А, це інша функція, яка приймає дані і відмальовує представлення! Я можу винести її в окремий компонент!”.

У майбутньому React буде оптимізований так, щоб функціональні компоненти були ефективнішими, але поки що їх продуктивність знаходиться під великим знаком питання; я рекомендую Вам ознайомитися з цим матеріалом для прояснення картини.

Важливо зазначити, що у функціональних компонент є декілька обмежень, які я вважаю їх сильними сторонами. Перше – до функціонального компонента не можна прив’язати ref. Хоча ref і є зручним способом для спілкування компонента зі своїми нащадками, я вважаю, що це не для функціонального React, а швидше для імперативного jQuery.

Друге обмеження – до функціональних компонент не можна прикріпити стан, і це також є перевагою, оскільки я раджу…

4. Пишіть компоненти без станів

Варто сказати, що найбільше болю при написанні React-додатків я відчув від компонент з великим використанням стану.

Стани утрудняють тестування

Найпростіше тестувати чисті функції, так навіщо псувати їх, додаючи стани? Після додавання станів нам треба привести всі компоненти в потрібний стан, а також перебрати всі комбінації станів і властивостей, що дуже незручно.

Стани утрудняють розуміння компонентів

За читання коду компонента, насиченого станами, постає дуже багато питань: “Цей стан вже ініціалізував?”, “Що станеться, якщо я зміню цей стан тут?”, “Де ще змінюється цей стан”?, Чи “є тут стан гонки (race condition)?” і подібних – а це зайвий головний біль.

Стани занадто спрощують додавання бізнес-логіки в компоненти

Ми не повинні займатися визначенням поведінки компонента. Пам’ятайте, React – це бібліотека для роботи з представленнями, і тому, коли в компоненті є доступ до стану додатка, доведеться стримуватися від спокуси додавання в нього обчислень або валідації, адже їм там не місце. Крім того, змішення логіки відмальвки і бізнес-логіки ще більше ускладнить тестування.

Стани утрудняють обмін інформацією між частинами додатка

Коли компонент має стан, його можна передати нижче за ієрархією компонентів, але не в інших напрямах.

Звичайно, іноді в тому, що у конкретного компонента є повний доступ до конкретного стану, є сенс, і тоді можна використати this.setState – це цілком законна частина API React-компонентів. Наприклад, якщо користувач вводить інформацію в поле, немає сенсу робити кожне натиснення клавіш доступним усьому додатку, тому досить буде відстежувати стан поля самому полю, а після закінчення введення передати далі введене значення.

Коротше кажучи, будьте украй обережними при додаванні станів. Коли почнете, буде дуже складно утриматися від додавання чергової “маленької фічі”.

5. Використовуйте Redux.js

У першому пункті я сказав, що React потрібний лише для роботи з представленнями. Але куди ж помістити всі стани і логіку?

Ви напевно чули про Flux-SPA(style/pattern/architecture) для розробки веб-додатків, часто використовуваних з React. Є декілька фреймворків, що реалізовують ідеї Flux, але я однозначно рекомендую Redux.js.

Ось стислий опис принципів роботи Redux:

  1. Компоненти приймають колбеки як властивості і викликають їх, коли відбувається UI-подія.
  2. Ці колбеки створюють і відправляють дії залежно від події.
  3. Редюсери обробляють дії, обчислюючи новий стан.
  4. Новий стан усього додатка поміщається в одне сховище.
  5. Компоненти отримують новий стан як властивість і перевідмальовують себе за необхідності.

Велика частина вищеназваних ознак зустрічаються не лише в Redux, але він надає дуже просту їх реалізацію і крихітний API. Перенісши немалий проект з Alt.js на Redux, я виділив декілька переваг:

  • Редюсери – це чисті функції, які просто роблять наступне: oldState + action = newState. Кожен редюсер обчислює окрему частину станів, які потім об’єднуються. Це помітно спрощує тестування бізнес-логіки і станів.
  • API менше, простіше і краще задокументований.
  • Якщо Ви використовуєте Redux як годиться, покладатися на нього буде зовсім невелика кількість компонентів; інші лише отримуватимуть стани і колбеки як властивості.

Є ще декілька бібліотек, які прекрасно доповнюють,Redux:

  • Immutable.js – незмінні структури даних в JavaScript! У них варто зберігати стани, щоб вони випадково не змінилися.
  • redux – thunk – вона використовується, коли крім зміни стану повинен виникнути ще якийсь “побічний ефект”.
  • reselect – використайте її для створення компонованих представлень.

6. Завжди використовуйте propTypes

propTypes надають нам дуже простий спосіб підвищення безпеки наших компонентів. Вони виглядають так:

const ListOfNumbers = props => (
    1. { props.numbers.map (number => (

    2. {number}

) ) }

);ListOfNumbers.propTypes ={ className: React.PropTypes.string.isRequired, numbers: React.PropTypes.arrayOf (React.PropTypes.number)};

У процесі розробки, якщо якомусь компоненту не буде передано потрібну властивість або властивість іншого типу, React створить лог помилки. Переваги:

  • можна заздалегідь ловити баги;
  • якщо Ви використовуєте isRequired, Вам не треба перевіряти на undefined чи null;
  • вирішується питання документування.

Хоча і схоже на пропагування статичної типізації, це не так. Я надаю перевагу динамічним типам за їх простоту, але propTypes додатково підвищують безпеку  компонентів, тому я не бачу причин, щоб їх не використовувати.

Також варто налаштувати тести на провал у випадку зіткнення з помилками propType. Це просто працює:

beforeAll (() => { console.error = error => { throw new Error (error); };});

7. Використовуйте неглибокий рендеринг

Тестування React-компонентів може бути непростим, оскільки ця тема все ще розвивається, і немає однозначно кращого підходу. На даний момент я вважаю за краще використати неглибокий рендеринг (shallow rendering) і підтвердження властивостей (prop assertions).

Неглибокий рендеринг зручний тим, що він дозволяє повністю виводити один компонент, не зачіпаючи нащадків. Замість цього повернений об’єкт повідомить типи і властивості нащадків.

Найчастіше я пишу три типи юніт-тестів компонентів:

Логіка відмальовки

Представте компонент, який повинен за умовою відмальвувати зображення або іконку завантаження:

const Image = props => { if (props.loading) { return ;} return ;};

Ми можемо протестувати його так:

describe ('Image', () => { it ('renders a loading icon when the image is loading', () => { const image = shallowRender (); expect (image.type).toEqual (LoadingIcon); }); it ('renders the image once it has loaded', () => { const image = shallowRender (); expect (image.type).toEqual ('img'); });});

Елементарно! Варто зазначити, що API для неглибокого рендерингу складеніший, ніж у моєму прикладі. Функція shallowRender написана нами як обгортка API для його спрощення.

Повертаючись до компонента ListOfNumbers, можна протестувати його коректність:

describe ('ListOfNumbers', () => { it ('renders an item for each provided number', () => { const listOfNumbers = shallowRender (); expect (listOfNumbers.props.children.length).toEqual (4); });});

Перетворення властивостей

У попередньому прикладі ми зайшли в нащадків використовуваного компонента, щоб перевірити, чи коректно відмалювали. Це можна розширити, якщо підтвердити, що нащадки не лише коректні, але й отримали потрібні властивості. Це особливо корисно, коли компонент якось перетворює властивості перед їх передачею. Наприклад, розглянемо цей компонент, який бере імена CSS-класів у вигляді масиву рядків і передає їх як один рядок:

const TextWithArrayOfClassNames = props => (

{props.text}

);describe (‘TextWithArrayOfClassNames’, () => { it (‘turns the array into a space – separated string’, () => { const text = ‘Hello, world!’; const classNames =[‘red’, ‘bold’, ‘float – right’]; const textWithArrayOfClassNames = shallowRender (); const childClassNames = textWithArrayOfClassNames.props.children.props.className; expect (childClassNames).toEqual (‘red bold float – right’); });});

Часто такий підхід критикують за props.children.props.children. Хоча такий код і не дуже красивий, він корисний тому, що дає зрозуміти: якщо в одному тесті надто багато props.children, компонент варто зменшити.

Ще одним зауваженням є те, що такі тести дуже залежні від зовнішньої реалізації компонента в тому плані, що навіть щонайменша зміна структури DOM ламає всі тести. Таку думку я поділяю, але боротися з цим можна, оскільки зменшуючи розмір компонентів, можна знизити кількість потенційно нестійких тестів.

Взаємодія з користувачем

Звичайно, компоненти ще й інтерактивні:

const RedInput = props => ( )

Ось мій улюблений спосіб тестування:

describe ('RedInput', () => { it ('passes the event to the given callback when the value changes', () => { const callback = jasmine.createSpy (); const redInput = shallowRender (); redInput.props.onChange ('an event!'); expect (callback).toHaveBeenCalledWith ('an event!'); });});

Приклад тривіальний, але я думаю, що ви зрозуміли суть.

Інтеграційне тестування

Поки що ми розглянули лише юніт-тестування, але потрібні якісь тести вищого рівня для тестування всього додатка. Не вдаватимуся до деталей:

  1. Відмальовуйте все дерево компонентів (замість неглибокого рендерингу).
  2. Заглибтеся в DOM (використовуючи React TestUtils, jQuery і так далі) для пошуку найважливіших елементів, і:
    • підтвердіть їх HTML-атрибути або вміст, або
    • симулюйте DOM-події та підтвердіть побічні ефекти (зміни DOM або шляхів, AJAX- виклики і т. д.).

8. Використовуйте JSX, ES6, Babel, Webpack і NPM

Єдиною річчю, специфічною для react, є JSX. Його недолік –  невелике збільшення часу складання, але це вирішується за допомогою Babel.

Якщо вже ми почали використовувати Babel, немає причин відмовлятися від використання всіх функцій ES6. Відчувається, що JavaScript починає рости як мова, враховуючи, скільки часу потрібно для підготовки всіх інструментів.

Закінчимо ми використанням Webpack для бандлінгу нашого коду і NPM для керування пакетами.

9. Використовуйте інструменти розробників React і Redux

Переклад статті “9 things every React.js beginner should know”

 

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *