在开发新的渲染器核心时遇到了这个问题,着实有趣,遂提笔记录。
关于这个问题的大部分讨论都在go-app issues #1043,也可以去这里看截图。
我遇到的问题的症结在:go-app框架在刷新组件时,会对 (app.Compo).Render 的返回值进行判断,用于避免重复渲染相同内容。但是它判断的依据相当粗暴,仅仅是比较新的返回值与旧返回值的类型,类型相同则忽略其内在差异,直接视为相同内容一棍子打死。
针对这个问题,@oderwat封装 app.UI 并实现了一个 replacer ,代码在metatexx/go-app-pkgs。实现非常简明,我就直接pin在文章里了。
package mountpoint
import (
"github.com/maxence-charriere/go-app/v9/pkg/app"
)
type replacer interface {
app.Composer
nr() int
set(app.UI)
}
type mp0 struct {
app.Compo
n int
ui app.UI
}
func (c *mp0) nr() int {
return c.n
}
func (c *mp0) set(el app.UI) {
c.ui = el
}
func (c *mp0) Render() app.UI {
return c.ui
}
type mp1 struct {
app.Compo
n int
ui app.UI
}
func (c *mp1) nr() int {
return c.n
}
func (c *mp1) set(el app.UI) {
c.ui = el
}
func (c *mp1) Render() app.UI {
return c.ui
}
type UI struct {
ui replacer
}
// Switch lets you switch the app.UI component with another one.
// It guarantees that the former component gets dismounted and
// the new one gets mounted in place of the old one.
func (c *UI) Switch(el app.UI) {
if c.ui == el {
return
}
if el.Mounted() {
return
}
switch c.ui.nr() {
case 0:
c.ui = &mp1{ui: el, n: 1}
case 1:
c.ui = &mp0{ui: el}
}
}
// UI returns the reference to the current mounted app.UI
func (m *UI) UI() app.UI {
return m.ui
}
// New creates a new mountpoint for switching app.UI components
func New(ui app.UI) *UI {
return &UI{&mp0{ui: ui}}
}他在这里定义了 mp1 和 mp2 两种类型,均为 replacer 的实现。其中包含 nr() 和 set() 方法,其中 nr() 和 field:n 用于获取和记录当前的状态,而 set() 用于更改replacer内部的component。然后定义了 UI 结构体,实现了 Switch() 和 UI() 方法,用于传递当前生效的replacer。
它的工作原理是,即使原组件和新组件的类型一样,通过 (*UI).Switch() 和 (*UI).UI() 方法,总能返回一个不一样的封装,即 mp1 和 mp2 将交替作为返回值。而真正的组件被封装在它们的 field:ui,有了这层关系,即使 (*mp1/*mp2).Render 返回的是一样的类型,也将触发强制重新渲染。
Q.E.D.
Mio
2025-03-02 06:55 提笔
2025-03-02 07:10 完稿