[英]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_name
和last_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.