vue3-render函数(h)&jsx

9,161 阅读4分钟

h函数

h函数可以在两个地方使用

  • Options API 的render函数选项中;
  • setup函数选项中(setup本身需要是一个函数类型,函数再返回h函数创建的VNode);

h函数参数:

第一个参数既可以是一个字符串 (用于原生元素) 也可以是一个 Vue 组件定义。第二个参数是要传递的 prop,第三个参数是子节点。

当创建一个组件的 vnode 时,子节点必须以插槽函数进行传递。如果组件只有默认槽,可以使用单个插槽函数进行传递。否则,必须以插槽函数的对象形式来传递。

为了方便阅读,当子节点不是插槽对象时,可以省略 prop 参数。

比有如下一个template结构, 需要使用h函数创建出来

方法1. 在Options API中的使用

import { h } from "vue"

export default {
  render() {
    return h("div", {className: "app"}, [
      h("h2", {className: "title"}, "我是标题"),
      h("p", null, "我是内容"),
    ])
  }
}

import { defineComponent } from "vue";
export default defineComponent({
  name: "Jsx",
  render() {
    return <div>我是一个div</div>;
  },
});

image.png

  • render函数不仅可以传入普通元素, 也可以传入一个组件
import { h } from "vue"
import Home from "./home.vue"

export default {
  render() {
    return h("div", { className: "app" }, [
      // 因为不是在模板中使用, 因此无需注册, 直接使用
      h(Home)
    ])
  }
}

方法2. 在Composition API中的使用

<script>
import { h } from "vue"

export default {
  setup() {
    // setup是一个函数, 让这个函数再返回一个函数
    return () => h("div", { class: "app" }, [
      h("h2", { class: "title" }, "我是标题"),
      h("p", null, "我是内容")
    ])
  }
}
</script>

  • 如果是在<script setup>标签中使用h函数, 需要如下方式(会变得很繁琐)
<template>
  <!-- 将render函数变量写在temolate标签中 -->
  <render></render>
</template>

<script setup>
import { h, ref } from "vue"

const conter = ref(0)

    const increment = () => {
      conter.value ++
    }

    const decrement = () => {
      conter.value --
    }

    // 将这个render函数保存到一个变量中
    const render = () => h("div", { class: "app" }, [
      h("h2", { class: "title" }, `当前计数: ${conter.value}`),
      h("button", { onclick: increment }, "+"),
      h("button", { onclick: decrement }, "-")
    ])
</script>

image.png

render函数虽然可以让我们使用JavaScript完全提升编程能力, 但是render函数使用起来比起之前是会变得更加繁琐, 因此我们开发中基本不使用rander函数

如果我们确实想要使用完全JavaScript编程, 那么在开发中, 更为常见的是使用jsx

JSX

使用规范:

  • JSX支持换行
let jsx = (
    <div>
        <h1>hello world</h1>
        <button/>
    </div>
)

1、jsx顶部只能有一个根元素,通常用

包起来(在Vue3中也可以使用和React一样不占据Dom结构的Fragment<></>空标签)。
2、为了方便阅读,jsx外面需要包上一层小括号()
3、标签要闭合(单边标签得有斜杠)

  • jsx的注释都需要用花括号包起来
{
    //我是单行注释
}

{/*我是一段注释*/}
  • jsx插入变量
const _c = 'hello world';
let jsx = (
    <div>
        <h1>{ _c }</h1>
    </div>
)
  • jsx表达式嵌入
// 运算表达式
const a = 1;
const b = 1;
let jsx = (
  <div>{ a + b }</div>
)

// 三元表达式
let _t = 'hello world';
let a = 1;
let b = 2;
let hasButton = true;
let jsx = (
  <>
    <h1>{ _t === 'hello world' ? a :b }</h1>
    {
      //如果hasButton为true,则渲染button组件
      hasButton && <button/>
     }
  </>
)

//函数调用
const func1 = ()=>{ return (<div>func1</div>) }
let jsx = (
    <div>
        <h1>
        {
            //如果在render外定义的函数,请注意加this:this.func1()
            func1()
        }
        </h1>
    </div>
)

  • JSX 绑定属性
// 绑定普通属性
let jsx = (
    <>
        <h1 title={title}>hello world</h1>
    </>
)

// 绑定style
/**
 在jsx中,windows风格的命名方式(属性、style、方法、event)都需要转换成驼峰式的写法,比如,正常写一个style指定左边的外边距:**margin-left:‘10px’**,需要换成:**marginLeft: '10px'** ;
*/
let jsx = (
    <>
        <h1 style={{ marginLeft:'10px',width:'100%' }}>hello world</h1>
    </>
)

// 绑定class
/**
在jsx中,class属性需要指定为className,因为class在JavaScript中是保留字段
*/
const hasCss = true;
const h1Css = [
    'flex',
    hasCss ? 'css' : 'noCss',
]
let jsx = (
    <>
        <h1 className='flex'>hello world</h1>
        <h1 className={h1Css}>hello world</h1>
    </>
)
/**
在vue3+jsx中,jsx文件里面可以用css模块化的方式进行样式导入后绑定。即在vue.config.js文件中css配置项中开启css模块化(requireModuleExtension: true),然后把css文件命名设置成***.module.less
*/
import style from './style.module.less'
let jsx = (
    <>
        <h1 className={style.h1Css}>hello world</h1>
    </>
)
  • JSX 绑定事件

JSX中绑定事件类似在HTML原生中绑定事件,只不过React中事件命名采用小驼峰(camelCase),而不是纯小写;(Vue3中经过测试也通用)

function fnc(){}
let jsx = (
  <>
     <button onClick={this.fnc}/>
  </>
)
  • JSX 条件渲染

在jsx中,不允许使用if、if-else,请使用三元运算符或者逻辑与&&
同样,也不允许使用for循环,请使用JS中的高阶函数map、filter……

const t = 'hello world';
const arg1 = 1;
const arg2 = 2;
const hasButton = true;
const list = [1,2,3,4,5,6,7,8,9];
let jsx = (
    <div>
        <h1>
        {
            t === 'hello world' ?  arg1 : arg2
        }
        </h1>
    {
            //如果hasButton为true,则渲染button组件
            hasButton && <button/>
        }
        <ul>
        {
            list.map((item) => <li>{item}</li>)
        }
        </ul>
    </div>
)

webpack环境下安装Babel支持Vue的jsx插件npm install @vue/babel - plugin -j sx -D

在babel.config.js配置文件中配置插件

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    "@vue/babel-plugin-jsx"
  ]
}

Vite环境下,需要安装插件npm install @vitejs/plugin-vue-jsx -D

在vite.config.js配置文件中配置插件

import jsx from '@vitejs/plugin-vue-jsx'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    jsx()
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
}

示例:

<!-- 告知script标签使用jsx -->
<script lang="jsx">
import { ref } from "vue"

export default {
  setup() {
    const counter = ref(0)

    const increment = () => {
      counter.value ++
    }

    const decrement = () => {
      counter.value --
    }


    return () => (
      <div class="app">
        <h2>当前计数: { counter.value }</h2>
        <button onclick={ increment }>+</button>
        <button onclick={ decrement }>-</button>
      </div>
    )
  }
}
</script>