Интерпретация исходного кода и анализ спроса табличного компонента в element-ui

JavaScript

Введение

element-ui с открытым исходным кодом стал интерфейсомСредняя и закулисная системаСамый популярный UI-фреймворк в .

Если Vue, React и Angular являются тремя мушкетёрами фронтенда, то можно сказать, что element-ui занимает половину полей мидл и бэкэнд, с 43 тысячами звёзд на github. На данный момент в нем 84 компонента (версия 2.13.0).

Первые две строки пустые, начиная со строки 2.

2. Причина

Требование: В связи с потребностями бизнеса компании часто на странице есть формы, которые нужно выбрать (проверить), а затем проверенные id собрать в строки и отправить в фон.

Решение: Посмотрите документацию на официальном сайте element-ui, вы можете найти ее вtableКомпонент находит реализациюФорма множественного выборапуть вtableдобавить один к компонентуtypeдляselectionколонка подойдет.

<el-table
    ref="multipleTable"
    :data="tableData"
    tooltip-effect="dark"
    style="width: 100%"
    @selection-change="handleSelectionChange">
    <el-table-column
      type="selection"
      width="55">
    </el-table-column>
</el-table>

Сотрудничатьselection-changeсобытие для получения массива строк, выбранных пользователем.

Эффект следующий:

Все выглядит идеально, но на практике все обстоит иначе.

Почему ты это сказал? Поскольку в реальном бизнесе компании таблица является таблицей подкачки, каждый раз, когда переключается номер страницы, происходит повторная выборка данных и повторная визуализация таблицы, возникает первая проблема:Выбранная пользователем строка в таблице с номером страницы 1 исчезает после переключения номера страницы.

Таблица с разбивкой на страницы должна выглядеть так:

Итак, я пошел на официальный сайт element-ui, чтобы снова просмотреть документацию, вtableВ компоненте был найден методtoggleRowSelection, этот метод может переключать выбранное состояние определенной строки в таблице.

С помощью этого метода после получения данных таблицы мы сразу же используем этот метод для установки ранее выбранных данных, чтобы выбранное состояние ранее выбранной строки также могло отображаться при переключении пользователя?

Яма и сразу прийти! !

потому что черезselection-changeСобытие получаетselectionМассив, содержащий информацию о строке, выбранной пользователем. Сохраняем этот массив в переменной, чтобы пользователь все еще мог видеть ранее выбранную строку после переключения номера страницы, а затем передаемtoggleRowSelectionМетод устанавливает выбранную информацию для строки.

selection.forEach(row => {
    this.$refs.multipleTable.toggleRowSelection(row);
});

На первый взгляд это не проблема, но в таблице это не имеет никакого эффекта проверки! ! ?

Все виды бывших матерей, которые, как говорят,nextTickдля вызова этого метода:

this.$nextTick(() => {
    selection.forEach(row => {
        this.$refs.multipleTable.toggleRowSelection(row);
    });
});

Ну нет ошибки, открой страницу и посмотри, а? ? Почему до сих пор не выбрали! ! !

В моем сердце трава Нима прошла мимо. . . .

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

Сыграйте на струне. . .

Успокоившись на некоторое время, я решил открыть исходный код element-ui, чтобы посмотреть, как табличный компонент оценивает выбор?

3. Анализ исходного кода

Это только часть исходного кода табличного компонента.

3.1 Структура

Сначала посмотрите на структуру табличного компонента

Структура такая: внешний index.js используется для экспорта этого табличного модуля, код внутри тоже очень простой и понятный.

// index.js
import ElTable from './src/table';

/* istanbul ignore next */
ElTable.install = function(Vue) {
  Vue.component(ElTable.name, ElTable);
};

export default ElTable;

Затем src содержит папку хранилища и некоторые компоненты таблицы: тело, столбец, нижний колонтитул, заголовок, макет и т. д., файл класса инструмента util.js, файл конфигурации config.js и выпадающий список (не понимаю), макет- наблюдатель (судя по названию, он для слежения за раскладкой), фильтр-панель (для фильтрации) наверное так.

Код в папке хранилища реализует частный Vuex, который используется только для обмена данными между компонентами в табличном компоненте.

3.2 Найдите это

В соответствии с моими потребностями мне нужно увидеть только часть исходного кода о выборе. Итак, из макета я могу начать с колонны и руки, то естьtable-column.jsэтот файл.

Но посмотриtable-column.jsТам действительно есть какой-то контент о столбце, но буквально не нашел код функции части выбора.

Поэтому я пока отказываюсь смотреть с макета, я смотрю прямо из метода:toggleRowSelection. Используйте метод поиска в этой папке таблицы для прямого поиска ключевых слов.toggleRowSelection,существуетsrc/store/watcher.jsможно найти в следующем:

