写jsx_使用Vue 3.0做JSX(TSX)风格的组件开发
前言
我日常工作都是使用React来做开发,但是我对React一直不是很满意,特别是在推出React Hooks以后。
不可否认React Hooks极大地方便了开发者,但是它又有非常多反直觉的地方,让我难以接受。所以在很长一段时间,我都在尝试寻找React的替代品,我尝试过不少别的前端框架,但都有各种各样的问题或限制。
在看到了Vue 3.0 Composition-API的设计,确实有眼前一亮的感觉,它既保留了React Hooks的优点,又没有反复声明销毁的问题,而Vue一直都是支持JSX语法的,3.0对TypeScript的支持又非常好,所以我开始尝试用Vue + TSX来做开发。
Vue 3.0已经发布了alpha版本,可以通过以下命令来安装:
npm install vue@next --save
简单示例
先来看看用Vue3.0 + TSX写一个组件是什么什么样子的。
实现一个Input组件:
import { defineComponent } from 'vue';interface InputProps {value: string;onChange: (value: string) => void;
}
const Input = defineComponent({setup(props: InputProps) {const handleChange = (event: KeyboardEvent) => {props.onChange(event.target.value);}return () => (<input value={props.value} onInput={handleChange} />)}
})
可以看到写法和React非常相似,和React不同的是,一些内部方法,例如handleChange
,不会在每次渲染时重复定义,而是在setup
这个准备阶段完成,最后返回一个“函数组件”。
这算是解决了React Hooks非常大的一个痛点,比React Hooks那种重复声明的方式要舒服多了。
Vue 3.0对TS做了一些增强,不需要像以前那样必须声明props
,而是可以通过TS类型声明来完成。
这里的defineComponent
没有太多实际用途,主要是为了让TS类型提示变得友好一点。
Babel插件
为了能让上面那段代码跑起来,还需要有一个Babel插件来转换上文中的JSX,Vue 3.0相比2.x有一些变化,不能再使用原来的vue-jsx插件。
我们都知道JSX(TSX)实际上是语法糖,例如在React中,这样一段代码:
const input = <input value="text" />
实际上会被babel插件转换为下面这行代码:
const input = React.createElement('input', { value: 'text' });
Vue 3.0也提供了一个对应React.createElement
的方法h
。但是这个h
方法又和vue 2.0以及React都有一些不同。
例如这样一段代码:
<div class={['foo', 'bar']} style={{ margin: '10px' }} id="foo" onClick={foo} />
在vue2.0中会转换成这样:
h('div', {class: ['foo', 'bar'],style: { margin: '10px' }attrs: { id: 'foo' },on: { click: foo }
})
可以看到vue会将传入的属性做一个分类,会分为class
、style
、attrs
、on
等不同部分。这样做非常繁琐,也不好处理。
在vue 3.0中跟react更加相似,会转成这样:
h('div', {class: ['foo', 'bar'],style: { margin: '10px' }id: 'foo',onClick: foo
})
基本上是传入什么就是什么,没有做额外的处理。
当然和React.createElement
相比也有一些区别:
- 子节点不会作为以
children
这个名字在props
中传入,而是通过slots
去取,这个下文会做说明。 - 多个子节点是以数组的形式传入,而不是像React那样作为分开的参数
所以只能自己动手来实现这个插件,我是在babel-plugin-transform-react-jsx的基础上修改的,并且自动注入了h
方法。
实际使用
在上面的工作完成以后,我们可以真正开始做开发了。
渲染子节点
上文说到,子节点不会像React那样作为children
这个prop
传递,而是要通过slots
去取:
例如实现一个Button组件
// button.tsx
import { defineComponent } from 'vue';
import './style.less';interface ButtonProps {type: 'primary' | 'dashed' | 'link'
}
const Button = defineComponent({setup(props: ButtonProps, { slots }) {return () => (<button class={'btn', `btn-${props.type}`}>{slots.default()}</button>)}
})export default Button;
然后我们就可以使用它了:
import { createApp } from 'vue';
import Button from './button';// vue 3.0也支持函数组件
const App = () => <Button>Click Me!</Button>createApp().mount(App, '#app');
Reactive
配合vue 3.0提供的reactive
,不需要主动通知Vue更新视图,直接更新数据即可。
例如一个点击计数的组件Counter:
import { defineComponent, reactive } from 'vue';const Counter = defineComponent({setup() {const state = reactive({ count: 0 });const handleClick = () => state.count++;return () => (<button onClick={handleClick}>count: {state.count}</button>)}
});
这个Counter组件如果用React Hooks来写:
import React, { useState } from 'react';const Counter = () => {const [count, setCount] = useState(0);const handleClick = () => setCount(count + 1);return (<button onClick={handleClick}>count: {count}</button>)
}
对比之下可以发现Vue 3.0的优势:
在React中,useState
和定义handleClick
的代码会在每次渲染时都执行,而Vue定义的组件重新渲染时只会执行setup
中最后返回的渲染方法,不会重复执行上面的那部分代码。
而且在Vue中,只需要更新对应的值即可触发视图更新,不需要像React那样调用setCount
。
当然Vue的这种定义组件的方式也带来了一些限制,setup
的参数props
是一个reactive
对象,不要对它进行解构赋值,使用时要格外注意这一点:
例如实现一个简单的展示内容的组件:
// 错误示例
import { defineComponent, reactive } from 'vue';interface LabelProps {content: string;
}
const Label = defineComponent({setup({ content }: LabelProps) {return () => <span>{content}</span>}
})
这样写是有问题的,我们在setup
的参数中直接对props
做了解构赋值,写成了{ content }
,这样在后续外部更新传入的content
时,组件是不会更新的,因为破坏了props
的响应机制。以后可以通过eslint之类的工具来避免这种写法。
正确的写法是在返回的方法里再对props
做解构赋值:
import { defineComponent, reactive } from 'vue';interface LabelProps {content: string;
}
const Label = defineComponent({setup(props: LabelProps) {return () => {const { content } = props; // 在这里对props做解构赋值return <span>{content}</span>;}}
})
生命周期方法
在Vue 3.0中使用生命周期方法也非常简单,直接将对应的方法import进来即可使用。
import { defineComponent, reactive, onMounted } from 'vue';interface LabelProps {content: string;
}
const Label = defineComponent({setup(props: LabelProps) {onMounted(() => { console.log('mounted!'); });return () => {const { content } = props;return <span>{content}</span>;}}
})
vue 3.0对tree-shaking非常友好,所有API和内置组件都支持tree-shaking。
如果你所有地方都没有用到onMounted
,支持tree-shaking的打包工具会自动将起去掉,不会打进最后的包里。
指令和过渡效果
Vue 3.0还提供了一系列组件和方法,来使JSX也能使用模板语法的指令和过渡效果。
使用Transition
在显示/隐藏内容块时做过渡动画:
import { defineComponent, ref, Transition } from 'vue';
import './style.less';const App = defineComponent({setup() {const count = ref(0);const handleClick = () => {count.value ++;}return () => (<div><button onClick={handleClick}>click me!</button><Transition name="slide-fade">{count.value % 2 === 0 ?<h1>count: {count.value}</h1>: null}</Transition></div>)}
})
// style.less
.slide-fade-enter-active {transition: all .3s ease;
}
.slide-fade-leave-active {transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to {transform: translateX(10px);opacity: 0;
}
也可以通过withDirectives
来使用各种指令,例如实现模板语法v-show
的效果:
import { defineComponent, ref, Transition, withDirectives, vShow } from 'vue';
import './style.less';const App = defineComponent({setup() {const count = ref(0);const handleClick = () => {count.value ++;}return () => (<div ><button onClick={handleClick}>toggle</button><Transition name="slide-fade">{withDirectives(<h1>Count: {count.value}</h1>, [[vShow, count.value % 2 === 0]])}</Transition></div>)}
})
这样写起来有点繁琐,应该可以通过babel-jsx插件来实现下面这种写法:
<h1 vShow={count.value % 2 === 0}>Count: {count.value}</h1>
优缺点
在我看来Vue 3.0 + TSX完全可以作为React的替代,它既保留了React Hooks的优点,又避开了React Hooks的种种问题。
但是这种用法也有一个难以忽视的问题:它没办法获得Vue 3.0编译阶段的优化。
Vue 3.0通过对模板的分析,可以做一些前期优化,而JSX语法是难以做到的。
例如“静态树提升”优化:
如下一段模板(这是模板,并非JSX):
<template><div><span>static</span><span>{{ dynamic }}</span></div>
</template>
如果不做任何优化,那么编译后得到的代码应该是这样子:
render() {return h('div', [h('span', 'static'),h('span', this.dynamic)]);
}
那么每次重新渲染时,都会执行3次h
方法,虽然未必会触发真正的DOM更新,但这也是一部分开销。
通过观察,我们知道h('span', 'static')
这段代码传入的参数始终都不会有变化,它是静态的,而只有h('span', this.dynamic)
这段才会根据dynamic
的值变化。
在Vue 3.0中,编译器会自动分析出这种区别,对于静态的节点,会自动提升到render
方法外部,避免重复执行。
Vue 3.0编译后的代码:
const __static1 = h('span', 'static');render() {return h('div', [__static1,h('span', this.dynamic)])
}
这样每次渲染时就只会执行两次h
。换言之,经过静态树提升后,Vue 3.0渲染成本将只会和动态节点的规模相关,静态节点将会被复用。
除了静态树提升,还有很多别的编译阶段的优化,这些都是JSX语法难以做到的,因为JSX语法本质上还是在写JS,它没有任何限制,强行提升它会破坏JS执行的上下文,所以很难做出这种优化(也许配合prepack可以做到)。
考虑到这一点,如果你是在实现一个对性能要求较高的基础组件库,那模板语法仍然是首选。
另外JSX也没办法做ref
自动展开,使得ref
和reactive
在使用上没有太大区别。
后话
我个人对Vue 3.0是非常满意的,无论是对TS的支持,还是新的Composition API,如果不限制框架的话,那Vue以后肯定是我的首选。
我的文章最先发表在我的[GitHub博客](https://github.com/hujiulong/blog/issues/11),欢迎关注
更新:
本文中通过TS的interface声明props类型的依赖vue3的Optional props decalration,但后续版本中这个功能被废除了,原因可以查看#154, 在#1155中也有一些替代方案的讨论
写jsx_使用Vue 3.0做JSX(TSX)风格的组件开发相关推荐
- jsx怎么往js里传参数_实践Vue 3.0做JSX(TSX)风格的组件开发
作者:莫夭 转发链接:https://zhuanlan.zhihu.com/p/102668383 前言 我日常工作都是使用React来做开发,但是我对React一直不是很满意,特别是在推出React ...
- vue tree组件_使用Vue 3.0做JSX(TSX)风格的组件开发
前言 我日常工作都是使用React来做开发,但是我对React一直不是很满意,特别是在推出React Hooks以后. 不可否认React Hooks极大地方便了开发者,但是它又有非常多反直觉的地方, ...
- Vue3.0实践:使用Vue3.0做JSX(TSX)风格的组件开发
前言 我日常工作都是使用React来做开发,但是我对React一直不是很满意,特别是在推出React Hooks以后. 不可否认React Hooks极大地方便了开发者,但是它又有非常多反直觉的地方, ...
- Muse-UI(基于 Vue 2.0 和 Material Desigin 的 UI 组件库)
基于 Vue 2.0 和 Material Desigin 的 UI 组件库,Muse UI 拥有40多个UI 组件,用于适应不同业务环境.Muse UI 自定义主题方式极为优雅,仅需少量代码即可完成 ...
- 他写出了 Vue,却做不对这十道 Vue 笔试题
有十道关于 Vue 的选择题,在群里引出了一众社区知名人士竞折腰,最后钓出了 Evan You 本人亲自挑战-- 然后他自己也做错了(其中的某两道). 鲁迅会做错鲁迅文选的阅读理解?有截图为证: 所以 ...
- java和vue实现拖拽可视化_Vue拖拽组件开发实例详解
摘要:这篇Vue栏目下的"Vue拖拽组件开发实例详解",介绍的技术点是"Vue拖拽组件开发实例.vue拖拽组件.拖拽组件.组件开发.开发实例.实例详解",希望对 ...
- vue两个卡片并排_vue--卡片层叠 组件 开发小记
背景:影城移动点餐web App增加会员卡支付功能 需求:确认订单页点击会员卡项弹出会员卡列表,多张会员卡依次叠加覆盖上一张80%的高度,点击任意卡片则改卡片置为当前卡片,只有当前卡片显示全部卡片信息 ...
- Vue + EChart4.0 从0到1打造商业级数据报表项目
Vue + EChart4.0 从0到1打造商业级数据报表项目 环境搭建 相关配置 安装脚手架 创建项目 项目初始化 安装element插件 安装echarts 组件 创建组件 嵌套组件到Home组件 ...
- JSX/TSX的知识介绍
JSX/TSX 基本介绍 JSX和TSX是一种基于JavaScript的语法扩展,用于在React和Vue.js等框架中编写可复用的UI组件和控制逻辑.JSX/TSX可以帮助开发者更加直观和高效地编写 ...
- 【精讲】vue组件开发基础、多层嵌套(内含详细注释)、vuecomponent构造函数
目录 vue组件开发基础 多层嵌套(内含详细注释) vuecomponent构造函数 第一部分:vue组件开发基础 <!-- vue中使用组件的三大步骤: 一 , ...
最新文章
- [转]计算机读研的取向
- 源码编译 busybox
- MySql数据库使用入门
- 小项目--bank1
- uniapp H5页面嵌入微信小程序 ios 下 video组件 播放视频 设置 border-radius overflow:hidden 不生效
- 15个基本的C#面试问题
- 【bzoj 2434】【codevs 1946】[Noi2011]阿狸的打字机(AC自动机)
- OAuth认证原理及HTTP下的密码安全传输
- Python描述符是什么?
- java读写文件操作
- 为什么我感觉生活不易挣钱太难
- [考试反思]0813NOIP模拟测试20
- 实施云计算之后如何保证安全
- 破解版xftp下载地址
- 新版Idea设置代码提示背景色
- 软媒时间3.11正式版发布:天气信息获取更迅速
- MATLAB中PI调节器设计,简单的PI控制器的设计.doc
- 国产系统deepin。为什么要国产化?国产化意味着什么?(含Deepin系统部分问题解决)
- 计算机经常自动关机怎么回事,教你电脑总是自动关机怎么办
- ACM程序设计 -L (字符串倒输出)