react & vue & angular

Vue 渲染函数 & JSX

2022-12-27  本文已影响0人  QiShare

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

上一篇下一篇

猜你喜欢

热点阅读