Как сгенерировать уникальные идентификаторы для меток форм в React?

129

У меня есть элементы формы с labels, и я хочу иметь уникальные идентификаторы для связывания labels с элементами с htmlForатрибутом. Что-то вроде этого:

React.createClass({
    render() {
        const id = ???;
        return (
            <label htmlFor={id}>My label</label>
            <input id={id} type="text"/>
        );
    }
});

Раньше я генерировал идентификаторы на основе, this._rootNodeIDно он недоступен с React 0.13. Как лучше и / или проще это сделать сейчас?

Артем Сапегин
источник
если вы создаете этот элемент снова и снова, я предполагаю, что в операторе for почему бы не использовать для него итератор? Я полагаю, вы также можете вызвать функцию, которая генерирует уникальный guid, если номер индекса недостаточно хорош. stackoverflow.com/questions/105034/…
Крис Хоукс
1
В разных компонентах есть много разных элементов формы, и все они должны иметь уникальные идентификаторы. Функция генерации идентификаторов - это то, о чем я думал и что я собираюсь делать, если никто не предложит лучшего решения.
Артем Сапегин
3
Вы можете где-нибудь сохранить "глобальный" счетчик увеличения и использовать его. id = 'unique' + (++GLOBAL_ID);где var GLOBAL_ID=0;?
WiredPrairie
1
Я знаю, что очень, очень опаздываю на эту вечеринку, но другая альтернатива - заключить ввод в метку вместо использования идентификаторов, например:<label>My label<input type="text"/></label>
Майк Дежарден

Ответы:

85

Эти решения отлично подходят для меня.

utils/newid.js:

let lastId = 0;

export default function(prefix='id') {
    lastId++;
    return `${prefix}${lastId}`;
}

И я могу использовать это так:

import newId from '../utils/newid';

React.createClass({
    componentWillMount() {
        this.id = newId();
    },
    render() {
        return (
            <label htmlFor={this.id}>My label</label>
            <input id={this.id} type="text"/>
        );
    }
});

Но в изоморфных приложениях это не сработает.

Добавлено 17.08.2015 . Вместо настраиваемой функции newId вы можете использовать uniqueId из lodash.

Обновлено 28.01.2016 . ID лучше генерировать в формате componentWillMount.

Артем Сапегин
источник
3
Потому что он снова начнет генерировать идентификаторы с 1-го числа в браузере. Но на самом деле вы можете использовать разные префиксы на сервере и в браузере.
Артем Сапегин
7
Не делайте этого вовнутрь render! Создайте идентификатор вcomponentWillMount
sarink
1
Вы создали контейнер с отслеживанием состояния, но пренебрегаете использованием setState и нарушаете спецификацию для render. facebook.github.io/react/docs/component-specs.html . Хотя это должно быть довольно легко исправить.
aij
3
Я использую uniqueId из lodash в конструкторе и использую setState для установки идентификатора. Хорошо работает для моего клиентского приложения.
CrossProduct
1
componentWillMountустарел, сделайте это в конструкторе. См. Responsejs.org/docs/react-component.html#unsafe_componentwillmount
Вик,
79

Идентификатор должен быть помещен внутри componentWillMount (обновление на 2018 год) constructor, а не render. Помещая это вrender приведет к повторному созданию новых идентификаторов без необходимости.

Если вы используете подчеркивание или lodash, есть uniqueIdфункция, поэтому ваш результирующий код должен быть примерно таким:

constructor(props) {
    super(props);
    this.id = _.uniqueId("prefix-");
}

render() { 
  const id = this.id;
  return (
    <div>
        <input id={id} type="checkbox" />
        <label htmlFor={id}>label</label>
    </div>
  );
}

Обновление хуков 2019:

import React, { useState } from 'react';
import _uniqueId from 'lodash/uniqueId';

const MyComponent = (props) => {
  // id will be set once when the component initially renders, but never again
  // (unless you assigned and called the second argument of the tuple)
  const [id] = useState(_uniqueId('prefix-'));
  return (
    <div>
      <input id={id} type="checkbox" />
      <label htmlFor={id}>label</label>
    </div>
  );
}
sarink
источник
11
Или вы также можете поместить его в конструктор.
Джон Вайс
componentWillMount устарел с React 16.3.0, вместо него используйте UNSAFE_componentWillMount, см. responsejs.org/docs/react-component.html#unsafe_componentwillmount
lokers
2
Может ли кто-нибудь подсказать, как это должно быть сделано с новыми хуками в React 16.8?
Aximili 06
4
Поскольку вы не отслеживаете значение идентификатора, вы также можете использоватьconst {current: id} = useRef(_uniqueId('prefix-'))
forivall
1
В чем разница с использованием useRef вместо использования State?
XPD
24