// watcher.js 158行
toggleRowSelection(row, selected, emitChange = true) {
  const changed = toggleRowStatus(this.states.selection, row, selected);
  if (changed) {
    const newSelection = (this.states.selection || []).slice();
    // 调用 API 修改选中值,不触发 select 事件
    if (emitChange) {
      this.table.$emit('select', newSelection, row);
    }
    this.table.$emit('selection-change', newSelection);
  }
}

Этот метод заключается в том, чтобы выставить снаружи для нас вызов, первая строка - основная информация, вызовtoggleRowStatusзатем получите измененное значение, а затем поместите это значениеemitвыйди. Это, вероятно, процесс, затем отtoggleRowStatusИдти.

Обратите внимание, что в первой строкеthis.states.selectionбудет ключом к последующим действиям.

Поиск прямых ключевых слов, вы можете найти этот метод внешний для экспорта ссылок.

import { getKeysMap, getRowIdentity, getColumnById, getColumnByKey, orderBy, toggleRowStatus } from '../util';

Открытьutil.jsфайл успешно нашел следующий код:

export function toggleRowStatus(statusArr, row, newVal) {
  let changed = false;
  const index = statusArr.indexOf(row);
  const included = index !== -1;

  const addRow = () => {
    statusArr.push(row);
    changed = true;
  };
  const removeRow = () => {
    statusArr.splice(index, 1);
    changed = true;
  };

  if (typeof newVal === 'boolean') {
    if (newVal && !included) {
      addRow();
    } else if (!newVal && included) {
      removeRow();
    }
  } else {
    if (included) {
      removeRow();
    } else {
      addRow();
    }
  }
  return changed;
}

Разобраться несложно, название метода означает: переключить состояние линии. Есть два метода, одинaddRow,ОдинremoveRow, оба буквально. Основная функция реализации: оценить, является ли это новым значением (newVal), если оно не существует (!included), добавить его, иначе удалить. В основном это получение значения индекса, которое очень просто и грубо и может использоваться напрямую.Array.prototype.indexOfСудить и думать, не здесь ли причина?

Array.prototype.indexOf():方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

  • Яма 1: если этот элемент является объектом, каждый должен знать, что объект является ссылочным типом, то есть, используя indexOf для суждения, можно только судить, совпадает ли адрес ссылки на объект, и не может судить, является ли значение внутри не то же самое.

Однако, после тщательного рассмотрения, это, кажется, не имеет значения здесь. Конкретное мышление: мы инициализируем 10 фрагментов данных в таблице.В это время выбор массива, используемый для хранения выбранной строки в компоненте таблицы, сначала пуст, а затем мы вызываем toggleRowSelection, чтобы активно установить выбранную строку, и эти строки ставятся в Выделение в табличном компоненте находится в (через метод addRow в этом toggleRowStatus).

Уже вставлено, почему бы не отобразить соответствующее состояние! ! ?

Теперь, когда вы знаете, что компонент таблицы сохраняет выбранную строку посредством выбора, выполните поиск выбора.

Получил 78 результатов в 7 файлах. Результатов слишком много, а они нам не нужны.

Затем дальнейший поиск с полным совпадением:

Получил 38 результатов в 5 файлах.

Это несколько сужает рамки, но это все равно много, а выхода нет. Файл найден.

Чисто по распоряжению Vscode первый файл этоconfig.jsдокумент.

Это ключевое слово встречается в файле config.js 4 раза.Вы можете видеть, что первые два совпадения — это стиль, а не то, что нам нужно.

Следующие два достойны внимания.

// config.js 29行
// 这些选项不应该被覆盖
export const cellForced = {
  selection: {
    renderHeader: function(h, { store }) {
      return <el-checkbox
        disabled={ store.states.data && store.states.data.length === 0 }
        indeterminate={ store.states.selection.length > 0 && !this.isAllSelected }
        nativeOn-click={ this.toggleAllSelection }
        value={ this.isAllSelected } />;
    },
    renderCell: function(h, { row, column, store, $index }) {
      return <el-checkbox
        nativeOn-click={ (event) => event.stopPropagation() }
        value={ store.isSelected(row) }
        disabled={ column.selectable ? !column.selectable.call(null, row, $index) : false }
        on-input={ () => { store.commit('rowSelectedChanged', row); } } />;
    },
    sortable: false,
    resizable: false
  }
  // ...省略
}

