На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: ElcnU, ANDLL, fatalist
  
> React.render , Непонятки
    Доброго времени суток

    Столкнулся с одним непонятным мне поведением отрисовки компоненты. В моем примере есть компонента DataTable, в ней можно редактировать содержимое каждой ячейки таблицы, ниже приведен кусок кода (полный пример см на https://github.com/Cfon/react-render-bug). Так вот если вызвать внутрений компонент Table, как функцию в разметке(см в самом конце кода я отметил в коментарии), то если изменить данные в ячейке, то реакт обновляет тока редактируемую ячейку. А вот если вызвать компонент как тэг в разметке и попробовать редактировать данные, то обновляется вся таблица! :wacko:
    ExpandedWrap disabled
      import React, { useState } from 'react';
      import * as PropTypes from 'prop-types';
      import { ArrowDown, ArrowUp, NoArrow } from './Arrows';
      import { Editor } from './Editor';
       
      export const DataTable = props => {
          const [_data, setData] = useState(props.initialData);
          const [_sort, setSort] = useState({
              sortBy: null,
              desc: false,
          });
          const [_edit, setEdit] = useState(null);
       
          const handleSort = event => {
              const index = event.target.cellIndex;
              const { sortBy, desc } = _sort;
              const descending = sortBy === index && !desc;
              const dataCopy = [..._data];
       
              dataCopy.sort((a, b) => {
                  return descending
                      ? a[index] < b[index]
                          ? 1
                          : -1
                      : a[index] > b[index]
                      ? 1
                      : -1;
              });
       
              setData(dataCopy);
              setSort({
                  sortBy: index,
                  desc: descending,
              });
          };
       
          const handleShowEditor = event => {
              setEdit({
                  row: parseInt(event.target.dataset.row),
                  cell: event.target.cellIndex,
              });
          };
       
          const updateCell = value => {
              const { row, cell } = _edit;
              const dataCopy = [..._data];
              dataCopy[row][cell] = value;
              setData(dataCopy);
              setEdit(null);
          };
       
          const { headers } = props;
          const Table = () => {
              return (
                  <table className="table table-dark">
                      <thead onClick={handleSort}>
                          <tr>
                              {headers.map((title, index) => {
                                  const { sortBy, desc } = _sort;
                                  return (
                                      <th key={index}>
                                          {sortBy === index ? (
                                              desc ? (
                                                  <ArrowUp />
                                              ) : (
                                                  <ArrowDown />
                                              )
                                          ) : (
                                              <NoArrow />
                                          )}
                                          {title}
                                      </th>
                                  );
                              })}
                          </tr>
                      </thead>
                      <tbody onDoubleClick={handleShowEditor}>
                          {_data.map((row, rowIndex) => (
                              <tr key={rowIndex}>
                                  {row.map((cell, cellIndex) => {
                                      return (
                                          <td key={cell} data-row={rowIndex}>
                                              {_edit &&
                                              _edit.row === rowIndex &&
                                              _edit.cell === cellIndex ? (
                                                  <Editor
                                                      cell={cell}
                                                      update={updateCell}
                                                  />
                                              ) : (
                                                  cell
                                              )}
                                          </td>
                                      );
                                  })}
                              </tr>
                          ))}
                      </tbody>
                  </table>
              );
          };
       
          return (
              <div>
                  {/* - либо как разметка ---- */}
                  {/*<Table />*/}
                  {/* - либо как выражение ---- */}
                  {Table()}
              </div>
          );
      };
       
      DataTable.propTypes = {
          initData: PropTypes.arrayOf(PropTypes.string),
      };
       
      DataTable.defaultProps = {
          initialData: [],
      };

    Короче либо я чего то не даганяю либо это баг реакта :wall:
    Сообщение отредактировано: Cfon -
      Если вынести Table в отдельную компоненту то все пучком апдейтится 1 ячейка
      ExpandedWrap disabled
        import { Table } from './Table';
         
        export const DataTable = props => {
            ...
         
            const { headers } = props;
            return (
                <div>
                    <Table
                        headers={headers}
                        handleSort={handleSort}
                        _sort={_sort}
                        handleShowEditor={handleShowEditor}
                        _data={_data}
                        _edit={_edit}
                        updateCell={updateCell}
                    />
                </div>
            );
        };
        Никто не знает сам автор реакта не ведает сказал что будет думать :D
        Конкретно я озадачил реакт сообщество уже кирпичами какают :lool:
        Сообщение отредактировано: Cfon -
          Пока думал о текущей трабле обнаружил с коде другую грубейшую ошибку :D

          Я неправильно делал копию данных _data!
          ExpandedWrap disabled
            const dataCopy = [..._data];

          Тут создается неглубокая копия, а следовательно в функциях handleSort и updateCell данные изменялись непосредствено через _data.
          Сообщение отредактировано: Cfon -
            - Маста, а как сделать глубокую копию? :huh:

            Новичок на самом деле глубокую копию делать надо не всегда. Все зависит от глубины изменяемых данных :D

            - Это как? :scratch:

            Например, вот как правильно скопировать данные в функции updateCell (см комменты)
            ExpandedWrap disabled
              const updateCell = value => {
                  const { rowIndex: i, cellIndex: k } = _edit;
                  const dataCopy = [..._data];
                  const rowCopy = [..._data[i]]; // копируем внутренний массив ячеек
                  rowCopy[k] = value; // изменяем значение в ячейке
                  dataCopy[i] = rowCopy; // присваиваем массив измененых ячеек копии главного массива
                  setData(dataCopy);
                  setEdit({ rowIndex: null, cellIndex: null });
              };

            Есть более красивый способ это сделать с помощью линз Ramda
            ExpandedWrap disabled
              const updateCell = value => {
                  const { rowIndex: i, cellIndex: k } = _edit;
                  const cellLens = R.lensPath([i, k]);
                  const dataCopy = R.set(cellLens, value, _data);
                  setData(dataCopy);
                  setEdit({ rowIndex: null, cellIndex: null });
              };

            Ну или с помощью Immer или Immutable :D
            Сообщение отредактировано: Cfon -
              Разобрался! :D

              Если коротко, то в случае формы тэга <Table /> реакт создает реакт-элемент, который полностью отрисовывается, поскольку изменяется состояние внешнего компонента DataTable, а у компонента Table нет состояния.
              В случае выражения вызова функции {Table()} реакт-элемент не создается, а возвращаются внутренние реакт-элементы представляющие компонент Table и они становятся частью компонента DataTable, и когда изменяется состояние, то реакт-дом может определить только те части компонента которые требуют перерисовки :blush:

              Более подробно я опишу позже на более простом примером чем тот что на гите :D
              Сообщение отредактировано: Cfon -
                Зерте простой пример, запускайте сразу в броузере и инспектируйте разметку :D
                ExpandedWrap disabled
                  <!DOCTYPE html>
                  <html lang="en">
                  <head>
                      <meta charset="UTF-8">
                      <meta name="viewport" content="width=device-width, initial-scale=1.0">
                      <meta http-equiv="X-UA-Compatible" content="ie=edge">
                      <title>React Render Bug?</title>
                      <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
                      <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
                      <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
                      <script src="https://unpkg.com/moment@2.24.0/min/moment.min.js"></script>
                  </head>
                  <body>
                      <div id="root"></div>
                   
                      <script type="text/babel">
                          const getCurrentTime = () => moment().format('hh:mm:ss A.');
                   
                          const Timer = () => {
                              const [state, setState] = React.useState({
                                  time: getCurrentTime(),
                              });
                   
                              React.useEffect(() => {
                                  const id = setInterval(() => {
                                      setState({ time: getCurrentTime() });
                                  }, 1000);
                   
                                  return () => clearInterval(id);
                              }, []);
                   
                              const Output = () => {
                                  return (
                                      <div className="output">
                                          <h2>It is {state.time}</h2>
                                      </div>
                                  );
                              };
                   
                              return (
                                  <div className="timer">
                                      <h1>Hello, World!</h1>
                                      <Output />
                                      {/*{Output()}*/}
                                  </div>
                              );
                          };
                   
                          ReactDOM.render(<Timer />, document.querySelector('#root'))
                      </script>
                  </body>
                  </html>

                Мой комменты апосля :D
                  Давайте изучим этот перл
                  Кстати этот пример можно изи юзать как шаблон для фастовой проверки кода при изучении реакт.
                  Итак по строчно :D
                  1 getCurrentTime - функция возвращает текущее время
                  2 Далее идет определение функционального компонента Timer
                  3 инициализация стейта где сохраняет текущее время
                  4 инициализация эффекта в данном случае он имитирует componentDidMount и запускает таймет каждые 1000мс, возвращаемая функция иммитирует componentWillUnmount и отключает таймер
                  5 Далее наш предмет изучения определение функционального компонента Output
                  Он возвращает разметку в которой выводится текущее время.
                  6 Возврат разметки где выводится заголовок с Хело ворлд для того чтобы проще различать рендеринг внутренней разметки и внешней. Ну и далее выводится сам наш компонент Output, в виде реакт-элемента и в виде вызова функции (я его закомментировал)

                  Напомню тема такая наш Output в случае разметки при обновлении стейта полностью перерисовывается, т.е и заголовок Хелло Ворлд и текущее время, а в случае вызова функции (второй вариант) перерисовывается тока текущее время и это правильно.

                  Все пока :D
                  Сообщение отредактировано: Cfon -
                    Сорян все оказалось намного проще чем я тут развел! :lool:

                    Как всегда все оказалось просто :D
                    Наш функциональный компонент и есть метод render компонента! К чему я это веду?
                    Все дело в том что наш внутренний компонент Output на каждой отрисовке создается снова и поэтому реакт полностью его отрисовывает при каждом рендеринге. Логично? :D

                    Есть нюансы например что если перенести стейт внутрь Output?
                    ExpandedWrap disabled
                      const Timer = () => {
                          const Output = () => {
                              const [state, setState] = React.useState({
                                  time: getCurrentTime(),
                              });
                       
                              React.useEffect(() => {
                                  const id = setInterval(() => {
                                      setState({ time: getCurrentTime() });
                                  }, 1000);
                       
                                  return () => clearInterval(id);
                              }, []);
                       
                              return (
                                  <div className="output">
                                    <h2>It is {state.time}</h2>
                                  </div>
                              );
                          };
                       
                          return (
                              <div className="timer">
                                  <h1>Hello, World!</h1>
                                  <Output />            
                              </div>
                          );
                      };

                    Теперь все пучком в Output перерисовывается тока {state.time} те даже при том что определение компоненты занова перезаписывается тем не менее реакт определяет тока нужные изменения.
                    Сообщение отредактировано: Cfon -
                    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                    0 пользователей:


                    Рейтинг@Mail.ru
                    [ Script execution time: 0,0337 ]   [ 15 queries used ]   [ Generated: 29.03.24, 15:37 GMT ]