Vue 渲染函数 & JSX

Vue - template

Vue 官方推荐使用template语法来创建应用,虽然写法像html,但Vue最终会把template解析为render函数返回虚拟DOM,这点可以在Vue Dev Tools中看到:

template渲染流程:

因此在某些特定情况下,我们可以直接使用render函数来实现我们的组件。

示例

根据接口返回的数值level,动态渲染标题组件h1~h6

采用Vue的模板语法,实现如下:

<template>
    <h1 v-if="level==1" class="template_class"><slot></slot></h1>
    <h2 v-if="level==2" class="template_class"><slot></slot></h2>
    <h3 v-if="level==3" class="template_class"><slot></slot></h3>
    <h4 v-if="level==4" class="template_class"><slot></slot></h4>
    <h5 v-if="level==5" class="template_class"><slot></slot></h5>
    <h5 v-if="level==6" class="template_class"><slot></slot></h5>
</template>

<script>
export default {
    props: {
        level: Number,
    },
}
</script>

不过实现过程有些冗余了。因此我们可以使用render函数来动态返回我们的组件。在此之前我们需要先了解下Vue中的h函数;

Vue - Render & H

h 函数

h函数是Vue创建一个Vnode的函数。

<!--模板语法-->
<div class="className" style="color:red" @click="clicked">
     <h1>hello,world</h1>
</div>

等价于

let vnode = h(
                'div',     //标签名
                {
                    class: 'className',
                    style: { color: 'red' },
                    onClick() {
                        console.log("点击事件")
                    }
                },         ///标签属性
                h(         ///子节点
                    'h1',
                    'hello,world'
                ),
              )

render函数

render函数在Vue中可以代替template标签,直接返回虚拟DOM

接下来,我们使用render函数的方式实现示例需求,有三种写法:

Vue 2

<script>
import { h } from 'vue'
export default {
    props: {
        level: Number,
    },
    methods: {
        clicked() {
            alert('点击事件')
        }
    },
    render() {
        let tag = 'h' + this.level
        return h(
            tag,//tag type
            {
                class: 'head_h',
                onClick: this.clicked
            }, ///props
            this.$slots.default() //children 
        )
    }
}
</script>

Vue 3

<script>
import { h } from 'vue'
export default {
   props: {
       level: Number,
   },
   setup(props, { slots }) {
       return () => {
           let tag = 'h' + props.level
           return h(
               tag,//tag type
               {
                   class: 'head_h',
                   onClick: ()=> {
                       alert('点击事件')
                   }
               }, ///props
               slots.default() //children 
           )
       }
   }
}
</script>

Vue 3 变体

<script>
import { h } from 'vue'
export default (props, { slots }) => { 
//或 export default function head_h(props, { slots }) {
   let tag = 'h' + props.level
   return h(
       tag,//tag type
       {
           class: 'head_h',
           onClick: () => {
               alert('点击事件')
           }
       }, ///props
       slots.default() //children 
   )
}
</script>

注意:setup语法糖是不能直接使用render函数的。

<script setup> 
//这里不能使用render函数
</script>

render函数除了返回单个vNode外,也可以返回字符串和数组,不过组件树中的vNodes必须是唯一的(同一个vNode实例,不能在组件中多次使用)。

极简且合法的Vue组件:

function Hello() {
 return 'hello world!'
}

小结

render函数+h函数虽可以处理动态性较高的场景,但是遇到复杂的组件时h函数层层嵌套,各种属性对象,写起来很复杂 ~ ~;有没有简单,方便的写法呢? 说到这里,就不得不说说JSX

Vue - Render & JSX

JSX

JSX 是在JavaScript 语法上的拓展,允许 HTML 代码和 JS 一起写。
React框架的结合是比较紧密的。

///JSX 表达式 :单行代码
const heading = <h1>Mozilla Developer Network</h1>; 
///JSX 表达式:多行代码
const header = (
  <header>
    <h1>Mozilla Developer Network</h1>
  </header>
);

React一样,Vue会将JSX表达式,经过parcelbabel编译后,转换为创建虚拟DOM节点的函数。不同的是:ReactReact.createElement函数,而VuecreateVNode

///React JSX 编译后
const header = React.createElement("header", null,
  React.createElement("h1", null, "Mozilla Developer Network")
);
///Vue JSX 编译后
const header = createVNode("header", null,
  createVNode("h1", null, "Mozilla Developer Network")
);

Vue JSX编译后的产物,在Vue Dev Tools也能看见:

因此在Vue中要想使用JSX,则必须安装可以将JSX表达式转换为createVNode的编译器插件。

环境

