首页 > 其他分享 >第四十四篇 vue - 进阶主题 - 渲染函数 & JSX

第四十四篇 vue - 进阶主题 - 渲染函数 & JSX

时间:2023-04-05 17:46:38浏览次数:43  
标签:vue 函数 渲染 第四十四 props 组件 div JSX

渲染函数 & JSX

在绝大多数情况下,Vue 推荐使用模板语法来创建应用。然而在某些使用场景下,我们真的需要用到 JavaScript 完全的编程能力。这时渲染函数就派上用场了

基本用法

1、创建 Vnodes

Vue 提供了一个 h() 函数用于创建 vnodes

import { h } from 'vue'

const vnode = h(
  'div', // type
  { id: 'foo', class: 'bar' }, // props
  [
    /* children */
  ]
)

h() 是 hyperscript 的简称——意思是“能生成 HTML (超文本标记语言) 的 JavaScript”。这个名字来源于许多虚拟 DOM 实现默认形成的约定。一个更准确的名称应该是 createVnode(),但当你需要多次使用渲染函数时,一个简短的名字会更省力
h() 函数的使用方式非常的灵活

// 除了类型必填以外,其他的参数都是可选的
h('div')
h('div', { id: 'foo' })

// attribute 和 property 都能在 prop 中书写
// Vue 会自动将它们分配到正确的位置
h('div', { class: 'bar', innerHTML: 'hello' })

// props modifiers such as .prop and .attr can be added
// with '.' and `^' prefixes respectively
h('div', { '.name': 'some-name', '^width': '100' })

// 类与样式可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })

// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })

// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')

// 没有 props 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])

// children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])
得到的 vnode 为如下形式:

const vnode = h('div', { id: 'foo' }, [])

vnode.type // 'div'
vnode.props // { id: 'foo' }
vnode.children // []
vnode.key // null

注意事项

完整的 VNode 接口包含其他内部属性,但是强烈建议避免使用这些没有在这里列举出的属性。这样能够避免因内部属性变更而导致的不兼容性问题
2、声明渲染函数

我们可以使用 render 选项来声明渲染函数

render() 函数可以访问同一个 this 组件实例

除了返回一个单独的 vnode 之外,你还可以返回字符串或是数组

import { h } from 'vue'

export default {
  data() {
    return {
      msg: 'hello'
    }
  },
  render() {
    return h('div', this.msg)
  }
}
export default {
  render() {
    return 'hello world!'
  }
}

import { h } from 'vue'

export default {
  render() {
    // 用数组来返回多个根节点
    return [
      h('div'),
      h('div'),
      h('div')
    ]
  }
}

如果一个渲染函数组件不需要任何实例状态,为了简洁起见,它们也可以直接被声明为一个函数

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

没错,这就是一个合法的 Vue 组件!参阅函数式组件来了解更多语法细节。
3、Vnodes 必须唯一

组件树中的 vnodes 必须是唯一的

下面是错误示范

function render() {
  const p = h('p', 'hi')
  return h('div', [
    // 啊哦,重复的 vnodes 是无效的
    p,
    p
  ])
}
如果你真的非常想在页面上渲染多个重复的元素或者组件,你可以使用一个工厂函数来做这件事。比如下面的这个渲染函数就可以完美渲染出 20 个相同的段落

function render() {
  return h(
    'div',
    Array.from({ length: 20 }).map(() => {
      return h('p', 'hi')
    })
  )
}

JSX / TSX

JSX 是 JavaScript 的一个类似 XML 的扩展,有了它,我们可以用以下的方式来书写代码

const vnode = <div>hello</div>

在 JSX 表达式中,使用大括号来嵌入动态值:

const vnode = <div id={dynamicId}>hello, {userName}</div>
create-vue 和 Vue CLI 都有预置的 JSX 语法支持。如果你想手动配置 JSX,请参阅 @vue/babel-plugin-jsx 文档获取更多细节。

