Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.131.110.169] |
|
Сообщ.
#1
,
|
|
|
Доброго времени суток
Столкнулся с одним непонятным мне поведением отрисовки компоненты. В моем примере есть компонента DataTable, в ней можно редактировать содержимое каждой ячейки таблицы, ниже приведен кусок кода (полный пример см на https://github.com/Cfon/react-render-bug). Так вот если вызвать внутрений компонент Table, как функцию в разметке(см в самом конце кода я отметил в коментарии), то если изменить данные в ячейке, то реакт обновляет тока редактируемую ячейку. А вот если вызвать компонент как тэг в разметке и попробовать редактировать данные, то обновляется вся таблица! 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: [], }; Короче либо я чего то не даганяю либо это баг реакта |
Сообщ.
#2
,
|
|
|
Если вынести Table в отдельную компоненту то все пучком апдейтится 1 ячейка
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> ); }; |
Сообщ.
#3
,
|
|
|
Никто не знает сам автор реакта не ведает сказал что будет думать
Конкретно я озадачил реакт сообщество уже кирпичами какают |
Сообщ.
#4
,
|
|
|
Пока думал о текущей трабле обнаружил с коде другую грубейшую ошибку
Я неправильно делал копию данных _data! const dataCopy = [..._data]; Тут создается неглубокая копия, а следовательно в функциях handleSort и updateCell данные изменялись непосредствено через _data. |
Сообщ.
#5
,
|
|
|
- Маста, а как сделать глубокую копию?
Новичок на самом деле глубокую копию делать надо не всегда. Все зависит от глубины изменяемых данных - Это как? Например, вот как правильно скопировать данные в функции updateCell (см комменты) 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 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 |
Сообщ.
#6
,
|
|
|
Разобрался!
Если коротко, то в случае формы тэга <Table /> реакт создает реакт-элемент, который полностью отрисовывается, поскольку изменяется состояние внешнего компонента DataTable, а у компонента Table нет состояния. В случае выражения вызова функции {Table()} реакт-элемент не создается, а возвращаются внутренние реакт-элементы представляющие компонент Table и они становятся частью компонента DataTable, и когда изменяется состояние, то реакт-дом может определить только те части компонента которые требуют перерисовки Более подробно я опишу позже на более простом примером чем тот что на гите |
Сообщ.
#7
,
|
|
|
Зерте простой пример, запускайте сразу в броузере и инспектируйте разметку
<!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> Мой комменты апосля |
Сообщ.
#8
,
|
|
|
Давайте изучим этот перл
Кстати этот пример можно изи юзать как шаблон для фастовой проверки кода при изучении реакт. Итак по строчно 1 getCurrentTime - функция возвращает текущее время 2 Далее идет определение функционального компонента Timer 3 инициализация стейта где сохраняет текущее время 4 инициализация эффекта в данном случае он имитирует componentDidMount и запускает таймет каждые 1000мс, возвращаемая функция иммитирует componentWillUnmount и отключает таймер 5 Далее наш предмет изучения определение функционального компонента Output Он возвращает разметку в которой выводится текущее время. 6 Возврат разметки где выводится заголовок с Хело ворлд для того чтобы проще различать рендеринг внутренней разметки и внешней. Ну и далее выводится сам наш компонент Output, в виде реакт-элемента и в виде вызова функции (я его закомментировал) Напомню тема такая наш Output в случае разметки при обновлении стейта полностью перерисовывается, т.е и заголовок Хелло Ворлд и текущее время, а в случае вызова функции (второй вариант) перерисовывается тока текущее время и это правильно. Все пока |
Сообщ.
#9
,
|
|
|
Сорян все оказалось намного проще чем я тут развел!
Как всегда все оказалось просто Наш функциональный компонент и есть метод render компонента! К чему я это веду? Все дело в том что наш внутренний компонент Output на каждой отрисовке создается снова и поэтому реакт полностью его отрисовывает при каждом рендеринге. Логично? Есть нюансы например что если перенести стейт внутрь Output? 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} те даже при том что определение компоненты занова перезаписывается тем не менее реакт определяет тока нужные изменения. |