Сьпісы (Lists) ды ключы (Keys)

Па-першае, давайце разгледзім, як можна пераўтвараць сьпісы ў JavaScript.

У прыведзеным ніжэй кодзе мы выкарыстоўваем функцыю map(), каб прыняць масіў numbers ды падвоіць іхныя значэньні. Новы масіў, які вяртаецца з дапамогай map(), мы прызначаем зьменнай doubled і запісваем яго:

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);

Гэты код запісвае [2, 4, 6, 8, 10] у кансоль.

У React’е пераўтварэньне масіваў у сьпісы элемэнтаў (elements) амаль ідэнтычнае.

Рэндэрынг складаных кампанэнтаў

Вы можаце ствараць калекцыі элемэнтаў і ўключаць іх у JSX, выкарыстоўваючы фігурныя дужкі {}.

Ніжэй мы перабіраем масіў numbers, выкарыстоўваючы JavaScript’аўскую функцыю map(). Мы вяртаем элемэнт <li> для кожнага элемэнта масіву (item). Нарэшце, атрыманы масіў элемэнтаў мы прызначаем listItems:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

Мы ўключаем увесь масіў listItems унутр элемэнта <ul> ды рэндэрым яго ў DOM:

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

Паспрабуйце на CodePen.

Гэты код выводзіць маркіраваны сьпіс лікаў ад 1 да 5.

Базавы кампанэнт сьпісу (List)

Звычайна вы можаце рэндэрыць сьпісы ўнутры кампанэнта.

Мы можам рэарганізаваць (refactor) папярэдні прыклад у кампанэнт, які прымае масіў numbers ды выводзіць неўпарадкаваны сьпіс элемэнтаў.

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

Пры выкананьні гэтага кода, вам будзе дадзена папярэджаньне аб тым, што для элемэнтаў сьпісу павінен быць прадстаўлены ключ (key). Ключ “key” — гэта спэцыяльны радковы атрыбут, які патрэбна вам уключыць пры стварэньні сьпісаў элемэнтаў. Чаму гэта важна — мы абмяркуем у наступным разьдзеле.

Давайце прызначым key элемэнтам нашага сьпіса ўнутры numbers.map() ды выправім праблему адсутнасьці ключа.

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

Паспрабуйце на CodePen.

Ключы (Keys)

Ключы дапамагаюць React вызначаць, якія элемэнты (items) былі зьмененыя, дададзеныя або выдаленыя. Ключы павінны надавацца элемэнтам унутры масіву, каб даць элемэнтам устойлівую ідэнтычнасьць:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

Лепшы спосаб падбору ключа — выкарыстаць радок, які адназначна ідэнтыфікуе элемэнт сьпіса сярод ягоных братніх элемэнтаў. Часьцей за ўсё ў якасьці ключоў вы будзеце выкарыстоўваць ідэнтыфікатары (IDs) з вашых даных:

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

Калі ў вас няма стабільных ідэнтыфікатараў (IDs) для элемэнтаў, якія рэндэрацца, то ў крайнім выпадку ў якасьці ключа вы можаце скарыстацца індэксам гэтых элемэнтаў:

const todoItems = todos.map((todo, index) =>
  // Рабіць так толькі, калі элемэнты ня маюць стабільных IDs
  <li key={index}>
    {todo.text}
  </li>
);

Мы не рэкамэндуем выкарыстоўваць індэксы ў якасьці ключоў, калі парадак элемэнтаў можа зьмяняцца. Гэта можа адмоўна паўплываць на прадукцыйнасьць і можа выклікаць праблемы са станам (state) кампанэнта. Каб спраўдзіцца, вось артыкул Робіна Пакорнага па паглыбленым тлумачэньні адмоўных наступстваў выкарыстаньня індэкса ў якасьці ключа. Калі вы ня выбралі відавочнага ключа, каб прызначыць яго элемэнтам сьпісу, тады React будзе па змаўчаньні выкарыстоўваць індэксы ў якасьці ключоў.

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

Выманьне кампанэнтаў з ключамі

Ключы маюць сэнс толькі ў кантэксьце навакольнага масіву.

Напрыклад, калі вы вымаеце кампанэнт ListItem, то вам трэба трымаць ключ на элемэнты <ListItem /> ў масіве, а не на элемэнт <li> у самым ListItem itself.

Прыклад: Няправільнае выкарыстаньне ключа

function ListItem(props) {
  const value = props.value;
  return (
    // Няправільна! Ня трэба ўказваць ключ тут:
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Няправільна! Ключ павінен быць указаны тут:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

Прыклад: Правільнае выкарыстаньне ключа

function ListItem(props) {
  // Правільна! Тут ня трэба ўказваць ключ:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Правільна! Ключ павінен быць указаны ўнутры масіву.
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

Паспрабуйце на CodePen.

Добрае эмпірычнае правіла складаецца ў тым, што элемэнтам ўнутры выкліку map() патрэбны ключы.

Ключы павінны быць унікальнымі толькі сярод братніх элемэнтаў (Siblings)

Ключы, якія выкарыстоўваюцца ў масівах, павінны быць унікальнымі сярод іхніх братніх элемэнтаў (siblings). Аднак, няма патрэбы, каб яны былі ўнікальнымі глябальна. Мы можам выкарыстоўваць адны і тыя ж ключы, калі робім два розныя масівы:

function Blog(props) {
  const sidebar = (
    <ul>
      {props.posts.map((post) =>
        <li key={post.id}>
          {post.title}
        </li>
      )}
    </ul>
  );
  const content = props.posts.map((post) =>
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  );
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  );
}

const posts = [
  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
  <Blog posts={posts} />,
  document.getElementById('root')
);

Паспрабуйце на CodePen.

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

const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);

У прыкладзе вышэй, кампанэнт Post можа чытаць props.id, але ня props.key.

Убудоўваньне map() у JSX

У вышэйпрыведзеных прыкладах мы абвяшчалі асобную зьменную listItems ды ўключалі яе ў JSX:

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

JSX дазваляе ўбудаваньне любога выразу ў фігурных дужках, каб мы маглі ўбудаваць у радок вынік map():

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
      )}
    </ul>
  );
}

Паспрабуйце на CodePen.

Часам гэта прыводзіць да больш выразнага кода, але такім стылем можна таксама злоўжываць. Як і ў JavaScript, вам вырашаць, ці варта рабіць выцягваньне для зручнасьці чытаньня. Майце на ўвазе, што калі цела map() мае занадта шмат укладаньняў, то гэта можа быць самы час, каб выцягнуць кампанэнт.