本文仅用于记录个人对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 (嘿嘿嘿)