Передача моделей на компоненты - PullRequest
1 голос
/ 24 марта 2019

Как использовать каркас hyperstack.org, как уменьшить циклы рендеринга при рендеринге изменяемых моделей?

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

Например, если у нас есть эта таблица:

class UserIndex < HyperComponent
  render(DIV) do
    puts "UserIndex render"
    BridgeAppBar()
    UserDialog(user: User.new)
    Table do
      TableHead do
        TableRow do
          TableCell { 'Name' }
          TableCell { 'Gender' }
          TableCell { 'Edit' }
        end
      end
      TableBody do
        user_rows
      end
    end
  end

  def user_rows
    User.each do |user|
      TableRow do
        TableCell { "#{user.first_name} #{user.last_name}" }
        TableCell { user.is_female ? 'Female' : 'Male' }
        TableCell { UserDialog(user: user) }
      end
    end
  end
end

И этот Compnent (который используется для редактирования и нового):

class UserDialog < HyperComponent
  param :user

  before_mount do
    @open = false
  end

  render do
    puts "UserDialog render"
    if @open
      render_dialog
    else
      edit_or_new_button.on(:click) { mutate @open = true }
    end
  end

  def render_dialog
    Dialog(open: @open, fullWidth: false) do
      DialogTitle do
        'User'
      end
      DialogContent do
        content
        error_messages if @User.errors.any?
      end
      DialogActions do
        actions
      end
    end
  end

  def edit_or_new_button
    if @User.new?
      Fab(size: :small, color: :primary) { Icon { 'add' } }
    else
      Fab(size: :small, color: :secondary) { Icon { 'settings' } }
    end
  end

  def content
    FormGroup(row: true) do
      TextField(label: 'First Name', defaultValue: @User.first_name.to_s).on(:change) do |e|
        @User.first_name = e.target.value
      end
      TextField(label: 'Last Name', defaultValue: @User.last_name.to_s).on(:change) do |e|
        @User.last_name = e.target.value
      end
    end

    BR()

    FormLabel(component: 'legend') { 'Gender' }
    RadioGroup(row: true) do
      FormControlLabel(label: 'Male',
                       control: Radio(value: false, checked: !@User.is_female).as_node.to_n)
      FormControlLabel(label: 'Female',
                       control: Radio(value: true, checked: @User.is_female).as_node.to_n)
    end.on(:change) do |e|
      @User.is_female = e.target.value
    end
  end

  def actions
    Button { 'Cancel' }.on(:click) { cancel }

    if @User.changed? && validate_content
      Button(color: :primary, variant: :contained, disabled: (@User.saving? ? true : false)) do
        'Save'
      end.on(:click) { save }
    end
  end

  def save
    @User.save(validate: true).then do |result|
      mutate @open = false if result[:success]
    end
  end

  def cancel
    @User.revert
    mutate @open = false
  end

  def error_messages
    @User.errors.full_messages.each do |message|
      Typography(variant: :h6, color: :secondary) { message }
    end
  end

  def validate_content
    return false if @User.first_name.to_s.empty?
    return false if @User.last_name.to_s.empty?
    return false if @User.is_female.nil?

    true
  end

end

Базовая таблица (из первого примера кода) перерисовывается при каждом нажатии клавиши, вызванная:

TextField(label: 'First Name', defaultValue: @User.first_name.to_s)
.on(:change) do |e|
    @User.first_name = e.target.value
end

Это приводит к тому, что набор текста выглядит вялым из-за повторного рендеринга.

Должен ли я хранить локальную переменную состояния для каждого поля, а затем изменять только поля модели при сохранении?

Ответы [ 2 ]

2 голосов
/ 27 марта 2019

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

Все, что мне пришлось изменить, было:

User.each do |user|
    TableRow do
        ...
        TableCell { UserDialog(user: user) }
    end
end

Кому:

User.each do |user|
    TableRow do
        ...
        # this passes a unique key to each Component
        TableCell { UserDialog(user: user, key: user) } 
    end
end

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

2 голосов
/ 24 марта 2019

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

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

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

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

Я замечаю, что у вас есть defaultValue, который указывает на «неконтролируемый» ввод.Но вы реагируете на каждое изменение ввода, которое является «контролируемым» поведением.Я думаю, что вы можете изменить defaultValue на value.

class UserDialog < HyperComponent
  param :user

  before_mount do
    @open = false
    @first_name = @User.first_name
    @last_name = @User.last_name 
    @is_female = @User.is_female
  end

  render do
    puts "UserDialog render"
    if @open
      render_dialog
    else
      edit_or_new_button.on(:click) { mutate @open = true }
    end
  end

  def render_dialog
    Dialog(open: @open, fullWidth: false) do
      DialogTitle do
        'User'
      end
      DialogContent do
        content
        error_messages if @User.errors.any?
      end
      DialogActions do
        actions
      end
    end
  end

  def edit_or_new_button
    if @User.new?
      Fab(size: :small, color: :primary) { Icon { 'add' } }
    else
      Fab(size: :small, color: :secondary) { Icon { 'settings' } }
    end
  end

  def content
    FormGroup(row: true) do
      TextField(label: 'First Name', value: @first_name).on(:change) do |e|
        mutate @first_name = e.target.value
      end
      TextField(label: 'Last Name', value: @last_name).on(:change) do |e|
        mutate @last_name = e.target.value
      end
    end

    BR()

    FormLabel(component: 'legend') { 'Gender' }
    RadioGroup(row: true) do
      FormControlLabel(label: 'Male',
                       control: Radio(value: false, checked: !@is_female).as_node.to_n)
      FormControlLabel(label: 'Female',
                       control: Radio(value: true, checked: @is_female).as_node.to_n)
    end.on(:change) do |e|
      mutate @is_female = e.target.value
    end
  end

  def actions
    Button { 'Cancel' }.on(:click) { cancel }

    return unless ready_to_save?
    Button(color: :primary, variant: :contained, disabled: (@User.saving? ? true : false)) do
      'Save'
    end.on(:click, &:save)
  end

  def save
    @User.update(first_name: @first_name, last_name: @last_name, is_female: @is_female).then do |result|
      mutate @open = false if result[:success]
    end
  end

  def cancel
    mutate @open = false
  end

  def error_messages
    @User.errors.full_messages.each do |message|
      Typography(variant: :h6, color: :secondary) { message }
    end
  end

  def ready_to_save?
    return false if @first_name.empty?
    return false if @last_name.empty?
    return false if @is_female.nil?
    return true if @first_name != @User.first_name
    return true if @last_name != @User.last_name
    return true if @is_female != @User.is_female
  end

end
...