Выложил только полезное, от больших ушей, экспортировал модуль под названиемcellForced, хотя я не знаю, что это значит. (Не удалось пройти четвертый уровень, разбили, разбили Хуэй). Но я могу понять две функции внутри, см.renderКлючевое слово, не это ли означает рендеринг?Загляни внутрь, о боже, счастье! ! Существуютel-checkboxЭтот компонент не тот столбец в режиме нескольких выбора? (В дополнение к этому место в таблице это невозможно поставить!).

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

indeterminate={ store.states.selection.length > 0 && !this.isAllSelected }

Анализ: store.states.selection: я являюсь коллекцией массивов с выбранной строкой.

Собственно, тогда и смотрим третий файл результатов поиска: watcher.js, его очевидно найти:

А в пятом файле: table.vue для выбора карты используется mapStates, и его тень тоже можно найти:

Затем эти два файла остаются в покое, потому что мы нашли расположение макета еще в config.js:

// config.js 29行
// 这些选项不应该被覆盖
export const cellForced = {
  selection: {
    renderHeader: function(h, { store }) {
      return <el-checkbox
        disabled={ store.states.data && store.states.data.length === 0 }
        indeterminate={ store.states.selection.length > 0 && !this.isAllSelected }
        nativeOn-click={ this.toggleAllSelection }
        value={ this.isAllSelected } />;
    },
    renderCell: function(h, { row, column, store, $index }) {
      return <el-checkbox
        nativeOn-click={ (event) => event.stopPropagation() }
        value={ store.isSelected(row) }
        disabled={ column.selectable ? !column.selectable.call(null, row, $index) : false }
        on-input={ () => { store.commit('rowSelectedChanged', row); } } />;
    },
    sortable: false,
    resizable: false
  }
  // ...省略
}

Всего используются две функции рендеринга, одна для рендеринга заголовка и одна для рендеринга сетки.По значению атрибута компонента el-checkbox мы можем определить, что он находится в строке 41:

value={ store.isSelected(row) }

Эта строка является ключом к отображению выбранного или нет. Внутри простой логики он вызывает метод с именем:isSelected, также говорит нам, чтоstoreметод в .

ок, найди папку магазина и найди ееisSelectedКлючевые слова, в watcher.js мы находим это:

// watcher.js 120行
// 选择
isSelected(row) {
  const { selection = [] } = this.states;
  return selection.indexOf(row) > -1;
},

Внутренняя логика еще проще: извлеките массив выбора, в котором хранится выбранная строка, а затем верните значение, если позиция строки в выборе больше -1. Обратитесь к содержимому в функции рендеринга, если возвращаемое значение истинно, рендеринг выбран, в противном случае он не выбран;

  • Яма 2: снова используетсяindexOfОпределяет, находится ли объект в массиве.

Здесь очень смертельно, почему ты так говоришь?

потому чтоselectionдействительно хранится вtoggleRowSelectionУстановите входящую строку. Но когдаisSelectedсерединаСтрока параметра формыОттабличный компонентсерединаpropsсерединаdataПрошло более.

dataОн получается путем повторного запроса интерфейса, поэтому строка, хранящаяся в строке, и выборка в данных не являются строкой.

Почему это предложение звучит так?Возвращаясь к самому основному, строка — это объект, это ссылочный тип, до тех пор, пока ссылочные адреса разные, тогда вы — не вы.

Хотя с точки зрения структуры данных и содержимого эти две строки одинаковы, если предположить следующее:

const row1 = { name: '1', id: 0, code: 110110, area: '北京市', street: '二环' }
const row2 = { name: '1', id: 0, code: 110110, area: '北京市', street: '二环' }

строка1 и строка2 не равны.

Но согласно нашему реальному бизнесу, row1 и row2 имеют одинаковую структуру и один и тот же идентификатор, это одно и то же.

Возьмем более практический пример: ваш второй дядя вышел из дома с черепичной крышей в деревне, и вы узнали в нем своего второго дядю; вы видели, как ваш второй дядя выходил из какой-то части в определенном районе на Восточной Второй Кольцевая дорога в Пекине, и ваш второй дядя Дядя он вам не второй дядя. Это нелепо! !

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

Чтобы найти корень проблемы, это также довольно легко решить.

