title: Poor performance in Vue v2.6
body: <p>Consider this:</p>
  <pre><code>&lt;table class=&quot;v-gridView&quot; style=&quot;width:100%;&quot;&gt;   
      &lt;template v-for=&quot;(row, idx) in rows&quot;&gt;                                                
          &lt;tr :key=&quot;row.key&quot; :class=&quot;rowClass(row, idx)&quot; :style=&quot;rowStyle(row)&quot;&gt;
              &lt;td v-for=&quot;col in cols&quot; :key=&quot;col.field&quot; :class=&quot;cellClass(row, col)&quot; :style=&quot;cellStyle(row, col)&quot; @click=&quot;cellClick(row, col)&quot;&gt;
                  {{formatField(row, col)}}
              &lt;/td&gt;
          &lt;/tr&gt;                        
      &lt;/template&gt;
  &lt;/table&gt;
  </code></pre>
  <p>And script:</p>
  <pre><code>let vm = new Vue({
      el: '#vmPage',
      data: {
          cols: [
              { name: 'Col1', field: 'a', ... },
              { name: 'Col2', field: 'b', ... },
              { name: 'Col3', field: 'c', ... },
          ],
          rows: [
              { key: 1, a: 'A', b: 'B', c: 'C', ... },
              { key: 2, a: 'D', b: 'E', c: 'F', ... },
              { key: 3, a: 'G', b: 'H', c: 'I', ... },
          ]
      },
      methods: {
          rowClass(row, idx) {
              console.log('rowClass');
              // complex processing here
          },
          rowStyle(row) {
              console.log('rowStyle');
              // complex processing here
          },
  
          cellClass(row, col) {
              console.log('cellClass');
              // complex processing here
          },
          cellStyle(row, col) {
              console.log('cellStyle');
              // complex processing here
          },
          cellClick(row, col) {            
              row[col.field] = 'new value';
          },
  
          formatField(row, col) {
              console.log('formatField');
              const val = row[col.field];  
              // complex val processing here
              return val;
          }
      }
  })
  </code></pre>
  <p>Now imagine this with a larger row set (300+ records) and more involved javascript processing in place of every one of those &quot;console.log()&quot; statements. In the actual code this takes 1200ms to render the entire table which is fine on initial render but not OK when only tiny parts of the row set are changed frequently. For example if I change a single property on a single row in the rows array, vue decides to call absolutely every method on every row and every cell even though there are no dependencies that would warrant that. Shrinking the data set is not an option that the customer will go for since this was possible with great performance even with 3000+ records before we moved to Vue from jQuery and this performs poorly even on small row counts.</p>
  <p>Right now my ugly solution is to keep the vue data (rows array) immutable, while making changes to a clone of that data from which I then make selective updates to table cells with jQuery. Performs well but the code is more complicated than it needs to be and there must be a better way.</p>
  <hr />
  <p>Update: Here is what I have now with given solution. The component template broken down into two components table and row:</p>
  <pre><code>&lt;template id=&quot;vt-gridview&quot;&gt;
      &lt;table v-if=&quot;rows&quot; ref=&quot;table&quot; :class=&quot;['v-gridView', {rowLines: rowLines}]&quot;&gt;
          &lt;tr v-if=&quot;rows.length === 0&quot;&gt;
              &lt;td&gt;
                  &lt;div style=&quot;padding:4px;&quot; class=&quot;noRec&quot;&gt;{{empty}}&lt;/div&gt;
              &lt;/td&gt;
          &lt;/tr&gt;
          &lt;template v-else&gt;
              &lt;tr v-if=&quot;showHeader&quot;&gt;
                  &lt;th v-if=&quot;colShow(col)&quot; v-for=&quot;col in cols&quot; :style=&quot;headStyle(col)&quot; :title=&quot;col.hTitle&quot; @click=&quot;doSort(col)&quot;&gt;
                      &lt;slot v-if=&quot;col.hSlot&quot; :name=&quot;col.hSlot&quot; :col=&quot;col&quot;&gt;&lt;/slot&gt;
                      &lt;template v-else&gt;
                          {{col.name}}&lt;span v-if=&quot;sort&quot; v-show=&quot;sort.exp==col.sort&quot; style=&quot;margin-left:3px;&quot;&gt;{{sortGliph}}&lt;/span&gt;
                      &lt;/template&gt;
                  &lt;/th&gt;
              &lt;/tr&gt;
              &lt;v-gridview-tr v-for=&quot;(row, idx) in rows&quot; :key=&quot;row[rowKeyProp]&quot; 
                  :row=&quot;row&quot; :idx=&quot;idx&quot; :cols=&quot;cols&quot; :col-key-prop=&quot;colKeyProp&quot; :cols-hidden=&quot;colsHidden&quot; :alt-rows=&quot;altRows&quot; :sel-row=&quot;selRow&quot; @row-selected=&quot;rowSelected&quot;&gt;
                  &lt;template v-for=&quot;(index, name) in $scopedSlots&quot; v-slot:[name]=&quot;data&quot;&gt;
                      &lt;slot :name=&quot;name&quot; v-bind=&quot;data&quot;&gt;&lt;/slot&gt;
                  &lt;/template&gt;
              &lt;/v-gridview-tr&gt;
          &lt;/template&gt;
      &lt;/table&gt;
  &lt;/template&gt;
  &lt;template id=&quot;vt-gridview-tr&quot;&gt;
      &lt;tr :class=&quot;rowClass&quot; :style=&quot;rowStyle&quot; @click=&quot;rowClick&quot;&gt;
          &lt;td v-if=&quot;colShow(col)&quot; v-for=&quot;col in cols&quot; :key=&quot;col[colKeyProp]&quot; :class=&quot;cellClass(col)&quot; :style=&quot;cellStyle(col)&quot; :title=&quot;row[col.bTitle]&quot; @click=&quot;cellClick(col)&quot;&gt;
              &lt;slot v-if=&quot;col.bSlot&quot; :name=&quot;col.bSlot&quot; :row-idx=&quot;idx&quot; :row=&quot;row&quot; :col=&quot;col&quot;&gt;&lt;/slot&gt;
              &lt;div v-else-if=&quot;col.html&quot; v-html=&quot;formatField(col)&quot;&gt;&lt;/div&gt;
              &lt;template v-else&gt;
                  {{formatField(col)}}
              &lt;/template&gt;
          &lt;/td&gt;
      &lt;/tr&gt;
  &lt;/template&gt;
  </code></pre>
  <p>The component JS:</p>
  <pre><code>Vue.component('v-gridview', {
      template: '#vt-gridview',
      props: {
          rowKeyProp: {
              type: String,
              default: 'ID'
          },
          colKeyProp: {
              type: String,
              default: 'field'
          },
          showHeader: {
              default: true
          },
          cols: {
              type: Array,
              required: true
          },
          colsHidden: Array,        
          empty: String,
          altRows: {
              default: true
          },
          rowLines: {
              default: false
          },
          rows: Array,
          rowClassFn: Function,
          cellStyleFn: Function,
          sort: Object,
          selRow: Object
      },
      created() {
  
      },
      computed: {
          sortGliph() {
              return this.sort.ord == 'ASC' ? '▲' : '▼';
          }
      },
      methods: {
          colShow(col) {
              return this.colsHidden ? !this.colsHidden.includes(col.field) : true;
          },
          colSortable(col) {
              return this.sort &amp;&amp; col.sort
          },
          doSort(col) {
              if (!this.colSortable(col)) return;
  
              const sort = {
                  exp: col.sort,
                  ord: this.sort.exp != col.sort ? 'ASC' : this.sort.ord == 'ASC' ? 'DESC' : 'ASC'
              }
              this.$emit('update:sort', sort);            // sync to parent
              this.$emit('sort-chng', sort);
          },
          headStyle(col) {
              return [col.hStyle, { cursor: this.colSortable(col) ? 'pointer' : '' }];
          },
  
          rowSelected(row) {
              this.$emit('update:sel-row', row);
              console.log('%crowSelected', 'color: red');
          }
      }
  });
  Vue.component('v-gridview-tr', {
      template: '#vt-gridview-tr',
      props: {
          row: Object,
          idx: Number,        
          colKeyProp: String,        
          cols: Array,
          colsHidden: Array,
          altRows: Boolean,
          rowClassFn: Function,
          cellStyleFn: Function,
          selRow: Object
      },
      data() {
          return {
              selected: false
          };
      },
      watch: {
          'selRow': {
              immediate: true,
              handler(val) {
                  this.selected = val === this.row;
              }
          }
      },
      computed: {
          rowClass() {
              const cls = {
                  alt: (this.altRows) ? this.idx % 2 === 1 : false,
                  selected: this.selected === undefined ? false : this.selected
              }
              if (this.rowClassFn)
                  return this.rowClassFn({ cls, row: this.row, idx: this.idx });
              else
                  return cls;
          },
          rowStyle() {
              return { cursor: this.selected === undefined ? '' : 'pointer' };
          }
      },
      methods: {
          colShow(col) {
              return this.colsHidden ? !this.colsHidden.includes(col.field) : true;
          },
          rowClick() {
              if (this.selected === undefined) return;
  
              this.$emit('row-selected', this.row);
          },
          cellClass(col) {
              console.log('%ccellClass', 'color: yellow');
          },
          cellStyle(col) {
              const style = {
                  //cursor: this.cellSelectable(col) ? 'pointer' : '',   // base style
                  ...col.bStyle                                        // override/add col styles
              };
              if (this.cellStyleFn)
                  return this.cellStyleFn({ style, row: this.row, col });   // override/add callback given styles
              else
                  return style;
          },
          cellClick(col) {
              
          },
          formatField(col) {
              console.log('%cformatField', 'color: lightblue');
              const val = this.row[col.field];
  
              if (val === undefined) {
                  console.error(`[GridView error]: &quot;${col.field}&quot; field not found in data row.`);
                  return;
              }
  
              if (col.format)
                  return $.toStr(val, col.format);
              else
                  return val;
          }
      }
  });
  </code></pre>
  <p>And the use of the component from the page:</p>
  <pre><code>&lt;v-gridview v-bind=&quot;trans&quot; :sort.sync=&quot;trans.sort&quot; :sel-row.sync=&quot;trans.selRow&quot; style=&quot;width:100%;&quot;&gt;
      &lt;template v-slot:trans=&quot;{row}&quot;&gt;
          {{ row.TransID }} &lt;span v-show=&quot;row.dirty&quot; style=&quot;color:var(--error-color);&quot;&gt;✽&lt;/span&gt;
      &lt;/template&gt;
      &lt;template v-slot:actclear=&quot;{row}&quot;&gt;
          &lt;input type='text' class='cellInput' :value=&quot;row.ClearedDate&quot; @focusout=&quot;commitInput(row, 'ClearedDate')&quot; /&gt;
      &lt;/template&gt;
  &lt;/v-gridview&gt; 
  </code></pre>
  <p>JS:</p>
  <pre><code>    data: {
          trans: {
              cols: [
                  { name: 'Tran.#', field: 'TransID', bSlot: 'trans', hStyle: { 'text-align': 'left' } },
                  { name: 'Date', field: 'TransDate', hStyle: { 'text-align': 'left' } },
                  { name: 'Cleared', field: 'ClearedDate', bSlot: 'actclear', hStyle: { 'text-align': 'left', width: '75px' }, bStyle: { padding: 0 } },
                  { name: 'Amount', field: 'Amount', format: 'money', hStyle: { 'text-align': 'right' }, bStyle: { 'text-align': 'right' } }
              ],
              rows: [
                  { TransID: 1, TransDate: '01/01/20', ClearedDate: null, Amount: 35 },
                  { TransID: 2, TransDate: '01/02/20', ClearedDate: null, Amount: 40 },
                  { TransID: 3, TransDate: '01/03/20', ClearedDate: '02/02/20', Amount: 45.56 },
              ],
              sort: null,
              selRow: null
          }
      },
      methods: {
          commitInput(row, field) {
              if (row[field] == event.target.value) return;
              row[field] = event.target.value;
              this.$set(row, 'dirty', true);
          },
      }
  </code></pre>
  <p>Although updating the input only changes data in a single row, all rows are updated. Same with selecting row. Problem still there even when removing all the row selection code.</p>