По состоянию на 04.04.2019, похоже, это можно сделать с помощью React Hooks useState:

import React, { useState } from 'react'
import uniqueId from 'lodash/utility/uniqueId'

const Field = props => {
  const [ id ] = useState(uniqueId('myprefix-'))

  return (
    <div>
      <label htmlFor={id}>{props.label}</label>
      <input id={id} type="text"/>
    </div>
  )      
}

export default Field

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

Значение idбудет myprefix-<n>где <n>- возвращаемое целочисленное значение uniqueId. Если это недостаточно для вас, подумайте о создании своего собственного лайка.

function gen4() {
  return Math.random().toString(16).slice(-4)
}

function simpleUniqueId(prefix) {
  return (prefix || '').concat([
    gen4(),
    gen4(),
    gen4(),
    gen4(),
    gen4(),
    gen4(),
    gen4(),
    gen4()
  ].join(''))
}

или просмотрите опубликованную мной библиотеку здесь: https://github.com/rpearce/simple-uniqueid . Существуют также сотни или тысячи других уникальных идентификаторов, но lodash uniqueIdс префиксом должно быть достаточно для выполнения работы.


Обновление 2019-07-10

Спасибо @Huong Hk за указание на ленивое начальное состояние хуков , сумма которого состоит в том, что вы можете передать функцию, useStateкоторая будет запускаться только при начальном монтировании.

// before
const [ id ] = useState(uniqueId('myprefix-'))

