jsx怎么往js⾥传参数_实践Vue3.0做JSX(TSX)风格的组件
开发
作者:莫夭
前⾔
我⽇常⼯作都是使⽤React来做开发,但是我对React⼀直不是很满意,特别是在推出React Hooks以后。
前⾯⽂章⼩编也详细整理了React.js和Vue.js的语法:
⼀篇⽂章教你并列⽐较React.js和Vue.js的语法【实践】
不可否认React Hooks极⼤地⽅便了开发者,但是它⼜有⾮常多反直觉的地⽅,让我难以接受。所以在很长⼀段时间,我都在尝试寻
React的替代品,我尝试过不少别的前端框架,但都有各种各样的问题或限制。
更多关于React和Vue 学习实践项⽬相关⽂章,请见本篇⽂章底部
在看到了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: Inp
可以看到写法和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 =
实际上会被babel插件转换为下⾯这⾏代码:
const input = ateElement('input', { value: 'text' });
Vue 3.0也提供了⼀个对应ateElement的⽅法h。但是这个h⽅法⼜和vue 2.0以及React都有⼀些不同。
例如这样⼀段代码:
在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})
基本上是传⼊什么就是什么,没有做额外的处理。
当然和ateElement相⽐也有⼀些区别:
⼦节点不会作为以children这个名字在props中传⼊,⽽是通过slots去取,这个下⽂会做说明。
多个⼦节点是以数组的形式传⼊,⽽不是像React那样作为分开的参数
所以只能⾃⼰动⼿来实现这个插件,我是在babel-plugin-transform-react-jsx的基础上修改的,并且⾃动注⼊了h⽅法。
实际使⽤
在上⾯的⼯作完成以后,我们可以真正开始做开发了。
渲染的节点
上⽂说到,⼦节点不会像React那样作为children这个prop传递,⽽是要通过slots去取:
例如实现⼀个Button组件
// button.tsximport { defineComponent } from 'vue';import './style.less';interface ButtonProps { type: 'primary' | 'dashed' | 'link'}const Button = defineCompo 然后我们就可以使⽤它了:
import { createApp } from 'vue';import Button from './button';// vue 3.0也⽀持函数组件const App = () => Click Me!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 = (
渲染结果
这个Counter组件如果⽤React Hooks来写:
import React, { useState } from 'react';const Counter = () => { const [count, setCount] = useState(0); const handleClick = () => setCount(count + 1); retur
对⽐之下可以发现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
这样写是有问题的,我们在setup的参数中直接对props做了解构赋值,写成了{ content },这样在后续外部更新传⼊的content时,组件
是不会更新的,因为破坏了props的响应机制。以后可以通过eslint之类的⼯具来避免这种写法。
正确的写法是在返回的⽅法⾥再对props做解构赋值:
import { defineComponent, reactive } from 'vue';interface LabelProps { content: string;}const Label = defineComponent({ setup(props: LabelProps) { ret
⽣命周期⽅法
在Vue 3.0中使⽤⽣命周期⽅法也⾮常简单,直接将对应的⽅法import进来即可使⽤。
import { defineComponent, reactive, onMounted } from 'vue';interface LabelProps { content: string;}const Label = defineComponent({ setup(props: LabelP
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 handleC
click me! {count.value % 2 === 0 ?
count: {count.value}
: null}
) }})
// 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, .s
渲染结果原生js和js的区别
也可以通过withDirectives来使⽤各种指令,例如实现模板语法v-show的效果:
import { defineComponent, ref, Transition, withDirectives, vShow } from 'vue';import './style.less';const
App = defineComponent({ setup() { const count =
toggle {withDirectives(
Count: {count.value}
, [[ vShow, count.value % 2 === 0 ]])}
) }})
这样写起来有点繁琐,应该可以通过babel-jsx插件来实现下⾯这种写法:
Count: {count.value}
优缺点
在我看来Vue 3.0 + TSX完全可以作为React的替代,它既保留了React Hooks的优点,⼜避开了React Hooks的种种问题。
但是这种⽤法也有⼀个难以忽视的问题:它没办法获得Vue 3.0编译阶段的优化。
Vue 3.0通过对模板的分析,可以做⼀些前期优化,⽽JSX语法是难以做到的。
例如“静态树提升”优化:
如下⼀段模板(这是模板,并⾮JSX):
static {{ dynamic }}
如果不做任何优化,那么编译后得到的代码应该是这样的:
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以后肯定是我的⾸选。推荐React和Vue学习资料⽂章:
《⼀篇⽂章教你并列⽐较React.js和Vue.js的语法【实践】》
《⼿拉⼿带你开启Vue3世界的⿁斧神⼯【实践】》
《深⼊浅出通过vue-cli3构建⼀个SSR应⽤程序【实践】》
《怎样为你的 Vue.js 单页应⽤提速》
《聊聊昨晚尤⾬溪现场针对Vue3.0 Beta版本新特性知识点汇总》
《【新消息】Vue 3.0 Beta 版本发布,你还学的动么?》
《Vue真是太好了 壹万多字的Vue知识点 超详细!》
《Vue + Koa从零打造⼀个H5页⾯可视化编辑器——Quark-h5》
《深⼊浅出Vue3 跟着尤⾬溪学 TypeScript 之 Ref 【实践】》
《⼿把⼿教你深⼊浅出vue-cli3升级vue-cli4的⽅法》
《Vue 3.0 Beta 和React 开发者分别杠上了》
《⼿把⼿教你⽤vue drag chart 实现⼀个可以拖动 / 缩放的图表组件》
《Vue3 尝鲜》
《总结Vue组件的通信》
《⼿把⼿让你成为更好的Vue.js开发⼈员的12个技巧和窍门【实践】》
《Vue 开源项⽬ TOP45》
《2020 年,Vue 受欢迎程度是否会超过 React?》
《尤⾬溪:Vue 3.0的设计原则》
《使⽤vue实现HTML页⾯⽣成图⽚》
《实现全栈收银系统(Node+Vue)(上)》
《实现全栈收银系统(Node+Vue)(下)》
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论