# Vue JSX

# render渲染函数 (opens new window)

# createElement

createElement参数 (opens new window)

第二个参数: https://juejin.cn/post/7030362678199582734#heading-2

render(h) {
    h('div',{
      class: {}, // 类名
      domProps: {}, // DOM 属性
      props: {},
      attrs: {},
      style: {}, // 样式
      on: {}, // 绑定事件
    })
  }
1
2
3
4
5
6
7
8
9
10

在vue文件中写render时不要写template,否则不会渲染。

<script>
export default {
  name: 'Home',
  render(h) {
    return h('h1', 'some') // h即createElement
  },
}
</script>
1
2
3
4
5
6
7
8

当使用render函数渲染一个函数式组件时,渲染函数还会接收一个额外的 context 参数。

<script>
export default {
  name: 'Func',
  functional: true,
  render(h, { props }) {
    console.log(props)
    return (
      <div>
        <p>这是一个函数组件</p>
      </div>
    )
  },
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# h auto-injection

Starting with version 3.4.0 we automatically inject const h = this.$createElement in any method and getter (not functions or arrow functions) declared in ES2015 syntax that has JSX so you can drop the (h) parameter.

从3.4.0开始h会被自动注入

# 在Vue2.x中使用jsx

yarn add @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props -D
1

按照教程 (opens new window)中安装了@vue/babel-preset-jsx和 @vue/babel-helper-vue-jsx-merge-props后报错

Duplicate declaration "h"。

官网有这样一段话

我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 自动注入 const h = this.$createElement,这样你就可以去掉 (h) 参数了。

而babel-preset-jsx应该是也注入了h所以导致报错。

设置babel.config.js

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
    ['@vue/babel-preset-jsx', { injectH: false }]
  ]
}
1
2
3
4
5
6

如果是Webpack构建的项目,则设置injectH: true

# ESLint报错

https://eslint.vuejs.org/user-guide/#faq

如果您使用 JSX,则需要在 ESLint 配置中启用 JSX。

  "parserOptions": {
      "ecmaVersion": 2020,
      "ecmaFeatures": {
+         "jsx": true
      }
  }
1
2
3
4
5
6

# Vue jsx接收父组件传递过来的参数

在SFC中我们在子组件中通过定义props来接收,而在jsx中我们一样可以通过定义props来接收,而如果不想用props接收,则属性默认挂载在$attrs上

关于props和$attrs,Vue官网的说明 (opens new window)

Vue jsx 中事件名第一个单词可以大写,后面必须小写,比如:

onMousemove 、 onDblclick

或者统一小写 onmousemove、 ondblclick

# jsx中如何向上传递数据

emit传递,通过setup第二个参数ctx 中的emit向上传递

// 子组件
const handleInput = (e) => {
  ctx.emit('do-input', e)
}
<el-input
  vModel={this.name}
  class="input"
  onInput={this.handleInput}
></el-input>

// 父组件
const name = ref('name')
const doInput = (e) => {
  name.value = e
}
<Mouse
  render={(mouse) => <Cat mouse={mouse} />}
  ondo-input={this.doInput}
></Mouse>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

emit event name 事件名最好用小写

# jsx语法使用elementui的el-table-column插槽功能

<template>
<el-table-column label="性别">
  <template>
    <el-input v-model="name"></el-input>
  </template>
</el-table-column>
</template>

// 使用jsx

<el-table-column>
  {<el-input vModel={this.name}></el-input>}
</el-table-column>
1
2
3
4
5
6
7
8
9
10
11
12
13

# JSX 具名插槽

JSX中使用插槽与createElement (opens new window)

// 子组件
<template>
  <div>
    <div class="header">
      <slot name="header">header</slot>
    </div>
    <div class="body">
      <slot></slot>
    </div>
    <div class="footer">
      <slot name="footer">footer</slot>
    </div>
  </div>
</template>

// 父组件 使用插槽
<ChildVue>
  {<div slot="header">this is body</div>}
  {<div>this is body</div>}
  {<div slot="footer">this is footer</div>}
</ChildVue>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

子组件使用jsx render

render() {
  return (
    <div>
      <div class="header">{this.$slots.header}</div>
      <div class="body">{this.$slots.default}</div>
      <div class="footer">{this.$slots.footer}</div>
    </div>
  )
},
1
2
3
4
5
6
7
8
9

