Мысьленьне ў React’е (Thinking in React)

React, на нашу думку, гэта найлепшы шлях для стварэньня вялікіх, хуткіх прыкладных Web праграмаў з дапамогай JavaScript. Ён вельмі добра маштабуецца для нас на Facebook ды Instagram.

Адна з многіх вялікіх роляў React — як ён прымушае вас думаць пра прыкладныя праграмы з пункту гледжаньня, як вы будзеце іх будаваць. У гэтым дакумэнце, пройдземся праз мысьленны працэс пабудовы табліцы даных пошукавага прадукту з выкарыстаньнем React.

Пачніце з макета

Уявіце сабе, што ў нас ужо ёсьць JSON API і макет ад нашага дызайнэра. Макет выглядае наступным чынам:

Mockup

Нашае JSON API вяртае некаторыя даныя, якія выглядаюць так:

[
  {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"},
  {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"},
  {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"},
  {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"},
  {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"},
  {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"}
];

Крок 1: Перавядзіце інтэрфэйс карыстальніка ў герархію кампанэнтаў

Першае, што хацелася б зрабіць, гэта намаляваць на макеце скрынкі вакол кожнага кампанэнта (і падпарадкаваных кампанэнтаў) і даць ім усім імёны. Калі вы працуеце з дызайнэрам(і), то, магчыма, ён(яны) гэта ўжо зрабіў(лі), так што трэба пайсьці ды паразмаўляць зь ім(і)! Іхнія назвы слаёў у Photoshop могуць стаць імёнамі вашых React кампанэнтаў!

Але адкуль вам ведаць, што павінна стаць гэтым асобным кампанэнтам? Проста выкарыстоўвайце тыя ж самыя мэтады для прыняцьця рашэньня, як і пры стварэньні новай функцыі ці аб’екта. Адным з такіх мэтадаў зьяўляецца прынцып адзінай адказнасьці (single responsibility principle), гэта значыць, што кампанэнт у ідэале павінен рабіць толькі адну рэч. Калі ў выніку росту гэта перастае быць так, то ён павінен быць раскладзены на меншыя субкампанэнты.

Як пачняце часта адлюстроўваць мадэль даных JSON для карыстальніка, дык вы выявіце, што, калі ваша мадэль пабудавана правільна, то ваш UI (а, значыцца, і структура вашага кампанэнта) будзе адлюстраваны прыгожа. Гэта таму, што UI і мадэлі даных, як правіла, прытрымліваюцца аднае і тае ж інфармацыйнае архітэктуры, якая азначае, што праца па разьдзяленьні вашага UI на кампанэнты часта трывіяльная. Проста разьбіце яго на кампанэнты, якія прадстаўляюць роўна па аднаму кавалачку вашае мадэлі даных.

Component diagram

Вы ўбачыце тут, што ў нашай простай прыкладной праграме ёсьць пяць кампанэнтаў. Мы вылучылі курсівам даныя, якія прадстаўляе кожны кампанэнт.

  1. FilterableProductTable (аранжавы): зьмяшчае поўню прыклада
  2. SearchBar (сіні): прымае ўвесь увод карыстальніка (user input)
  3. ProductTable (зялёны): адлюстроўвае й фільтруе калекцыю даных (data collection), заснаваную на ўводзе карыстальніка (user input)
  4. ProductCategoryRow (бірузовы): адлюстроўвае загаловак для кожнай катэгорыі (category)
  5. ProductRow (чырвоны): адлюстроўвае радок для кожнага прадукту (product)

Калі вы паглядзіце на ProductTable, то ўбачыце, што загаловак табліцы (які зьмяшчае ярлыкі “Name” ды “Price”) не зьяўляецца ягоным ўласным кампанэнтам. Гэта пытаньне пераваг, і ёсьць аргумэнты, каб зрабіць і так, і гэтак. Для гэтага прыкладу, мы пакінулі яго як частку ProductTable, таму што ён зьяўляецца часткай візуалізацыі (rendering) набору даных (data collection), якая адносіцца да адказнасьці ProductTable. Аднак, калі гэты загаловак вырастае ў складаны (гэта значыць, калі мы дадаем магчымасьць для сартаваньня), то, канешне, быў бы сэнс зрабіць яго самастойным кампанэнтам ProductTableHeader component.

Цяпер, калі мы вызначылі кампанэнты ў нашым праекце, давайце ўкладзем іх у герархію. Гэта проста. Кампанэнты, якія ўваходзяць ў іншы кампанэнт у макеце павінны выглядаць як дзеці ў герархіі:

  • FilterableProductTable

    • SearchBar
    • ProductTable

      • ProductCategoryRow
      • ProductRow

Крок 2: Стварыце статычную вэрсію ў React’е

Глядзіце Pen Thinking In React: Step 2 на CodePen.

Цяпер, калі ў вас ёсьць герархія кампанэнтаў, надышоў час, каб рэалізаваць вашу прыкладную праграму. Самы просты спосаб — стварыць вэрсію, якая прымае мадэль даных і рэндэрыць UI, але ня мае інтэрактыўнасьці. Лепш за ўсё аддзяліць гэтыя працэсы, так як пабудова статычнай вэрсіі патрабуе шмат набіраць і ня думаць, а дадаваньне інтэрактыўнасьці патрабуе шмат думаць і няшмат набіраць. Мы пабачым чаму.

Каб пабудаваць статычную вэрсію прыкладаньня, якая рэндэрыць вашу мадэль даных, вы захочаце пабудаваць кампанэнты, якія паўторна выкарыстоўваюць іншыя кампанэнты і перадаюць даныя, выкарыстоўваючы props (уласьцівасьці). props зьяўляюцца спосабам перадачы даных ад бацькоўскага кампанэнта да дзіцячага. Калі вы знаёмыя з паняцьцем state, не выкарыстоўвайце стан наогул пры пабудове гэтай статычнай вэрсіі. Стан зарэзэрваваны толькі для інтэрактыўнасьці, гэта значыць даных, якія зьмяняюцца зь цягам часу. Так як гэта статычная вэрсія прыкладной праграмы, то ён вам не патрэбны.

Вы можаце будаваць зьверху ўніз або зьнізу ўверх. Гэта значыць, што вы можаце пачаць альбо з пабудовы кампанэнтаў, якія вышэй у герархіі (гэта значыць, пачаць з FilterableProductTable), альбо зь ніжніх (ProductRow). У больш простых прыкладах, як правіла, лягчэй ісьці зьверху ўніз, а на больш буйных праектах лягчэй ісьці зьнізу ўверх і пісаць тэсты ў працэсе будаваньня.

У канцы гэтага кроку, вы будзеце мець бібліятэку з годных да паўторнага выкарыстаньня кампанэнтаў, якая рэндэрыць вашу мадэль даных. Кампанэнты будуць мець толькі мэтады render(), паколькі гэта статычная вэрсія вашай прыкладной праграмы. Кампанэнт на вяршыні герархіі (FilterableProductTable) будзе прымаць вашу мадэль даных у якасьці ўласьцівасьці (prop). Калі вы ўнесяцё зьмены ў вашу базавую мадэль даных і выклічаце ReactDOM.render() ізноў, то UI будзе абноўлены. Лёгка ўбачыць, як абнаўляецца ваш UI і куды ўносіць зьмены, паколькі нічога складанага не адбываецца. Аднабаковы паток даных (one-way data flow) React’а (таксама завецца аднабаковым зьвязваньнем (one-way binding)) трымае ўсё модульным і хуткім.

Проста зьвярніцеся да React docs, калі вам патрэбна дапамога ў выкананьні гэтага кроку.

Кароткая інтэрлюдыя: Props супраць State

У React’е існуюць два тыпы “мадэльных” даных: уласьцівасьці (props) ды стан (state). Важна разумець адрозьненьне паміж імі; праглядзіце афіцыйную дакумэнтацыю React, калі вы ня ўпэўненыя ў чым розьніца.

Крок 3: Вызначце мінімальнае (але поўнае) прадстаўленьне стану інтэрфэйса карыстальніка (UI State)

Каб зрабіць ваш UI інтэрактыўным, вам трэба мець магчымасьць рэгістраваць зьмены ў сваёй базавай мадэлі даных. React лёгка робіць гэта з дапамогай state.

Каб правільна пабудаваць прыкладную праграму, у першую чаргу вам неабходна абдумаць мінімальны набор зьмяняльнага стану, які патрабуецца вашай прыкладной праграме. Ключом тут зьяўляецца DRY: Don’t Repeat Yourself (не паўтарайце сябе). Высьветліце абсалютнае мінімальнае прадстаўленьне стану, якое патрабуецца вашай прыкладной праграме, ды разьлічыце ўсё астатняе, што яшчэ вам спатрэбіцца па патрабаваньні. Напрыклад, калі вы ствараеце сьпіс TODO, то проста трымайце паблізу масіў элемэнтаў (пунктаў) TODO; ня трымайце асобную пераменную стану (state variable) для падліку. Замест гэтага, калі вы хочаце адрэндэрыць лік TODO, то проста вазьміце даўжыню масіву элемэнтаў (пунктаў) TODO.

Прадумайце ўсе часткі даных у нашым прыкладзе прыкладной праграмы. Мы маем:

  • Арыгінальны сьпіс прадуктаў
  • Пошукавы тэкст, уведзены карыстальнікам
  • Значэньне сьцяжка (checkbox)
  • Адфільтраваны сьпіс прадуктаў

Давайце пройдземся па кожнай ды вызначым, якая зь іх зьяўляецца станам. Проста задайце тры пытаньні пра кожную частку даных:

  1. Ці перадаецца яна ад бацькоўскага элемэнта праз уласьцівасьці (props)? Калі так, то гэта, верагодна, ня стан.
  2. Ці застаецца яна нязьменнай зь цягам часу? Калі так, то гэта, верагодна, ня стан.
  3. Ці можаце вы вылічыць яе на аснове іншага стану або ўласьцівасьцяў (props) у вашым кампанэнце? Калі так, то гэта ня стан.

Арыгінальны сьпіс прадуктаў перадаецца ў якасьці ўласьцівасьцяў (props), так што гэта ня стан. Пошукавы тэкст і сьцяжок (checkbox), здаецца, зьяўляюцца станам, так як яны зьмяняюцца зь цягам часу і ні з чаго ня могуць быць вылічаныя. І, нарэшце, адфільтраваны сьпіс прадуктаў не зьяўляецца станам, таму што ён можа быць вылічаны шляхам аб’яднаньня арыгінальнага сьпісу прадуктаў з пошукавым тэкстам і значэньнем сьцяжка.

Такім чынам, урэшце, нашым станам зьяўляецца:

  • Пошукавы тэкст, уведзены карыстальнікам
  • Значэньне сьцяжка

Крок 4: Вызначце, дзе павінен жыць ваш State

Глядзіце the Pen Thinking In React: Step 4 on CodePen.

Добра, такім чынам, мы вызначылі, якім ёсьць мінімальны набор стану (state) прыкладной праграмы. Далей, нам неабходна вызначыць, які кампанэнт гэты стан зьмяняе, або ім валодае.

Памятайце: React — гэта ўсё пра аднакірункавы паток даных уніз па герархіі кампанэнтаў. Можа быць не адразу зразумела, які кампанэнт якім станам павінен валодаць. Часта гэта зьяўляецца найбольш складанай часткай для разуменьня пачаткоўцаў, таму трымайцеся гэтых крокаў, каб зразумець:

Для кожнай часткі стану ў вашай прыкладной праграме:

  • Вызначце кожны кампанэнт, які нешта рэндэрыць на аснове гэтага стану.
  • Знайдзіце кампанэнт — агульны уладальнік (адзін кампанэнт вышэй за ўсе кампанэнты ў герархіі, якія маюць патрэбу ў стане).
  • Альбо агульны ўладальнік ці іншы кампанэнт вышэйшы ў герархіі, які павінен валодаць станам.
  • Калі вы ня можаце знайсьці кампанэнт, які ёсьць сэнс зрабіць уладальнікам стану, стварыце новы кампанэнт проста для трыманьня стану і дадайце яго дзе-небудзь у герархіі вышэй кампанэнта — агульнага ўладальніка.

Давайце разгледзім гэтую стратэгію для нашай прыкладной праграмы:

  • ProductTable мае патрэбу ў фільтраваньні сьпісу прадуктаў, грунтуючыся на стане, а SearchBar патрэбуе адлюстраваньня пошукавага тэксту ды стану сьцяжка.
  • Кампанэнтам — агульным уладальнікам зьяўляецца FilterableProductTable.
  • Канцэптуальна ёсьць сэнс, каб тэкст фільтра й значэньне сьцяжка жылі ў FilterableProductTable

Крута, вось мы вырашылі, што наш стан жыве ў FilterableProductTable. Па-першае, дадайце ўласьцівасьць экзэмпляра this.state = {filterText: '', inStockOnly: false} у constructor кампанэнта FilterableProductTable, каб адлюстраваць зыходны стан (initial state) вашай прыкладной праграмы. Затым, перадайце filterText ды inStockOnly да ProductTable ды SearchBar у якасьці ўласьцівасьці (prop). Нарэшце, выкарыстайце гэтыя ўласьцівасьці (props) для фільтрацыі радкоў у ProductTable ды ўсталяваньня значэньняў палёў формы ў SearchBar.

Вы можаце пачаць бачыць, як ваша прыкладная праграма будзе сябе паводзіць: прызначце filterText значэньне "ball" ды абнавіце прыкладную праграму. Вы ўбачыце, што табліца даных абноўлена правільна.

Крок 5: Дадайце адваротны паток даных

Глядзіце Pen Thinking In React: Step 5 on CodePen.

Дагэтуль, мы стварылі прыкладную праграму, якая карэктна рэндэрыць ў залежнасьці ад уласьцівасьцяў (props) ды стану (state), якія перадаюцца ўніз па герархіі. Цяпер настаў час падтрымаць паток даных у іншым накірунку: кампанэнтам формы ў глыбіні герархіі неабходна абнавіць стан у FilterableProductTable.

React робіць гэты паток даных ясным ды лёгкім для разуменьня, як працуе ваша праграма, але гэта патрабуе крыху больш набору з клявіятуры, чым традыцыйнае двухбаковае зьвязваньне даных.

Калі вы паспрабуеце набраць тэкст, або паставіць сьцяжок у цяперашняй вэрсіі прыкладу, то вы ўбачыце, што React ігнаруе ваш увод. Гэта так задумана, так як мы ўстанавілі ўласьцівасьць (prop) value для input заўсёды быць роўнай state, які перадаецца з FilterableProductTable.

Давайце падумаем аб тым, што мы хочам, каб адбылася. Мы хочам пераканацца, што кожны раз, калі карыстальнік зьмяняе форму, мы абнаўляем стан, каб адлюстраваць ўвод карыстальніка (user input). Паколькі кампанэнты павінны абнаўляць толькі свой уласны стан, то FilterableProductTable будзе праводзіць зваротныя выклікі да SearchBar, які спрацуе, калі гэты стан павінен быць абноўлены. Мы можам выкарыстоўваць падзею onChange ува ўводах (inputs) для паведамленьня пра гэта. Зваротныя выклікі, перададзеныя кампанэнтам FilterableProductTable, будуць выклікаць setState(), і прыкладная праграма будзе абнаўляцца.

Хоць гэта і здаецца складаным, але гэта сапраўды ўсяго толькі некалькі радкоў коду. І сапраўды ясна, як вашы даныя цякуць праз прыкладную праграму.

І гэта ўсё

Будзем спадзявацца, што гэта дае вам прадстаўленьне пра тое, як думаць аб стварэньні кампанэнтаў і прыкладных праграмаў з React. Хоць пры гэтым можа быць трохі больш набору з клявіятуры, чым вы прывыклі, памятайце, што код чытаецца значна часьцей, чым пішацца, а такі модульны, ясны код чытаць значна лягчэй. Калі вы пачняцё будаваць вялікія бібліятэкі кампанэнтаў, то вы па вартасьці ацэніце гэтую яснасьць ды модульнасьць, а за кошт паўторнага выкарыстаньня колькасьць радкоў коду, якія вам трэба будзе набіраць, пачне скарачацца. :)