我正在使用带有 scalajs-react 0.11.1 的二极管 1.0.0。
用例:
- 带有子组件列表的父组件
- 子模型片段包含
Pot
用于异步获取的图像 - 子组件在挂载时获取图像并
Pot
更新Empty
其模型片段
使用幼稚的方法,这会导致以下情况(事件的顺序可能不同):
- 父级被渲染。
- 孩子 1 被渲染。
- Child 1 发送它的
GetImageAction
. 模型片段Pot
更新为Pending
. - 模型已更新,导致父级重新渲染。
- 所有的孩子都被重新渲染。
- 孩子 2 ... n 仍然有一个
Empty
Pot
,所以他们GetImageAction
再次触发他们的 s。
- Child 1 发送它的
- 现在孩子 2 被渲染。
- 模型已更新,导致父级重新渲染。
- 等等。
这会导致GetImageAction
调用和重新渲染的巨大树。
一些问题:
- 将模型用于此目的是错误的吗?使用组件状态会更好吗?
- 当只需要更新孩子时,如何避免重新渲染父母?我无法弄清楚我是否/如何可以
shouldComponentUpdate
用于此目的。
更新 1
我现在为每个子组件添加一个 React 键。这摆脱了关于唯一键的 React 警告,但不幸的是并没有解决上述问题。即使他们的shouldComponentUpdate
方法返回,孩子也会被重新渲染false
。
来自ParentComponent.render()
:
items.zipWithIndex.map { case (_, i) =>
proxy.connector.connect(
proxy.modelReader.zoom(_.get(i)), s"child_$i": js.Any).
apply(childComponent(props.router, _))
}
更新 2
我尝试在父组件中实现侦听器功能,但不幸的是,子组件仍然被卸载并重新安装。这是我的父组件的代码:
package kidstravel.client.components
import diode.data.{Empty, Pot}
import diode.react.ModelProxy
import diode.react.ReactPot._
import diode.{Action, ModelR}
import japgolly.scalajs.react.extra.router.RouterCtl
import japgolly.scalajs.react.vdom.prefix_<^._
import japgolly.scalajs.react.{BackendScope, ReactComponentB, _}
import kidstravel.client.KidsTravelMain.Loc
import kidstravel.client.services.{KidsTravelCircuit, RootModel}
case class TileProps[T](router: RouterCtl[Loc], proxy: ModelProxy[T])
/**
* Render sequence of models as tiles.
*/
trait Tiles {
// The type of the model objects.
type T <: AnyRef
/**
* Override to provide the action to obtain the model objects.
* @return An action.
*/
def getAction: Action
/**
* Returns the tile component class.
* @return
*/
def tileComponent: ReactComponentC.ReqProps[TileProps[T], _, _, _ <: TopNode]
case class Props(router: RouterCtl[Loc], proxy: ModelProxy[Pot[Seq[T]]])
class Backend($: BackendScope[Props, Pot[Seq[T]]]) {
private var unsubscribe = Option.empty[() => Unit]
def willMount(props: Props) = {
val modelReader = props.proxy.modelReader.asInstanceOf[ModelR[RootModel, Pot[Seq[T]]]]
Callback {
unsubscribe = Some(KidsTravelCircuit.subscribe(modelReader)(changeHandler(modelReader)))
} >> $.setState(modelReader())
}
def willUnmount = Callback {
unsubscribe.foreach(f => f())
unsubscribe = None
}
private def changeHandler(modelReader: ModelR[RootModel, Pot[Seq[T]]])(
cursor: ModelR[RootModel, Pot[Seq[T]]]): Unit = {
// modify state if we are mounted and state has actually changed
if ($.isMounted() && modelReader =!= $.accessDirect.state) {
$.accessDirect.setState(modelReader())
}
}
def didMount = $.props >>= (p => p.proxy.value match {
case Empty => p.proxy.dispatch(getAction)
case _ => Callback.empty
})
def render(props: Props) = {
println("Rendering tiles")
val proxy = props.proxy
<.div(
^.`class` := "row",
proxy().renderFailed(ex => "Error loading"),
proxy().renderPending(_ > 100, _ => <.p("Loading …")),
proxy().render(items =>
items.zipWithIndex.map { case (_, i) =>
//proxy.connector.connect(proxy.modelReader.zoom(_.get(i)), s"tile_$i": js.Any).apply(tileComponent(props.router, _))
//proxy.connector.connect(proxy.modelReader.zoom(_.get(i))).apply(tileComponent(props.router, _))
//proxy.wrap(_.get(i))(tileComponent(_))
tileComponent.withKey(s"tile_$i")(TileProps(props.router, proxy.zoom(_.get(i))))
}
)
)
}
}
private val component = ReactComponentB[Props]("Tiles").
initialState(Empty: Pot[Seq[T]]).
renderBackend[Backend].
componentWillMount(scope => scope.backend.willMount(scope.props)).
componentDidMount(_.backend.didMount).
build
def apply(router: RouterCtl[Loc], proxy: ModelProxy[Pot[Seq[T]]]) = component(Props(router, proxy))
}