作用域插槽

<user :obj="'123'">
  <template v-slot:default="slotProps">
    {{ slotProps }}
    {{ slotProps.obj }}
  </template>
</user>

// 子组件
<slot name="default" :user="user" :obj="obj">header</slot>
props: {
  obj: String
}
1
2
3
4
5
6
7
8
9
10
11
12

JSX作用域插槽

// 父组件 使用插槽
<UserVue
  obj="this is obj1"
  scopedSlots={{
    default: (props) => {
      console.log(props)
      return <div style="backgroundColor:orange">{props.obj}今年</div>
    },
    footer: (props) => `text${props.obj}`,
  }}
>
  <div slot="header">header</div>
</UserVue>

// 子组件 定义插槽
// 这里使用?.() 去调用,是为了避免作用域插槽未使用时会报错
render() {
  return (
    <div>
      <div class="header">{this.$slots.header}</div>
      <div class="body" obj={this.obj}>
        {this.$scopedSlots.default?.(this.$props)}
      </div>
      <div class="footer" obj={this.obj}>
        {this.$scopedSlots.footer?.(this.$props)}
      </div>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

jsx中的v-if

{this.isShow ? <div>123</div> : <div>456</div>}
1

v-show(v-show正常生效)

<div vShow={true}>v-show指令</div>
1

v-for

{[1, 2, 3].map((item, index) => { return <p key={index}>{item}</p> })}
1

# JSX函数组件

import { ref } from '@vue/composition-api'
import { Button } from 'element-ui'
const b = ref('b')

/* eslint-disable */
const ComponentA = (props) => {
  const a = ref('123')
  console.log('a: ', a.value)
  return (
    <div>
      <h3>{a.value}</h3>
      <h4>{b.value}</h4>
      <button
        onClick={() => {
          console.log(123)
        }}
      >
        click me
      </button>
      <el-button
        onClick={() => {
          console.log('ele click')
        }}
      >
        ele click
      </el-button>
      <Button
        onClick={() => {
          console.log('ele click')
        }}
      >
        ele click
      </Button>
    </div>
  )
}

export default ComponentA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 使用TSX

报错:JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.ts(7026)

因为不存在全局类型 "JSX.Element"。

解决办法有俩种:

  1. 不使用严格的类型检查,即tsconfig.json 中设置 "strict": false
  2. "noImplicitAny": false, // 是否在表达式和声明上有隐含的any类型时报错

2.报错Cannot find name 'React'

tsconfig中"jsx"需要设置为"preserve"

# 在Vue3.0中使用JSX

Babel Plugin JSX for Vue 3.0 (opens new window)

yarn add @vue/babel-plugin-jsx -D
1

Vite使用JSX => https://cn.vitejs.dev/guide/features.html#jsx

vite.config.ts

export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
})
1
2
3
4
5

此外编辑器不认识tsx标签,报类型错误,需要在tsconfig.json中配置

{
    "compilerOptions": {
        "jsx": "preserve",
        "jsxImportSource": "vue",
    }
}
1
2
3
4
5
6

tsx组件

import { defineComponent, ref } from "vue"

export default defineComponent({
  setup() {
    const count = ref(100)
    const handleClick = () => {
      count.value++
    }

    return () => (
      <div>
        <span>{count.value}</span>
        <button onClick={handleClick}>click</button>
      </div>
    )
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

需要注意的事项:

  1. return需要返回一个Function,这与React不同
  2. 通过ref声明的变量在JSX中需要通过.value去访问

# 接收props有两种方式

  1. defineComponent方式

    import { defineComponent, ref } from "vue"
    
    export default defineComponent({
      props: {
        name: String
      },
      setup(props) {
        const count = ref(100)
        const handleClick = () => {
          count.value++
        }
    
        return () => (
          <div>
            <span>{props.name}</span>
            <span>{count.value}</span>
            <button onClick={handleClick}>click</button>
            <input type="text" v-model={count.value} />
          </div>
        )
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

# 在vue3中使用tsx需要在tsconfig中配置

{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "vue",
}
1
2
3
4
5

那

"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",
1
2

这段配置呢

这段配置是 Vue2/手写 runtime JSX 的旧写法,在 Vue3 里正常情况下 不需要再写,甚至写了之后会和 @vitejs/plugin-vue-jsx 产生冲突。