// after
const [ id ] = useState(() => uniqueId('myprefix-'))
rpearce
источник
1
У меня такие же проблемы с серверным рендерингом, как и у многих других методов, упомянутых на этой странице: компонент будет повторно отображен с новым идентификатором в браузере.
Артем Сапегин
@ArtemSapegin: в проекте React возникла проблема ( github.com/facebook/react/issues/1137 ), в которой обсуждалась возможность каким-то образом иметь компоненты с уникальными идентификаторами, но я не думаю, что из этого что-то вышло. Насколько важно то, что сгенерированные идентификаторы одинаковы для сервера и клиента? Я думаю , что для того <input />, что бы дело в том , что htmlForи idатрибуты должны быть связаны друг с другом независимо от того , какие значения.
rpearce 05
Важно избегать ненужных обновлений DOM, которые могут вызвать новые идентификаторы.
Артем Сапегин
6
Лучше, если вы предоставите функцию как initialState№1 const [ id ] = useState(() => uniqueId('myprefix-'))вместо результата функции №2 const [ id ] = useState(uniqueId('myprefix-')) . Состояние: idдвух вышеуказанных способов не будет отличаться. Но другой uniqueId('myprefix-')будет выполняться один раз (# 1) вместо каждого повторного рендеринга (# 2). См .: Ленивое начальное состояние: reactjs.org/docs/hooks-reference.html#lazy-initial-state Как лениво создавать дорогие объекты ?: reactjs.org/docs/…
Huong Nguyen
1
@HuongHk, это потрясающе; Я не знал! Я
обновлю
4

Вы можете использовать такую ​​библиотеку, как node-uuid чтобы получить уникальные идентификаторы.

Установить с помощью:

npm install node-uuid --save

Затем в вашем компоненте реакции добавьте следующее:

import {default as UUID} from "node-uuid";
import {default as React} from "react";

export default class MyComponent extends React.Component {   
  componentWillMount() {
    this.id = UUID.v4();
  }, 
  render() {
    return (
      <div>
        <label htmlFor={this.id}>My label</label>
        <input id={this.id} type="text"/>
      </div>
    );
  }   
}
Стюарт Эрвин
источник
3
Impure renderнарушает facebook.github.io/react/docs/component-specs.html
aij
2
Ответ, кажется, был обновлен, чтобы соответствовать спецификации
Йонас Берлин
2
Это не работает в изоморфных приложениях, поскольку идентификатор, созданный на сервере, отличается от идентификатора, созданного на клиенте.
Дэниел Т.
2
Но это указано как часть ответа, который вводит в заблуждение
Том Маккензи
1
Да, -1 за использование УНИВЕРСАЛЬНО уникальных идентификаторов, это молоток размером со вселенную для гвоздя мирового размера.
Jon z
1

Надеюсь, это будет полезно для всех, кто ищет универсальное / изоморфное решение, поскольку проблема с контрольной суммой - это то, что в первую очередь привело меня сюда.

Как было сказано выше, я создал простую утилиту для последовательного создания нового идентификатора. Поскольку идентификаторы продолжают увеличиваться на сервере и начинаются с 0 в клиенте, я решил сбрасывать приращение при каждом запуске SSR.

// utility to generate ids
let current = 0

export default function generateId (prefix) {
  return `${prefix || 'id'}-${current++}`
}

export function resetIdCounter () { current = 0 }

А затем в конструкторе корневого компонента или componentWillMount вызовите сброс. Это по существу сбрасывает область JS для сервера при каждом рендеринге сервера. В клиенте это не имеет (и не должно) иметь никакого эффекта.

tenor528
источник
у вас все еще могут быть конфликты идентификаторов, если клиенты снова начнут именовать входы с 0.
Tomasz
@Tomasz, вы хотите, чтобы клиент начал с формы 0, чтобы контрольные суммы совпадали.
tenor528 06
0

Для обычного использования labelи inputпроще поместить ввод в метку следующим образом:

import React from 'react'

const Field = props => (
  <label>
    <span>{props.label}</span>
    <input type="text"/>
  </label>
)      

Это также позволяет в флажках / переключателях применять отступы к корневому элементу и по-прежнему получать обратную связь при нажатии на ввод.

Developia
источник
1
+1 для простоты и полезен в некоторых случаях, -1 не может использоваться, например select, с несколькими метками в разных позициях, несвязанными компонентами пользовательского интерфейса и т. Д., Также рекомендуется использовать идентификаторы a11y: Как правило, явные метки лучше поддерживаются вспомогательной технологией, w3. org / WAI / tutorials / forms / labels /…
Майкл Б.
-1

Я нашел такое простое решение:

class ToggleSwitch extends Component {
  static id;

  constructor(props) {
    super(props);

    if (typeof ToggleSwitch.id === 'undefined') {
      ToggleSwitch.id = 0;
    } else {
      ToggleSwitch.id += 1;
    }
    this.id = ToggleSwitch.id;
  }

  render() {
    return (
        <input id={`prefix-${this.id}`} />
    );
  }
}
Ozzie
источник
-1

Другой простой способ с машинописным текстом:

static componentsCounter = 0;

componentDidMount() {
  this.setState({ id: 'input-' + Input.componentsCounter++ });
}
Лукас Мояно Анджелини
источник
2
Это возможно без TypeScript
ChrisBrownie55
-1

Создаю модуль генератора uniqueId (Typescript):

const uniqueId = ((): ((prefix: string) => string) => {
  let counter = 0;
  return (prefix: string): string => `${prefix}${++counter}`;
})();

export default uniqueId;

И используйте модуль top для генерации уникальных идентификаторов:

import React, { FC, ReactElement } from 'react'
import uniqueId from '../../modules/uniqueId';

const Component: FC = (): ReactElement => {
  const [inputId] = useState(uniqueId('input-'));
  return (
    <label htmlFor={inputId}>
      <span>text</span>
      <input id={inputId} type="text" />
    </label>
  );
};     
Масих Джахангири
источник
-3

Не используйте идентификаторы вообще, если они вам не нужны, вместо этого оберните ввод такой меткой:

<label>
   My Label
   <input type="text"/>
</label>

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

Майк Дежарден
источник
2
Хотя это поддерживается HTML5, это не рекомендуется для доступности: «Однако даже в таких случаях рекомендуется устанавливать атрибут for, потому что некоторые вспомогательные технологии не понимают неявных отношений между метками и виджетами». - с
сайта
1
Этот способ рекомендует команда React в соответствии с документами, найденными на responsejs.org/docs/forms.html
Blake Plumb
1
Команда @BlakePlumb React также имеет раздел доступных форм: reactjs.org/docs/accessibility.html#accessible-forms
Вик