简体   繁体   English

将模型传递给组件

[英]Passing models to components

Using the hyperstack.org framework, how can I reduce the rendering cycles when mutating models that are being rendered? 使用hyperstack.org框架,在对正在渲染的模型进行突变时,如何减少渲染周期?

When passing a Model which is being rendered to a Component which mutates that Model, all Components rendering that Model get re-rendered on any mutation. 将要渲染的模型传递给使该模型发生突变的组件时,所有呈现该模型的组件都会在发生任何突变时重新渲染。 This is fine unless the mutation is per key press as this means that all Components get re-rendered per key press. 除非突变是按按键进行的,否则这很好,因为这意味着按按键重新渲染所有组件。

For example, if we have this table: 例如,如果我们有此表:

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

And this Compnent (which is used for edit and new): 该组件(用于编辑和新建):

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

The underlying table (from the first code example) is re-rendered on every keypress, caused by: 在每个按键上都会重新呈现基础表(来自第一个代码示例),原因是:

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

This is causing typing to appear sluggish due to the amount of re-rendering. 由于重新渲染的数量,这导致键入显示缓慢。

Should I be keeping a local state variable for each field then only mutating the model fields on save? 我应该为每个字段保留局部状态变量,然后仅在保存时对模型字段进行更改吗?

Looks like you are using Material UI which will dynamically size tables to be best fit the content. 看起来您正在使用Material UI,它将动态调整表格大小以使其最适合内容。 So I suspect what is happening is that you are displaying the value of first_name and last_name in the MUI table, while you editing the values in the Dialog box. 因此,我怀疑正在发生的事情是您在对话框中编辑值时在MUI表中显示first_namelast_name的值。

So MUI is constantly recalculating the size of the MUI table columns as each character is typed. 因此,在键入每个字符时,MUI会不断重新计算MUI表列的大小。

Not only is this going to slow things down, but its also going to be disconcerting to the human user. 这不仅会减慢速度,而且还会给人类用户带来不安。 It will give the impression that the changes they are making all already taking effect even before you have saved them. 它给人的印象是,所做的更改全部已经生效,甚至在您保存它们之前。

So yes I think the best approach is to not directly update the state of the record while the user is typing but rather update a local state variable. 因此,是的,我认为最好的方法是在用户键入时不直接更新记录的状态,而是更新局部状态变量。 Then only when the user saves, do you update the actual record. 然后只有在用户保存时,您才更新实际记录。

I do notice that you have defaultValue which indicates an "uncontrolled" input. 我确实注意到您有defaultValue ,它指示“不受控制”的输入。 But you are reacting to every change in the input, which is the "controlled" behavior. 但是您要对输入中的每个更改做出反应,这就是“受控”行为。 I think you can change defaultValue to value . 我认为您可以将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

As it turned out the things that were causing the performance problem was that I was not passing a unique key to the items in the list. 事实证明,导致性能问题的原因是我没有将唯一键传递给列表中的项目。 React is very particular about this yet this is not something you get warnings about. React对此非常讲究,但这并不是您得到警告的地方。

All I had to change was: 我所要做的就是:

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

To: 至:

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

With the above change everything works perfectly in both examples (the first being where the underlying table is updated as the user types and the second, provided by @catmando, where the changes are only applied on save. 通过上述更改,所有内容在两个示例中均能完美运行(第一个示例是根据用户类型更新基础表,第二个示例由@catmando提供),其中,更改仅在保存时应用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM