Vue TSX备忘 [WIP]

本文仅用于记录个人对Vue+TSX的理解,由于时间、精力及技术能力限制,内容难免出现错漏。

Vue TSX组件包含两类,PureFunction、常规组件类型。

PureFuncitonalComponent纯函数组件即不含任何内部状态的纯函数类型组件,纯函数组件可直接使用类似于React组件的方式进行组件编写,但由于其中不能拥有任何内部状态,因此只能对从父组件上传递下来的props进行重构渲染。

常规组件类型,即可以拥有内部状态的组件,常规组件类型需要使用defineComponent API进行组件构建。

PureFunctionalComponent纯函数组件

纯函数组件PureFunctionalComponent主要结构代码如下:

export declare interface FunctionalComponent<P = {}, E extends EmitsOptions = {}> extends ComponentInternalOptions {
    (props: P, ctx: Omit<SetupContext<E>, 'expose'>): any;
    props?: ComponentPropsOptions<P>;
    emits?: E | (keyof E)[];
    inheritAttrs?: boolean;
    displayName?: string;
    compatConfig?: CompatConfig;
}
// NumberIndicator PureFunctionalComponent
export const NumberIndicator:Function<{ name:string; value:number }> = (props,ctx)=>{
    return <div>
       {props.name}:{props.value}
    </div>;
}

这种组件的编写方式是和React的纯函数组件非常相似的,但由于vue和react进行sideEffect处理的机制不同,因此导致这种方式只能作为纯函数组件使用。

常规组件

Vue+TSX中常规包含内部状态的组件需要使用defineComponent API进行定义,这种组件的构建方式如下:

export const Counter = defineComponent({
  props: {
    initialCount: Number,
    increment: Number,
    name: String,
  },
  setup(props) {
    const state = reactive({ count: props.initialCount });
    onMounted(() => {
      console.log("I'm mounted");
    });

    return () => {
      <div>
        {props.name ?? "Counter"}:{state.count}
      </div>;
    };
  },
});

其中setup函数与script tag上添加setup属性后的标签体内的写法基本相似,但在使用defineComponent API时,setup函数中的返回值为该组件的渲染函数,及一返回TSX Single-parent 的标签集。在这些标签中,可以使用自定义或第三方的组件,并以TSX的方式将组件的属性传递下去,而事件绑定也需变更为TSX的方式。这个渲染函数可以看做一个React的组件,但通过作用域拿到外部使用vue reactive API所定义的状态进行数据绑定。我之前一直使用的是React写前端代码,在尝试Vue TSX后第一感觉是还是有点繁杂,但在经过一段时间使用后,对于这些API的使用熟练程度逐渐提升起来时,对于这两个框架的仅从API上的看法已经有了改变,暂时我还没法总结出来两者之间的区别,后续有一定心得后再来补充这一点。

这里总体来说逻辑是比较好理解的,因为Vue使用的Proxy的方式对特定的数据进行代理监听,因此对数据进行变化时,Vue框架只会将监听(可以理解为渲染、依赖、监听)该数据的监听器触发,因此仅仅会对进行数据变更的组件进行刷新,这部分是和React的主要区别。

而这种方式导致两者在进行TSX类型组件构建时存在一定区别,Vue需要的仅仅是针对某一组件进行重渲染,而不需要对其所有的side-effect hooks进行一次判断,因此其renderer函数中不需要类似于useEffect类型的hook,因为这部分的工作已经由其Proxy的特性而避免了。React则因为需要根据props进行组件新旧props的不断比较,从而根据reconcile算法确定需要更新的fiber子树,并根据调度算法进行DOM刷新(因此React开发组也称其实React更像Scheduler,在合适的时间对外界对系统的输入进行反馈输出)。

而因为Vue是使用ES6 Proxy完成数据的动态绑定功能,因此对于其“reactive”的对象如果直接destructure,则会导致该对象lose reactive,因此在Vue中使用Reactive API的时候非常需要注意这一点,但这个问题也可以通过使用toRefs API将该Reactive对象转变为所有属性均为Reactive的plain object,相当于将Proxy层级往下沉了一层:

toRefs():Converts a reactive object to a plain object where each property of the resulting object is a ref pointing to the corresponding property of the original object. Each individual ref is created using toRef().

下图为LearnVue网站对于Composition API下Vue组件生命周期的一个描述,当使用上方setup方式时,beforeCreate及created生命周期将被setup函数替代,因此下图中在beforeCreate及created两个生命周期hook下方加上了setup。

这里可以看到,setup函数实际上只会调用一次,而后续只会调用其对应的生命周期函数,而这里我们可以合理的猜测在re-render的时候,数据变动所影响到的组件中的标签将会被重新渲染,而这里我们仔细看一下defineComponent中与标签相关的代码,可以看到其实在setup中使用TSX时就只有一个地方向外部暴露了TSX,就是return的函数,该函数通过闭包可以获取到setup函数中的各种reactive变量,同时返回值是一个可以看做当前Component进行实际DOM渲染时的一个函数,实际上这个函数也是组件的renderer。在我们有了renderer的这个概念之后可以合理推理出,当有依赖的数据发生变动时,该renderer将在所依赖的数据发生变动时被调用,这个调用的机制我需要去读一下vue的源码才能确定,在这里就暂时先不讨论。

组件内部状态


  • 一切都是玛卡巴卡的阴谋nwp 2022-11-08 (嘿嘿嘿)

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注