React: отрисовка только необходимых компонент

Есть React компонент WordsEditor, который содержит сотни компонент WordBlock.

При изменении состояний WordsEditor происходит его перерисовка и, соответственно, перерисовка всем его компонент WordBlock, при том, что изменяется лишь несколько компонент WordBlock.

Подскажите как сделать так, чтобы перерисовывались лишь необходимые компоненты.

WordsEditor:

// компонент: 'Редактор слов'
const WordsEditor: React.FC<IPropsWordsEditor> = (props) => {
  // контроль состояний
  const [selectedIndices, setSelectedIndices]       = useState<number[]>([]);               // список индексов выделенных слов
  const [selectedAuxIndices, setSelectedAuxIndices] = useState<number[]>([]);               // список вспомогательных индексов выделенных слов
  const [categoriesGroups, setCategoriesGroups]     = useState<IDataCategoryGroup[]>([]);   // список сформированных групп слов

  // ОСНОВНАЯ ЛОГИКА
  // ...    
 
  // отрисовать компонент
  const toolbarIndex: number = toolbar_enabled && selectedIndices.length > 0 ? Math.min(...selectedIndices) : -1;

  const content: any = words.map((wordInfo, index) => {
    if (wordInfo.active) {
      const wordBlock: any = <WordBlock 
          key         = {index} 
          index       = {index} 
          word        = {wordInfo.word}
          main        = {selectedIndices.includes(index)}
          aux         = {selectedAuxIndices.includes(index)}
          type        = {categoriesGroups.find(elem => elem.ids.includes(index))?.sentiment ?? ''}
      />;

      if (index === toolbarIndex)
        return (
          <ToolbarPanel
            key             = {index}
            selected        = {selectedIndices}
            groups          = {categoriesGroups}
            onClose         = {handleDisableSelections}
            onDisableTones  = {handleDisableTonesSelections}
            onRegroup       = {handleRegroupSelections}
            onCategoryze    = {handleCategoryzeSelections}
          >
            {wordBlock}
          </ToolbarPanel>
        );
      else
        return wordBlock;
    }
    return wordInfo.word;
  });

  return (
    <div className='words-editor' onClick={handleWordsEditorClick}>
      {content}
    </div>
  );
}

WordBlock:

// компонент: 'Слово'
const WordBlock: React.FC<IPropsWordBlock> = (props) => {
  console.log('word');
  // функция определяющая тип класса
  function prepareClassName() {
    // определить название классов для разных состояний слова
    const main: string  = props.main ? 'selected' : '';
    const aux: string   = props.aux ? 'selected-aux' : '';
    const type: string  = props.type;

    // сформировать классы
    return [(main !== '') ? main : type, aux].join(' ').trim();
  }

  // отрисовать компонент
  return (
    <span
      data-index  = {props.index}
      className   = {prepareClassName()}
    >
      {props.word}
    </span>
  );
}

Ответы (1 шт):

Автор решения: ksa

При изменении состояний WordsEditor происходит его перерисовка и, соответственно, перерисовка всем его компонент WordBlock, при том, что изменяется лишь несколько компонент WordBlock. Подскажите как сделать так, чтобы перерисовывались лишь необходимые компоненты.

Можно применить меморизацию компонента. Т.о., если пропсы не менялись - компонент рендериться не будет.

const Itm = React.memo(props => {
  const d = props.data
  const test = d.test ? d.test.toString() : '?'
  console.log('Рендер', d.id)
  return <li>
    {d.name} - {test}
  </li>
})
//
function App() {
  const [itm, setItm] = React.useState(null)
  const [arr, setArr] = React.useState([])
  React.useEffect(_ => {
    const url = 'https://jsonplaceholder.typicode.com/users'
    fetch(url)
      .then(r => r.json())
      .then(a => {
        setArr(a)
      })
      .catch(console.error)
  }, [])
  const act = _ => {
    const i = Math.floor(Math.random() * arr.length)
    console.log('Меняем только', i)
    setItm(i)
    setArr(old => old.map((o, j) => i == j ? {...o, test: new Date()} : o))
  }
  console.log('Рендер App')
  return <section>
    {arr.length ? <button onClick={act}>Тест</button> : 'Получение данных...'}
    <ul>
      {arr.map(o => <Itm key={o.id} data={o} />)}
    </ul>
  </section>
}

const domContainer = document.querySelector('#like_button_container');
const root = ReactDOM.createRoot(domContainer);
root.render(<App />);
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<div id="like_button_container"></div>

→ Ссылка