В-четвертых, решение

  • 1. Дождитесь обновления Element-UI, некоторые люди решают, упоминают проблему.
  • 2. Сравните строку, хранящуюся в переменной, со строкой в ​​текущих данных таблицы, чтобы получить позицию выбранной строки в текущих данных таблицы, а затем используйте форму toggleRowSelection(tableData[index]), чтобы убедиться, что вы дядя твой второй дядя.
  • 3. Инкапсулируйте компонент формы множественного выбора самостоятельно, вместо встроенного выбора вы можете реализовать эту функцию самостоятельно (см. область комментариевshaonialifeМетод детской обуви, очень хорошо).
  • 4. Перепишите метод Array.prototype.indexOf, чтобы внутренняя логика оценки выполняла глубокое сравнение во время объекта.
  • 5. Установив для ключа строки в el-table значение id, а затем установив для резервного выбора столбца с type="selection" значение true, при переключении номера страницы значение, выбранное предыдущим номером страницы, будет быть сохраненным ( в разделе комментариевХан__Друзья, спасибо) Однако следует отметить, что если вам нужно проверять по умолчанию, вам все равно нужно судить о позициях строк, которые проверяются по умолчанию, в источнике данных tableData, а затем устанавливать их через toggleRowSelection.

Вышеупомянутые методы 1, 2, 3 и 4 были реализованы мной, и они варьируются в зависимости от конкретных потребностей бизнеса.

Если сравнивать по глубине, я реализовал только один слой. Моя идея заключается в том, чтобы сначала сравнить количество значений ключа, а затем судить, дается ли ключ вашего второго дяди вашему дяде, существует ли этот атрибут и равны ли значения в нем. Потому что мои бизнес-данные имеют только один слой значений атрибутов.

4.1 Первый - отрисовать новый el-checkbox сам по себе (shaonialifeПредлагается детская обувь):

// 通过自己渲染一个新的<el-checkbox />
<el-table-column
  :align="tableColumn.align"
  type="index"
  label="序号"
  width="70">
  <template slot-scope="scope">
    <el-checkbox :value="!!checkedRowIds[scope.row.id]" @change="(val) => { toggleRowSelection(val, scope.row) }"/>
  </template>
</el-table-column>

checkedRowIdsэто объект, который содержитkeyдляidколлекция, например:

const checkedRowIds = {
    0: true,
    1: true,
    2: false
}

Когда row.id = 1, checkedRowIds[row.id] эквивалентен форме checkedRowIds.1, это значение равно true, и проверенное состояние визуализируется как true.

Затем давайте посмотрим на функцию обратного вызова toggleRowSelection события изменения.

toggleRowSelection(val, row) {
  const { checkedRowIds } = this.$props
  const { id } = row
  
  this.$set(checkedRowIds, id, val)
  
  const includes = checkedRowIds.hasOwnProperty(id)
  
  const remove = () => delete checkedRowIds[id]
  if (!val && includes) remove()
  
  const keys = Object.keys(checkedRowIds)
  const arrToString = arr => arr.join(',')
  
  const ids = arrToString(keys)
  // ids: 1,2,3
},

Сначала деконструируйте, чтобы получить CheckRowIds и row.id, а затем передайтеthis.$setМетод устанавливает элемент, ключ которого равен id, а значение — val, в набор объектов checkedRowIds.

Затем оцените, является ли val ложным и существует ли ключ в checkedRowIds, и удалите это свойство, если выполняются два условия. Наконец, получите набор всех ключей в checkedRowIds с помощью Object.keys(), а затем используйте метод join() для формирования строковых идентификаторов.

Анализ: Этот метод очень хорош,!!checkedRowIds[scope.row.id]Это предложение очень короткое и лаконичное, очень красивое. Тогда очень удобно проверять дефолтную проверку на странице входа, пока в CheckRowIds есть такие, проверка будет отображаться, и нет необходимости вызывать настройку toggleRowSelection.

4.2 Вторая комбинация строки-ключа и резерва-выбора (хан__детская обувь)

// 利用row-key与reserve-selection的组合拳
 <el-table
  v-loading="loading"
  :data="tableData"
  row-key="id"
  @selection-change="rowChange"
>

  <el-table-column
    :reserve-selection="true"
    type="selection"
    width="55"/>
    
</el-table>

уже настроенrow-keyПосле идентификатора каждая строка имеет неуникальное значение ключа, а затем передаетсяreserve-selectionПоле сохраняет результат каждого выбора, так что независимо от того, как переключается номер страницы, может быть сохранено предыдущее значение, выбранное пользователем.

Анализ: этот метод очень прост, и в основном это официально предоставленный атрибут, что неплохо без слишком большого изменения кода. Но после входа на страницу для отображения проверенной по умолчанию строки нужно сравнить ее с данными в tableData, а затем задать через метод toggleRowSelection.

5. Напишите это сзади

Я добился успеха, потому что стоял на плечах гигантов. ———— Ньютон

Большое спасибо за ваш мозговой штурм. Я внимательно читаю каждое сообщение в области комментариев и буду практиковать каждый план. Спасибо. Надеюсь пройти дальше по этому пути.