虽然最早是由 React 引入,但实际上 JSX 语法并没有定义运行时语义,并且能被编译成各种不同的输出形式。如果你之前使用过 JSX 语法,那么请注意 Vue 的 JSX 编译方式与 React 中 JSX 的编译方式不同,因此你不能在 Vue 应用中使用 React 的 JSX 编译。与 React JSX 语法的一些明显区别包括:

  1、可以使用 HTML attributes 比如 class 和 for 作为 props - 不需要使用 className 或 htmlFor。
  
  2、传递子元素给组件 (比如 slots) 的方式不同。
  
Vue 的类型定义也提供了 TSX 语法的类型推导支持。当使用 TSX 语法时,确保在 tsconfig.json 中配置了 "jsx": "preserve",这样的 TypeScript 就能保证 Vue JSX 语法编译过程中的完整性

渲染函数案例

下面我们提供了几个常见的用等价的渲染函数 / JSX 语法,实现模板功能的案例

1、v-if
<div>
  <div v-if="ok">yes</div>
  <span v-else>no</span>
</div>

等价于使用如下渲染函数 / JSX 语法:

h('div', [this.ok ? h('div', 'yes') : h('span', 'no')])

<div>{this.ok ? <div>yes</div> : <span>no</span>}</div>
2、v-for
<ul>
  <li v-for="{ id, text } in items" :key="id">
    {{ text }}
  </li>
</ul>

等价于使用如下渲染函数 / JSX 语法:

h(
  'ul',
  this.items.map(({ id, text }) => {
    return h('li', { key: id }, text)
  })
)

<ul>
  {this.items.map(({ id, text }) => {
    return <li key={id}>{text}</li>
  })}
</ul>
3、v-on

以 on 开头,并跟着大写字母的 props 会被当作事件监听器。比如,onClick 与模板中的 @click 等价

h(
  'button',
  {
    onClick(event) {
      /* ... */
    }
  },
  'click me'
)
<button
  onClick={(event) => {
    /* ... */
  }}
>
  click me
</button>
4、事件修饰符

对于 .passive、.capture 和 .once 事件修饰符,可以使用驼峰写法将他们拼接在事件名后面

h('input', {
  onClickCapture() {
    /* 捕捉模式中的监听器 */
  },
  onKeyupOnce() {
    /* 只触发一次 */
  },
  onm ouseoverOnceCapture() {
    /* 单次 + 捕捉 */
  }
})
<input
  onClickCapture={() => {}}
  onKeyupOnce={() => {}}
  onm ouseoverOnceCapture={() => {}}
/>

对于事件和按键修饰符,可以使用 withModifiers 函数

import { withModifiers } from 'vue'

h('div', {
  onClick: withModifiers(() => {}, ['self'])
})

<div onClick={withModifiers(() => {}, ['self'])} />
5、组件

在给组件创建 vnode 时,传递给 h() 函数的第一个参数应当是组件的定义。这意味着使用渲染函数时不再需要注册组件了 —— 可以直接使用导入的组件

不管是什么类型的文件,只要从中导入的是有效的 Vue 组件,h 就能正常运作

import Foo from './Foo.vue'
import Bar from './Bar.jsx'

function render() {
  return h('div', [h(Foo), h(Bar)])
}

function render() {
  return (
    <div>
      <Foo />
      <Bar />
    </div>
  )
}

动态组件在渲染函数中也可直接使用

import Foo from './Foo.vue'
import Bar from './Bar.jsx'

function render() {
    return ok.value ? h(Foo) : h(Bar)
}

function render() {
  return ok.value ? <Foo /> : <Bar />
}

注意

如果一个组件是用名字注册的,不能直接导入 (例如,由一个库全局注册),可以使用 resolveComponent() 来解决这个问题
6、渲染插槽

在渲染函数中,可以通过 this.$slots 来访问插槽

export default {
  props: ['message'],
  render() {
    return [
      // <div><slot /></div>
      h('div', this.$slots.default()),

      // <div><slot name="footer" :text="message" /></div>
      h(
        'div',
        this.$slots.footer({
          text: this.message
        })
      )
    ]
  }
}
等价 JSX 语法:

// <div><slot /></div>
<div>{this.$slots.default()}</div>

// <div><slot name="footer" :text="message" /></div>
<div>{this.$slots.footer({ text: this.message })}</div>
7、传递插槽

向组件传递子元素的方式与向元素传递子元素的方式有些许不同。我们需要传递一个插槽函数或者是一个包含插槽函数的对象而非是数组,插槽函数的返回值同一个正常的渲染函数的返回值一样——并且在子组件中被访问时总是会被转化为一个 vnodes 数组

// 单个默认插槽
h(MyComponent, () => 'hello')

// 具名插槽
// 注意 `null` 是必需的
// 以避免 slot 对象被当成 prop 处理
h(MyComponent, null, {
    default: () => 'default slot',
    foo: () => h('div', 'foo'),
    bar: () => [h('span', 'one'), h('span', 'two')]
})
等价 JSX 语法

// 默认插槽
<MyComponent>{() => 'hello'}</MyComponent>

// 具名插槽
<MyComponent>{{
  default: () => 'default slot',
  foo: () => <div>foo</div>,
  bar: () => [<span>one</span>, <span>two</span>]
}}</MyComponent>
插槽以函数的形式传递使得它们可以被子组件懒调用。这能确保它被注册为子组件的依赖关系,而不是父组件。这使得更新更加准确及有效
8、内置组件

诸如 <KeepAlive>、<Transition>、<TransitionGroup>、<Teleport> 和 <Suspense> 等内置组件在渲染函数中必须导入才能使用

import { h, KeepAlive, Teleport, Transition, TransitionGroup } from 'vue'

export default {
  render () {
    return h(Transition, { mode: 'out-in' }, /* ... */)
  }
}
9、v-model

v-model 指令扩展为 modelValue 和 onUpdate:modelValue 在模板编译过程中,我们必须自己提供这些 props

export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  render() {
    return h(SomeComponent, {
      modelValue: this.modelValue,
      'onUpdate:modelValue': (value) => this.$emit('update:modelValue', value)
    })
  }
}
10、自定义指令

可以使用 withDirectives 将自定义指令应用于 vnode

import { h, withDirectives } from 'vue'

// 自定义指令
const pin = {
  mounted() { /* ... */ },
  updated() { /* ... */ }
}

// <div v-pin:top.animate="200"></div>
const vnode = withDirectives(h('div'), [
  [pin, 200, 'top', { animate: true }]
])

当一个指令是以名称注册并且不能被直接导入时,可以使用 resolveDirective 函数来解决这个问题

函数式组件

函数式组件可以像普通组件一样被注册和使用。如果你将一个函数作为第一个参数传入 h,它将会被当作一个函数式组件来对待

函数式组件是一种定义自身没有任何状态的组件的方式。它们很像纯函数:接收 props,返回 vnodes。函数式组件在渲染过程中不会创建组件实例 (也就是说,没有 this),也不会触发常规的组件生命周期钩子。

我们用一个普通的函数而不是一个选项对象来创建函数式组件。该函数实际上就是该组件的渲染函数。

而因为函数式组件里没有 this 引用,Vue 会把 props 当作第一个参数传入

function MyComponent(props, context) {
  // ...
}

第二个参数 context 包含三个属性:attrs、emit 和 slots。它们分别相当于组件实例的 $attrs、$emit 和 $slots 这几个属性。

大多数常规组件的配置选项在函数式组件中都不可用,除了 props 和 emits。我们可以给函数式组件添加对应的属性来声明它们:

MyComponent.props = ['value']

MyComponent.emits = ['click']


如果这个 props 选项没有被定义,那么被传入函数的 props 对象就会像 attrs 一样会包含所有 attribute。除非指定了 props 选项,否则每个 prop 的名字将不会基于驼峰命名法被一般化处理。

对于有明确 props 的函数式组件,attribute 透传的原理与普通组件基本相同。然而,对于没有明确指定 props 的函数式组件,只有 class、style 和 onXxx 事件监听器将默认从 attrs 中继承。在这两种情况下,可以将 inheritAttrs 设置为 false 来禁用属性继承

