Skip to content

JSX 简介:https://zh-hans.reactjs.org/docs/introducing-jsx.html

比较

template

学习成本低 大量内置指令简化开发 组件作用域 CSS

  • 模版语法(HTML 的扩展)
  • 数据绑定使用 Mustache 语法(双大括号)
vue
<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-forv-if,分别条件运算符 (?:) 替代v-ifarray.map 替代 v-for

jsx
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

jsx
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>

jsx
render() {
  const propertiesData = { a: 1, b: 2 }
  /**
   * template
   * <div v-bind="propertiesData"></div>
   */
  return (
    <div {...propertiesData}>
    	<a href={this.href}>link</a>
    </div>
  )
}

class绑定

注:与 reactjsx 绑定的有区别,react 中使用 classNamevue 中使用 class

jsx
<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')}**

jsx
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

jsx
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
// 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"
/>

插槽

jsx
// 默认插槽
// 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 属性声明

jsx
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 名称(如果你发现我的代码没有这样写,那一定是幻觉,请偷偷给我提醒下),如下所示

jsx
<d-select @update:value="..." />

// 在 JSX 中可以写为
<DSelect onUpdateValue={...} />

样式模块化

样式文件必须已 .module 文件名结尾才能使用模块的方式 vue 单文件使用 scoped 可以实现模块化:<style scoped>

Button.tsx

typescript
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

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 名称为主

image.png

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 的一个语法糖