将模型传递给组件
Passing models to components
使用 hyperstack.org 框架,如何在改变正在渲染的模型时减少渲染周期?
当将正在渲染的模型传递给会改变该模型的组件时,渲染该模型的所有组件都会在发生任何改变时重新渲染。这很好,除非突变是每次按键,因为这意味着所有组件都会在每次按键时重新呈现。
例如,如果我们有这个 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
而这个组件(用于编辑和新建):
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
基础 table(来自第一个代码示例)在每次按键时重新呈现,原因是:
TextField(label: 'First Name', defaultValue: @User.first_name.to_s)
.on(:change) do |e|
@User.first_name = e.target.value
end
由于重新渲染的数量,这导致打字显得迟缓。
我是否应该为每个字段保留一个本地状态变量,然后只在保存时改变模型字段?
看起来您正在使用 Material UI,它将动态调整 table 的大小以最适合内容。所以我怀疑正在发生的事情是您在编辑对话框中的值时在 MUI table 中显示 first_name
和 last_name
的值。
所以 MUI 在键入每个字符时不断地重新计算 MUI table 列的大小。
这不仅会减慢速度,还会让人类用户感到不安。这会给人一种印象,即他们所做的更改甚至在您保存之前就已经生效了。
所以是的,我认为最好的方法是在用户键入时不直接更新记录的状态,而是更新本地状态变量。然后只有当用户保存时,你才更新实际记录。
我确实注意到您有 defaultValue
,这表示 "uncontrolled" 输入。但是您正在对输入的每个变化做出反应,这就是 "controlled" 行为。我认为您可以将 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
事实证明,导致性能问题的原因是我没有将唯一键传递给列表中的项目。 React 对此非常讲究,但您不会收到警告。
我需要改变的是:
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
通过上述更改,两个示例中的一切都完美运行(第一个是基础 table 随着用户输入而更新,第二个由@catmando 提供,更改仅在保存时应用.
使用 hyperstack.org 框架,如何在改变正在渲染的模型时减少渲染周期?
当将正在渲染的模型传递给会改变该模型的组件时,渲染该模型的所有组件都会在发生任何改变时重新渲染。这很好,除非突变是每次按键,因为这意味着所有组件都会在每次按键时重新呈现。
例如,如果我们有这个 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
而这个组件(用于编辑和新建):
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
基础 table(来自第一个代码示例)在每次按键时重新呈现,原因是:
TextField(label: 'First Name', defaultValue: @User.first_name.to_s)
.on(:change) do |e|
@User.first_name = e.target.value
end
由于重新渲染的数量,这导致打字显得迟缓。
我是否应该为每个字段保留一个本地状态变量,然后只在保存时改变模型字段?
看起来您正在使用 Material UI,它将动态调整 table 的大小以最适合内容。所以我怀疑正在发生的事情是您在编辑对话框中的值时在 MUI table 中显示 first_name
和 last_name
的值。
所以 MUI 在键入每个字符时不断地重新计算 MUI table 列的大小。
这不仅会减慢速度,还会让人类用户感到不安。这会给人一种印象,即他们所做的更改甚至在您保存之前就已经生效了。
所以是的,我认为最好的方法是在用户键入时不直接更新记录的状态,而是更新本地状态变量。然后只有当用户保存时,你才更新实际记录。
我确实注意到您有 defaultValue
,这表示 "uncontrolled" 输入。但是您正在对输入的每个变化做出反应,这就是 "controlled" 行为。我认为您可以将 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
事实证明,导致性能问题的原因是我没有将唯一键传递给列表中的项目。 React 对此非常讲究,但您不会收到警告。
我需要改变的是:
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
通过上述更改,两个示例中的一切都完美运行(第一个是基础 table 随着用户输入而更新,第二个由@catmando 提供,更改仅在保存时应用.