场景一:
Vue新项目,若要使用JSX,则在Vue项目创建时,配置支持JSX,如此Vue便会自动帮我们配置好JSX的编译插件。

场景二:
Vue非新建项目,若要使用JSX:

# -D @vitejs/plugin-vue-jsx 插件 开发环境下有效
npm install @vitejs/plugin-vue-jsx -D

其次找到vite.config.js文件,配置plugin-vue-jsx插件如下:

import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
  plugins: [vue(),vueJsx()]
})

使用

新建文件后缀更名.jsx,文件内容不再需要写<script>标签;同样,我们使用render函数+JSX的方式实现示例需求,有三种写法:

Vue 2

import HelloWorld from '../components/HelloWorld.vue'
import './head_jsx.css'
export const head_jsx = {
  props: {
    level: Number,
  },
  components: {
    HelloWorld
  },
  data() {
    return {};
  },
  render() {
    let tag = 'h' + this.level
    return <tag class='head_jsx' >{this.$slots.default()}</tag>
  }
}

Vue 3

export const head_jsx = {
    props: {
        level: Number,
    },
    components:{
      HelloWorld
    },

    setup(props, { slots }) {
        return () => {
            let tag = 'h' + props.level
            return <tag class='head_jsx' >{slots.default()}</tag>
        }
    }
}

Vue 3 变体

export const head_jsx = (props, { slots }) => {
    let tag = 'h' + props.level
    return  <tag class='head_jsx'>{slots.default()}</tag>
}

小结

基于上述写法的Vue JSX组件,虽然展示&交互都没有问题,但却有一个共同的问题:不支持VueHMR机制,每次都需要重新载入,于开发不便利。如何让Vue JSX组件支持HMR呢?

defineComponent

Vue JSX 组件支持HMR检测的必要条件有两个;

  1. 必须导出组件。
  2. 组件必须使用defineComponent根级语句调用来声明。

defineComponent是一个函数,除了HMR检测外,也可在定义 Vue 组件时提供类型推导的辅助。

并且代码写入时也会有提示。


使用

基于 Render & JSX使用部分所讲的Vue 2Vue 3的代码,外层包装defineComponent即可;Vue 3变体,defineComponent不可用。以下为Vue 3 JSX组件使用defineComponent 示例。

export const head_jsx = defineComponent({
  props: {
    level: Number,
  },
  components: {
    HelloWorld
  },
  setup(props, { slots }) {
    return () => {
      let tag = 'h' + props.level
      return <tag class='head_jsx' >{slots.default()}</tag>
    }
  }
})

v-model

v-model需要注意ref响应式变量,在JSX表达式中的取值

export const inputer = {
  setup(props, { slots }) {
    let val = ref(0)
    return () => (
      <div>
        <input type='number' v-model={val.value} value={val.value}/>
        <div> 结果:{val.value} </div>
      </div>
    )
  }
}

v-slot

JSXv-slot 应替换为 v-slots

插槽组件定义

export const sloter = {
  setup(props, { slots }) {
    return () => {
      return (
        <div>
          <div style={{ color: 'green' }}>
            我是默认插槽:
            {slots.default ? slots.default() : ''}
          </div>
          <div style={{ color: 'red' }}>
            我是插槽A:
            {slots.A ? slots.A() : ''}
          </div>
          <div style={{ color: 'blue' }}>
            我是插槽B:
            {slots.B ? slots.B() : ''}
          </div>
        </div>
      )
    }
  }
}

插槽组件使用

  1. template使用
    <sloter>
      <template #default>
        默认
      </template>
      <template #A>
        A 
      </template>
    </sloter>
  1. JSX 使用
export const slotsUse = {
  components: {
    sloter
  },
  setup(props,) {
    const slots = {
      default: ()=>(<span>JSX下默认插槽的值</span>),
      A : ()=>(<span>JSX下A插槽的值</span>),
    }
    return () => <sloter v-slots={slots}> </sloter>
  }
}

小结

上文简单列举了渲染函数和JSX配合基本使用,详细使用可见Vue JSX 插件文档

Vue - Template VS Render&JSX

template 优势:

  1. 官方推荐,模板化固定的语法,便于静态标记和分析,使得编译器能应用许多编译时的优化,提升性能。
  2. 贴近实际的 HTML,利于重用已有的HTML代码片段,便于理解和修改。

Render & JSX优势:

  1. 处理高度动态的逻辑时,渲染函数相比于模板更加灵活。
  2. 一个文件中可以返回多个组件。
  3. 利于React开发者快速上手Vue

参考

https://cn.vuejs.org/guide/extras/rendering-mechanism.html

https://cn.vuejs.org/guide/extras/render-function.html

https://github.com/vuejs/babel-plugin-jsx

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容