MyComponent.inheritAttrs = false

标签:vue,函数,渲染,第四十四,props,组件,div,JSX
From: https://www.cnblogs.com/caix-1987/p/17289994.html

相关文章

  • 第四十三篇 vue - 进阶主题 - 渲染机制
    渲染机制Vue是如何将一份模板转换为真实的DOM节点的,又是如何高效地更新这些节点的呢?我们接下来就将尝试通过深入研究Vue的内部渲染机制来解释这些问题虚拟DOM你可能已经听说过“虚拟DOM”的概念了,Vue的渲染系统正是基于这个概念构建的虚拟DOM(VirtualDOM,简称VDOM......
  • 第四十六篇 vue - 进阶主题 - 动画技巧
    动画技巧Vue提供了<Transition>和<TransitionGroup>组件来处理元素进入、离开和列表顺序变化的过渡效果。但除此之外,还有许多其他制作网页动画的方式在Vue应用中也适用。这里我们会探讨一些额外的技巧基于CSSclass的动画对于那些不是正在进入或离开DOM的元素,我们可......
  • 第四十五篇 vue - 进阶主题 - Vue 与 Web Components
    Vue与WebComponentsWebComponents是一组web原生API的统称,允许开发者创建可复用的自定义元素(customelements)Vue和WebComponents是互补的技术。Vue为使用和创建自定义元素提供了出色的支持。无论你是将自定义元素集成到现有的Vue应用中,还是使用Vue来构建和......
  • 第四十七篇 vue - vue2 和 vue3 的对比
    vue2和vue3不同点汇总1、生命周期2、多根节点3、CompositionApi4、异步组件5、响应式原理6、Teleport7、虚拟Dom8、事件缓存9、Diff算法优化10、打包优化11、TypeScript支持生命周期1、Vue3生命周期整体上变化不大,Vue3在大部分生命周期钩子名称......
  • 基于SpringBoot+Vue+ElementUI的在线考试系统(可做毕设)
    项目简介青云是一套麻雀虽小但五脏俱全的在线考试系统。采用了目前主流的技术栈SpringBoot+Vue+ElementUI,并进行了前后端分离。对于事务和锁都有应用,非常适合学习练手。项目演示项目演示地址:http://xuezhabiji.com:5000账号:admin密码:admin代码获取:github:https://github.com......
  • vue项目启动时 `webpack-dev-server –inline –progress –config build/webpack.dev
    vue项目在npmrundev时报错npmERR!book_view@1.0.0dev:webpack-dev-server--inline--progress--configbuild/webpack.dev.conf.js解决这类问题主要分两种情况这个项目已经构建好的项目,你只是从git、snv或者其他地方引入,别人能运行你不能运行这是一个新构建的vue项目第一......
  • 从一个电影网站项目学习[前台显示端]—Vue.js
    本篇文章通过一个完整的电影介绍和电影资源发布网站的项目,过一遍Vue.js。通过前面章节的介绍http://www.shanhubei.com/tag/vue或在本平台下的相关文章了解一下。(ps本人是通过工具编辑器编写,同步在多个平台上)项目源码:github:https://github.com/shanhubei/vue_movie_example......
  • springboot +vue2.x实现音乐网站
    1pom文件<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache......
  • Vue进阶(四十七):面试必备:2023 Vue经典面试题总结(含答案)
    一、什么是MVVM?MVVM是Model-View-ViewModel的缩写。MVVM是一种设计思想。Model层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View代表UI组件,它负责将数据模型转化成UI展现出来,ViewModel是一个同步View和Model的对象。在MVVM架构下,View和Model之间并没......
  • 第三十八篇 vue - 最佳实践 - 性能优化
    概述Vue在大多数常见场景下性能都是很优秀的,通常不需要手动优化。然而,总会有一些具有挑战性的场景需要进行针对性的微调。在本节中,我们将讨论用Vue开发的应用在性能方面该注意些什么首先,让我们区分一下web应用性能的两个主要方面1、页面加载性能首次访问时,应......