Yew 子组件的创建和渲染
前一篇 Yew框架(一) 应用初始化过程 我们了解一应用启动的过程,后续我将探索Yew中的一些功能是如何实现的,先来看看 子组件的创建渲染过程。
Yew 支持在组件的视图中嵌套组件,支持给子组件传递属性,下图是测试代码扩展前后的对比:
上面的代码主要是创建VChild对象,不是特别难,但要看懂它做了什么,为什么要这样做,还得先看 Properties 宏对组件的属性做了什么。
Properties 属性
这是我们自定义的Button组件的属性声明:
扩展后:
代码一下变多了。
首先是定义了一个 PropsWrapper,包含两个属性,但属性的类型都为Option类型,并且实现了Default特性,从后面的代码可以看到它就是PropsBuilder 用来存储过程信息的。
定义了两个结构体,都实现PropsBuilderStep特性,定义一个PropsBuilder结构体,有一个泛型参数,参数实现PropsBuilderStep。这是一个状态机在Rust的实现模式,用泛型参数来表示构建器的状态,通过构建器的方法来进行状态迁移,不同的状态可以调用不同的方法。
因为我们只有两个属性,所以 PropsBuilderStep 只有两个状态。PropsBuilderStep_missing_required_prop_text 是初始状态,该状态来可以调用 onclick 和 text 两个方法,来配置 builder,信息存储在 Builder 内部的 PropsWarpper 中。
值得注意的是,Yew 组件的属性是可以不传的,但可不可以不传是由属性的定义者来决定的,而不是调用者来决定的。如果定义一个属性为必传,那么调用者必须传入一个合适的值。如果定义为可不传,调用者可以决定传还是不传。Builder 中自动生成的方法参数上有区别,如果是可以不传的属性,其参数是Option类型。
为Props结构体实现 Properties 属性,只有一个builder方法,用来创建该属性的 Builder,最后由调用该Builder的build方法(该方法只能在 PropsBuilderStep_build 状态下调用,Builder的方法是逆序生成的,最后一个方法调用完成后进入 build 状态),用 PropsWrapper内存储的值来创建一个 Props 对象。
现在接着看 VChild 的创建。
创建 VChild 对象
VChild对象有一个泛型参数,是子组件的类型,这里是 Button。扩展后的创建代码如下:
调用 VChild::new() 方法,传入三个参数,第一个是通过构建器构建的组件属性,第二个是默认的 NodeRef, 第三个是 None。
从代码中看到构建方法的参数都用 VComp 的transform方法进行了转换,这是为何?
Transformer
从注释中得知这个特性功能是用来把属性(调用者传入的属性值)转换了期待的类型(子组件属性对象中对应属性期待的类型)。
VComp 实现多种类型转换,让父组件可以有多种传递属性给子组件的方式,简单说就是提供了“语法糖”。从 FROM 到 TO,表示当子组件的属性期待的是 TO 类型时,父组件可以为其提供 FROM 类型,由 Transformer 自动转换为 TO 类型,赋值给属性。
搜索源码,VComp一共有7个实现:
- 子组件需要什么,父组件就传什么;
- 当子组件需要 T 类型时,可以传入 &T,要求 T 具有 Clone 特性;
- 如果子组件需要的是 String,父组件可以传入 &str;
- 如果子组件需要的是 Option<T> 类型,父组件可以只传入 T 或 &T(要求 T 具有 Clone 特性)类型;
- 如果子组件需要的是 Option<String> 类型,父组件可以传入 &str 或 Option<&str>。
在示例中,我们需要的是一个 String 类型和一个 Option<Callback<()>> 类型,父组件传入的是 &str 和 Callback<()> 类型。经过Transformer 转换成了我们期望的类型。
这个功能非常好用,之前学习另一个框架 simi 时,发现我希望传递给子组件的数据有时候是 Option<T>,有时候是T,有时候是 str,有时候是String,但它的子组件只能接收相同类型的值,在使用的时候要手动做转换,比较麻烦,代码写出来很不好看,当时想过为simi贡献一个补丁来解决这个问题,但一直没有找到合适的方案。这里 Transformer 利用Rust的自动类型推导和泛型能力很优雅的解决了这个问题,非常不错。
VChild::new()
这个方法很简单,就是创建一个 VChild 对象。
转化为 VComp
第一张图51行,表示最终会调用VNode::from方法:
内部先调用 VComp::from 方法将 VChild 转换为 VComp,最终转换为 VNode::VComp。
如此看来,VChild 也是一个备胎,它也只是在中途被用一下,在这里就被抛弃了,才创建出来没有两分钟,哈哈哈!重头在 VComp::new()
果然如此,代码长度说明一切!
Generator
用来根据 GeneratorType 渲染子组件视图并挂载到 Dom 的闭包。GeneratorType 有两种情况,一种是创建组件对象并挂载到节点,另一种是之前已经挂载过该组件对象了,只需要更新组件的属性。
闭包内的代码已经很熟悉了(参考 Yew框架(一) 应用初始化过程),第一种情况下,和挂载任意VNode类似,第二种情况下发出一个更新指令到调度器,会先更新组件Scope的状态(期间会调用组件的 change 方法)并重新渲染视图。
创建一个可以创建或更新子组件的闭包,但并未执行,将VComp置为 Unmounted的状态。在渲染视图时,会根据具体情况决定如何来渲染该VComp。
这是什么问题呢?
<a rel="nofollow"></a>` tags to go to your route will not work out of the box, and are inefficient because the server will return the whole app bundle again at best, and at worst just return a 404 message if the server isn't configured properly. Using this library's RouteService, RouteAgent, RouterButton, and RouterLink to set the location via `history.push_state()` will change the route without retrieving the whole app again. #### Server configuration In order for an external link to your webapp to work, the server must be configured to return the `index.html` file for any GET request that would otherwise return a `404` for any conceivable client-side route. It can't be a `3xx` redirect to `index.html`, as that will change the
路由映射:
#[to = "/pages/index"] 必须在 #[to = "/"] 之前,
也就是说, #[to = "/"] 必须在最后,
现在没有问题了.