JSX 简介:https://zh-hans.reactjs.org/docs/introducing-jsx.html
比较
template
学习成本低 大量内置指令简化开发 组件作用域
CSS
- 模版语法(
HTML
的扩展) - 数据绑定使用
Mustache
语法(双大括号)
<span>{{ msg }}</span>
jsx|tsx
灵活
JavaScript
的语法扩展- 数据绑定使用单引号
扩展
引入
Vue
官方文档(https://cn.vuejs.org/v2/guide/comparison.html#JSX-vs-Templates)更抽象一点来看,我们可以把组件区分为两类:一类是偏视图表现的
**(presentational)**
,一类则是偏逻辑的**(logical)**
。我们推荐在前者中使用模板,在后者中使用**JSX**
或渲染函数。这两类组件的比例会根据应用类型的不同有所变化,但整体来说我们发现表现类的组件远远多于逻辑类组件。
常用
v-for 与 v-if
jsx|tsx
没有v-for
与v-if
,分别条件运算符(?:)
替代v-if
、array.map
替代v-for
render() {
return (
// v-if
{this.type && <div>true</div>}
// v-if / v-else
{this.type ? <div>true</div> : <div>false</div>}
// v-for
{this.options.map(item => {
<div>{item.title}</div>
})}
)
}
指令
大多数是不支持的,原生指令,只有
v-show
只支持的v-model
: 语法糖(value + input), 但是在vue-cli4中,是默认支持的,如果比较老的项目,可以使用babel-plugin-jsx-v-model
支持v-model_trim
>domPropsInnerHTML
等价于v-html
>domPropsTextContent
等价于v-text
render() {
return (
<div><input v-model={this.keyValue} /></div>
<div><input v-model_trim={this.keyValue} /></div>
<div><p domPropsInnerHTML={this.html} /></div>
<div><p domPropsTextContent={this.text} /></div>
)
}
属性
在
template
中,一般通过使用v-bind:prop="value"
或:prop="value"
来给组件绑定属性,在jsx|tsx
里面就不能继续使用v-bind指令了,而是通过单大括号的形式进行绑定批量绑定标签属性
template
:<div v-bind="propertiesData"></div>
>jsx|tsx
:<div {...propertiesData}></div>
render() {
const propertiesData = { a: 1, b: 2 }
/**
* template
* <div v-bind="propertiesData"></div>
*/
return (
<div {...propertiesData}>
<a href={this.href}>link</a>
</div>
)
}
class绑定
注:与
react
的jsx
绑定的有区别,react
中使用className
,vue
中使用class
<button
onClick={handleClick}
disabled={disabled}
type={'button'}
v-loading={loading}
class={[
'ws-btn',
'ws-button',
type ? `ws-button--${type}` : '',
{
'is-disabled': disabled,
'is-circle': circle,
'is-block': block,
},
]}
>
{defaultSlots}
</button>
事件
遵循一个规则或者如下:事件绑定需要在事件名称前端加上
on
前缀(on + 事件名称),原生事件添加nativeOn
@click
等价于on-click
等价于onClick
等价于vOn:click
(推荐**onClick**
)vOn:keyup_enter_native
>@click.native
等价于nativeOnClick
注意: > 传递参数不能直接使用
**onClick={this.search('params')}**
,这会导致**jsx|tsx**
每次都会**render**
会自执行一次方法(重则会死循环) > 处理: > **应该使用**bind**
,或箭头函数来处理传参 ****onClick={()=> this.search('params')}**
render() {
return (
<div><input v-model={this.keyValue} on-input={this.inputText} /></div>
<div><input v-model={this.keyValue} vOn:click_stop_prevent={this.inputText} /></div>
<div><input v-model={this.keyValue} vOn:keyup_enter_native={() => this.search()} /></div>
<div><el-button nativeOnClick={this.handleClick}>Native click</el-button></div>
)
}
除了上面的监听事件的方式之外,我们还可以使用对象的方式去监听事件 事件 & 按键修饰符更多可查看:link
import { CdInput } from '@hj-money/components'
render() {
return (
<CdInput
value={this.inputValue}
on={{
focus: this.handleFocus,
input: this.handleInput
}}
nativeOn={{
click: this.handleClick
}}
/>
)
}
监听.sync修饰符的事件
https://github.com/vuejs/babel-plugin-transform-vue-jsx/blob/master/example/example.js
// jsx | tsx
<el-dialog
visible={this.dialogVisible}
{...{ on: { 'update:visible': console.log } }}
before-close={() => (this.dialogVisible = false)}
/>
// jsx 发现这样写也ok
// 在 vue2.x 版本使用 tsx 这种方式打包会报错
<el-dialog
visible:sync={this.dialogVisible}
before-close={() => (this.dialogVisible = false)}
/>
// template
// 在 vue2.x 版本使用 tsx 这种方式打包会报错
<el-dialog
:visible.sync="dialogVisible"
:before-close="handleClose"
/>
插槽
// 默认插槽
// this.$slots.default
export default {
render() {
return (
<div class="custom-dialog">
{this.$slots.default}
</div>
)
}
}
// 具名插槽
// this.$slots.footer
import { CdDialog } from '@hj-money/components'
// use
render() {
return (
<CdDialog title="title" visible={this.visible}>
<div>content</div>
<template slot="footer">
<ElButton>confirm</ElButton>
<ElButton>cancel</ElButton>
</template>
</ElDialog>
)
}
// components
render() {
return (
<div vShow={this.visible}>
{this.$slots.default}
<div>{this.$slots.footer}</div>
</div>
)
}
// 作用域插槽(没有v-slot)
import { CdTable, CdTableColumn } from '@hj-money/components'
render() {
return (
<CdTable data={this.data}>
<CdTableColumn
label="姓名"
// scopedSlots 即作用域插槽,default为默认插槽,如果是具名插槽,将default该为对应插槽名称即可
scopedSlots={{
default: ({ row }) => {
return <div>{row.name}</div>
}
}}
/>
</ElTable>
)
}
自定义组件
导入直接使用,不需要在
components
属性声明
import HelloWolrd from './HelloWorld'
export default {
name: 'App',
render() {
return <HelloWorld msg='Welcome to Your Vue.js App' />
},
}
新版本
所有形如 on-update:*
的 prop
都有一个对应的 onUpdate*
属性可供使用。
由于
JSX
自身的规定,on-update:*
和onUpdate:*
不是合法的prop
名称(如果你发现我的代码没有这样写,那一定是幻觉,请偷偷给我提醒下),如下所示
<d-select @update:value="..." />
// 在 JSX 中可以写为
<DSelect onUpdateValue={...} />
样式模块化
样式文件必须已
.module
文件名结尾才能使用模块的方式vue
单文件使用scoped
可以实现模块化:<style scoped>
Button.tsx
import Vue, { VNode } from 'vue'
// @ts-ignore
import ButtonModule from './Button.module.less'
export default Vue.extend({
name: 'Button',
props: {
type: String,
loading: Boolean,
disabled: Boolean,
circle: Boolean,
block: Boolean,
},
methods: {
handleClick(event: MouseEvent) {
if (this.loading) {
event.preventDefault()
} else if (!this.disabled) {
this.$emit('click', event)
}
},
},
render(): VNode {
const { type, loading, disabled, circle, block, $slots, handleClick } = this
const loadingNode = () => {
return loading && <van-loading color='inherit' size='15px' />
}
const defaultSlots = () => {
return (
$slots?.default && (
<span class={loading ? ButtonModule['button--loading-text'] : null}>
{$slots.default}
</span>
)
)
}
return (
<button
onClick={handleClick}
disabled={disabled}
type={'button'}
class={[
ButtonModule['btn'],
ButtonModule['button'],
type ? ButtonModule[`button--${type}`] : '',
disabled ? ButtonModule['is-disabled'] : '',
loading ? ButtonModule['is-loading'] : '',
circle ? ButtonModule['is-circle'] : '',
block ? ButtonModule['is-block'] : '',
]}
>
{loadingNode()}
{defaultSlots()}
</button>
)
},
})
Button.module.less
@theme-color: #294ba3;
@theme-color-active: #26479a;
@white: #fff;
@secondary-font-color: #545454;
.button {
display: inline-block;
height: 0.88rem;
line-height: 0.88rem;
white-space: nowrap;
cursor: pointer;
background: @white;
border: 1px solid #ddd;
color: @secondary-font-color;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: none;
margin: 0;
transition: 0.1s;
font-weight: 500;
user-select: none;
padding: 0 0.24rem;
font-size: 15px;
border-radius: 0.12rem;
&.is-circle {
height: 28px;
line-height: 28px;
border-radius: 0.28rem;
}
&.is-loading {
display: flex;
justify-content: center;
align-items: center;
.van-loading {
display: flex;
color: inherit;
font-size: inherit;
margin-right: 0.5em;
}
}
&.is-active,
&:active {
background: #fafafa;
}
&.is-disabled,
&.is-disabled:active,
&.is-disabled:focus,
&.is-disabled:hover {
cursor: not-allowed;
color: #ccc;
background-color: @white;
}
&--primary {
border-color: @theme-color;
background: @theme-color;
color: @white;
&.is-disabled,
&.is-disabled:active,
&.is-disabled:focus,
&.is-disabled:hover {
cursor: not-allowed;
color: @white;
background-color: #ddd;
border-color: #ddd;
}
&.is-active,
&:active {
background: @theme-color-active;
}
}
&--border {
color: #294ba3;
background: @white;
border-color: #3975c6;
&.is-disabled,
&.is-disabled:active,
&.is-disabled:focus,
&.is-disabled:hover {
cursor: not-allowed;
color: #ccc;
background-color: @white;
border-color: @white;
}
&.is-disabled,
&.is-disabled:active,
&.is-disabled:focus,
&.is-disabled:hover {
cursor: not-allowed;
color: #ccc;
background-color: @white;
border-color: @white;
}
}
&--text {
height: 0.88rem;
background: transparent;
border: none;
}
& + .button {
margin-left: 0.08rem;
}
&--loading-text {
opacity: 0.2;
}
}
.is-block {
width: 100%;
display: block;
}
效果如下
命名规则以
XXX.module
名称为主
coding
vue2:https://github.com/WuChenDi/Front-End/tree/master/05-Vue/vue2-jsx
参考:
vue2
https://github.com/vuejs/jsx#readme
Babel Plugin JSX for Vue 3.0
https://github.com/vuejs/jsx-next#readme
vite
https://github.com/vitejs/vite/tree/main/packages/plugin-vue-jsx#readme
注:
vue jsx 2X 版本不支持空标签 <></>
的写法,3X 支持 React 中可以使用空标签 <></>
和 <react.Fragment></react.Fragment>
来实现包裹元素,其实空标签本质就只是 react.Fragment
的一个语法糖