[TOC]
>[success] # 渲染函数(render函数)和JSX快速掌握
在接下来讲学习到如何在 **Vue** 中使用 **渲染函数** 来 **创建视图模板** ,并且会讲解 **JSX** 的 **语法** ,同时会补充2个内容,第一是 **函数式组件** ,第二是 **作用域插槽** 。
>[success] ## render函数
**render函数** :可以用 **函数的方式** 渲染 **dom元素** 到页面中。
下面会讲解2种使用场景:
1. 在 **main.js** 中如何使用
2. 在 **.vue** 文件中使用
>[success] ### render函数在main.js中使用
**src/main.js**
~~~
import Vue from 'vue'
import App from './App.vue' // app组件
import router from './router'
import store from './store'
import './plugins/element.js'
import Bus from './lib/bus' // 引入Bus
Vue.config.productionTip = false
Vue.prototype.$bus = Bus // 挂载Bus到Vue原型链(全局挂载Bus)
new Vue({
router,
store,
render: h => h(App) // 渲染app组件
}).$mount('#app')
~~~
我们首先在 **main.js** 中学习 **render函数** , **render** 的 **属性值** 是一个 **回调函数** ,它的参数可以用括号包裹起来,例如这样: **render: (h) => h(App)** ,如果 **只有一个参数就可以不用括号包裹** , **h这个参数是一个方法** ,这个方法能 **创建一个虚拟节点** ,**这个函数 return 返回一个结果** 。
| 方法名 | 参数 | 是否必填 |
| --- | --- | --- |
| **render函数:render: h => h(App)** | **h函数的第1个参数**:**要渲染的组件,或者一个标签字符串,或者也可以是一个函数** | **是** |
| | **h函数的第2个参数**: 该参数是一个 **配置对象**,可以 **通过该对象给元素设置属性** ,例如 **div** 标签的 **id、class 等等** | **否** |
| | **h函数的第3个参数**:该 **参数可以是字符串或者数组**,主要作用是 **给元素添加内容** | **否** |
1. **h函数的参数1**
**说明** : **第1个参数** 是用来传入 **元素标签** 或者 **组件** 。
1.1. **传入组件** :**main.js** 中 **h函数** 默认传入的是 **app.vue页面组件**,所以就会 **渲染该组件** ,下面引入之前封装的 **CountTo 组件**
~~~
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
import Bus from './lib/bus'
import CountTo from '_c/count-to' // 引入CountTo 组件
Vue.config.productionTip = false
Vue.prototype.$bus = Bus
new Vue({
router,
store,
render: h => h(CountTo, {
// 'class': 'count-to', // 给组件最外层盒子添加class类名
// 或者这样写
// 'class': ['count-to', true ? 'count-to2' : ''],
// 或者这样写
'class': {
'count-to': true,
'count-to2': 1 === 1,
},
attrs: {}, // 定义属性id等等
style: {}, // 定义样式
props: { // 添加属性 这里可以理解为就是<count-to :endValue="100"/>
endValue: 100
},
// domProps: { // dom的一些属性
// innerHTML: '11' // 可以设置标签的一些内容
// },
on: { // 添加事件
'on-animation-end': (val) => { // 事件名
console.log(val)
}
},
nativeOn: { // 组件内没有定义click事件时,给组件最外层元素绑定一个click事件
'click': () => {
console.log('click')
}
},
directives: [], // 可以定义自定义指令
scopedSlots: {},
slot: '', // 插槽
key: '', // 设置一个值让每个组件的key不相等
ref: '' // ref
})
}).$mount('#app')
~~~
1.2. **传入字符串** :也可以像下面写,这样页面中就 **渲染** 出一个 **div标签** 。
~~~
new Vue({
router,
store,
render: h => h('div')
}).$mount('#app')
~~~
1.3. **传入函数** :
~~~
let func = ()=> 'h3'
new Vue({
router,
store,
render: h => h(func())
}).$mount('#app')
~~~
2. **h函数的参数2**
**说明** : **h函数** 的 **第2个参数是个对象** ,用来 **定义元素的一些属性** 。
2.1. **给元素设置属性** :
~~~
new Vue({
router,
store,
render: h => h('div', {
attrs: { // 添加属性
id: 'box'
},
style: { // 添加样式
color: 'red'
}
})
}).$mount('#app')
~~~
3. **h函数的参数3**
**说明** : **h函数的第3个参数可以是字符串或者数组** ,用来 **定义元素的内容** 。
3.1. **传入字符串**
~~~
new Vue({
router,
store,
render: h => h('div', {
attrs: { // 添加属性
id: 'box'
},
style: { // 添加样式
color: 'red'
}
}, '我是div的内容')
}).$mount('#app')
~~~
**如果不设置样式可以直接忽略h函数的第2个参数** , 直接写内容
~~~
new Vue({
router,
store,
render: h => h('div', '123')
}).$mount('#app')
~~~
3.2. **传入数组**
如果想让内容是 **多个标签** ,就需要 **传入数组**
~~~
new Vue({
router,
store,
render: h => h('div', [
h('span', '111'),
h('span', '222')
])
}).$mount('#app')
~~~
3.3. **循环传入标签**
如果想实现一个类似 **v-for** 的效果,首先先看 **正常循环列表写法**
~~~
<template>
<ul @click="handleClick">
<li @click.stop="handleClick" v-for="(item, index) in list" :key="`list_item_${index}`">{{ item.name }} </li>
</ul>
</template>
<script>
export default{
data(){
return{
list: [
{name: '小明'},
{name: '小黑'}
]
}
},
methods: {
handleClick(event){
console.log(event)
}
}
}
</script>
~~~
接下来使用 **render函数** 实现上面的 **循环列表跟点击事件**
~~~
// 点击事件
const handleClick = event => {
console.log(event)
event.stopPropagation()
}
// 数据
let list = [
{name: '小明'},
{name: '小黑'}
]
// 循环生成li
const getLiEleArr = (h) => {
return list.map((item, index) => h('li', {
on: {
'click': handleClick
},
key: `list_item_${index}`
}, item.name))
}
// 最终渲染
new Vue({
router,
store,
render: h => h('div',[
h('ul', {
on: {
'click': handleClick
}
}, getLiEleArr(h))
])
}).$mount('#app')
~~~
>[success] ### render函数在.vue中使用
有时 **即使你封装好了组件,但是想根据自己的方式去定制组件内的元素以及内容**,这时候就需要 **给组件传入一个render函数**
1. **父组件**
首先在 **路由列表** 的 **路由对象** 中添加新创建的 **render-page** 页面配置路由
**src/router/router.js**
~~~
export default [
{
path: '/render-page',
name: '/render_page',
component: () => import('@/views/render-page')
}
]
~~~
然后在 **src/views/render-page.vue** 页面 **引入list组件**
**src/views/render-page.vue**
~~~
<template>
<div>
<list :list="list" :render="renderFunc"></list>
</div>
</template>
<script>
import List from '_c/list'
export default{
data(){
return{
list: [ // 数据
{name: '小明'},
{name: '小黑'}
]
}
},
components: {
List
},
methods: {
renderFunc(h, name){ // 自定义render函数渲染自己想要的dom节点内容
return h('i', {
style: {
color: 'pink'
}
}, name)
}
}
}
</script>
~~~
1. **子组件**
**src/components/list/list.vue**
~~~
<template>
<ul>
<li v-for="(item, index) in list" :key="`item_${index}`">
<!-- 如果未传入render函数就span标签内容 -->
<span v-if="!render">{{ item.name }}</span>
<!-- 如果传入了render函数,就使用render函数自定义dom的节点来渲染 -->
<render-dom v-else :render-func="render" :name="item.name"></render-dom>
</li>
</ul>
</template>
<script>
// 引入函数式组件
import RenderDom from '_c/render-dom'
export default {
name: 'List',
components: {
RenderDom // 注册函数式组件
},
props: {
list: { // 列表内容
type: Array,
default: () => []
},
render: { // render 函数
type: Function,
default: () => {}
}
}
}
</script>
~~~
定义一个 **index.js 方便父组件引用**
**src/components/list/index.js**
~~~
import List from './list.vue'
export default List
~~~
在上面的组件中使用到了 **函数式组件** ,这里写的 **函数式组件** 就是使用 **reder函数 自定义一些想要的标签** 然后 **return** 返回一个 **虚拟节点** , **最终渲染在使用函数式组件的地方** ,**函数式组件详解看下方的文档介绍**。
**src/components/render-dom.js**
~~~
export default {
functional: true,
props: {
name: String, // 组件渲染的文字内容
renderFunc: Function // 传入的render函数
},
/**
* render渲染函数
* @param {Function} h - render函数的回调方法,用于生成dom节点
* @param {Object} ctx - 指代当前的这个对象
*/
render: (h, ctx) => {
return ctx.props.renderFunc(h, ctx.props.name)
}
}
~~~
>[success] ## 函数式组件
**函数式组件** : **只给它传入数据,它不做任何响应式的操作, 不监听传递给它的状态** ,这个组件 **没有生命周期和钩子函数**,它 **只是作为一个接收参数的函数** , 当 **functional 设置为 true** 时候,证明 **它是一个没有状态的组件,也没有实例,就是一个对象** , 当 **把这个对象引入到其他页面,当做一个组件去使用的时候,vue会把它做一个处理,会把 【render函数】里面逻辑返回的【虚拟节点】做一个渲染** 。
**src/components/render-dom.js**
~~~
export default {
functional: true,
props: {
name: String, // 组件渲染的文字内容
renderFunc: Function // 传入的render函数
},
/**
* render渲染函数
* @param {Function} h - render函数的回调方法,用于生成dom节点
* @param {Object} ctx - 指代当前的这个对象
*/
render: (h, ctx) => {
return ctx.props.renderFunc(h, ctx.props.name)
}
}
~~~
>[success] ## JSX
**JSX** :**JSX** 最先是 **react** 提出的, **通过一种形式,在 js 里面写 html 标签,还有一些特定的语法** ,最后会把这个 **字符串** 转译成 **js** ,去用 **render函数** 来做渲染。
>[success] ### JSX渲染标签字符串
1. **父组件**
**src/views/render-page.vue**
~~~
<template>
<div>
<list :list="list" :render="renderFunc"></list>
</div>
</template>
<script>
import List from '_c/list'
export default{
data(){
return{
list: [ // 数据
{name: '小明'},
{name: '小黑'}
]
}
},
components: {
List
},
methods: {
renderFunc(h, name){ // 自定义render函数渲染自己想要的dom节点内容
/**
* render函数方式
*/
// return h('i', {
// style: {
// color: 'pink'
// }
// }, name)
/**
* JSX方式
* JSX与template模板对比
* style:
* template的标签中写法:style="{ color: 'pink' }"
* JSX写法:style={{color: 'pink'}}
* JSX中style不需要添加双引号,属性都需要用{}包裹
*
* 事件:
* template的标签中写法v-click="handleClick" 或者 @click="handleClick"
* JSX写法on-click={ this.handleClick }
*/
return (
<i on-click={ this.handleClick } style={{color: 'pink'}}>{ name }</i>
)
},
handleClick(event){
console.log(event)
}
}
}
</script>
~~~
使用 **JSX** 时 **render函数** 的 **形参必须是 h** ,不可以改成其他的(例如 **createElement**)。以上代码中引入的 **List组件** 在上面的 **render函数** 文档中有写过,在此处就不再过多陈述。
>[success] ### JSX渲染组件
1. **父组件**
**src/views/render-page.vue**
~~~
<template>
<div>
<list :list="list" :render="renderFunc"></list>
</div>
</template>
<script>
import List from '_c/list'
import CountTo from '_c/count-to' // 引入组件
export default{
data(){
return{
list: [ // 数据
{ number: 100 },
{ number: 45 }
]
}
},
components: {
List
},
methods: {
renderFunc(h, number){ // 自定义render函数渲染自己想要的dom节点内容
return (
/**
* 这里可以引入组件进行渲染,而且不用在components对象中注册
* 事件分为2种:原生事件、自定义事件
* html标签:支持原生事件
* 组件:支持原生事件(给下面的CountTo组件绑定一个原生click事件,就相当于给组件内的最外层元素绑定了一个click事件,
* 写法:nativeOn-事件名称={方法})、
* 自定义事件(写法:on-自定义事件名称={方法})
* template模板中的事件修饰符在JSX跟render函数中用到需要看文档
* */
<CountTo nativeOn-click={this.handleClick} on-on-animation-end={this.handleEnd} endValue={number} style={{color: 'pink'}}></CountTo>
)
},
handleClick(event){
console.log(event)
},
handleEnd(){
console.log('end!')
}
}
}
</script>
~~~
父组件中引入的 **[CountTo组件](https://www.kancloud.cn/wangjiachong/vue_notes/1971966)**
2. **子组件**
**src/components/list.vue**
~~~
<template>
<ul>
<li @mousemove="handleMove" v-for="(item, index) in list" :key="`item_${index}`">
<!-- 如果未传入render函数就span标签内容 -->
<span v-if="!render">{{ item.number }}</span>
<!-- 如果传入了render函数,就使用render函数自定义dom的节点来渲染 -->
<render-dom v-else :render-func="render" :number="item.number"></render-dom>
</li>
</ul>
</template>
<script>
// 引入函数式组件
import RenderDom from '_c/render-dom'
export default {
name: 'List',
components: {
RenderDom // 注册函数式组件
},
props: {
list: { // 列表内容
type: Array,
default: () => []
},
render: { // render 函数
type: Function,
default: () => {}
}
},
methods: {
handleMove(event){
// 阻止默认行为(文字不可以选中复制)
event.preventDefault()
}
}
}
</script>
~~~
**list组件** 对应的 **index.js**,用于 **父组件方便引用**
**src/components/list/index.js**
~~~
import List from './list.vue'
export default List
~~~
**子组件** 中用到的 **函数式组件 render-dom.js**
**src/components/render-dom.js**
~~~
export default {
functional: true,
props: {
number: Number, // 组件渲染的文字内容
renderFunc: Function // 传入的render函数
},
/**
* render渲染函数
* @param {Function} h - render函数的回调方法,用于生成dom节点
* @param {Object} ctx - 指代当前的这个对象
*/
render: (h, ctx) => {
return ctx.props.renderFunc(h, ctx.props.number)
}
}
~~~
>[success] ## 作用域插槽
**定制组件内的元素以及内容** ,用 **render函数** 以及 **JSX** 都比较繁琐,接下来用 **作用域插槽** 来实现这个需求。
1. **父组件**
**src/views/render-page.vue**
~~~
<template>
<div>
<list :list="list">
<h3 slot="aa">我是小明</h3>
<coun-to slot-scope="count" :end-value="count.number"></coun-to>
</list>
</div>
</template>
<script>
import List from '_c/list'
import CounTo from '_c/count-to' // 引入组件
export default{
data(){
return{
list: [ // 数据
{ number: 100 },
{ number: 45 }
]
}
},
components: {
List,
CounTo
}
}
</script>
~~~
2. **子组件**
**src/components/list.vue**
~~~
<template>
<ul>
<li v-for="(item, index) in list" :key="`item_${index}`">
<!-- 默认插槽 -->
<!-- <slot></slot> -->
<!-- 具名插槽 -->
<slot name="aa"></slot>
<!-- 作用域插槽 -->
<slot :number="item.number"></slot>
</li>
</ul>
</template>
<script>
export default {
name: 'List',
props: {
list: { // 列表内容
type: Array,
default: () => []
}
}
}
</script>
~~~
3. **插槽总结**
3.1 **匿名插槽** 应用场景:**组件内** 只需要一个 **插槽** 的情况,可以使用 **匿名插槽** 。
3.2 **具名插槽** 应用场景:**组件内** 需要多个 **插槽** 的情况,可以使用 **具名插槽** 。
3.3 **作用域插槽**应用场景:**父组件插槽插入的内容中** 使用到了 **组件内的数据** 。
- vue 26课
- Vue-cli3.0项目搭建
- Vue-ui 创建cli3.0项目
- Vue-ui 界面详解
- 项目目录详解
- public文件夹
- favicon.ico
- index.html
- src文件夹
- api文件夹
- assets文件夹
- components文件夹
- config文件夹
- directive文件夹
- lib文件夹
- mock文件夹
- mock简明文档
- router文件夹
- store文件夹
- views文件夹
- App.vue
- main.js
- .browserslistrc
- .editorconfig
- .eslintrc.js
- .gitignore
- babel.config.js
- package-lock.json
- package.json
- postcss.config.js
- README.en.md
- README.md
- vue.config.js
- Vue Router
- 路由详解(一)----基础篇
- 路由详解(二)----进阶篇
- Vuex
- Bus
- Vuex-基础-state&getter
- Vuex-基础-mutation&action/module
- Vuex-进阶
- Ajax请求
- 解决跨域问题
- 封装axios
- Mock.js模拟Ajax响应
- 组件封装
- 从数字渐变组件谈第三方JS库使用
- 从SplitPane组件谈Vue中如何【操作】DOM
- 渲染函数和JSX快速掌握
- 递归组件的使用
- 登陆/登出以及JWT认证
- 响应式布局
- 可收缩多级菜单的实现
- vue杂项
- vue递归组件
- vue-cli3.0多环境打包配置
- Vue+Canvas实现图片剪切
- vue3系统入门与项目实战
- Vue语法初探
- 初学编写 HelloWorld 和 Counter
- 编写字符串反转和内容隐藏功能
- 编写TodoList功能了解循环与双向绑定
- 组件概念初探,对 TodoList 进行组件代码拆分
- Vue基础语法
- Vue 中应用和组件的基础概念
- 理解 Vue 中的生命周期函数
- 常用模版语法讲解
- 数据,方法,计算属性和侦听器
- 样式绑定语法
- 条件渲染
- 列表循环渲染
- 事件绑定
- 表单中双向绑定指令的使用
- 探索组件的理念
- 组件的定义及复用性,局部组件和全局组件
- 组件间传值及传值校验
- 单向数据流的理解
- Non-Props 属性是什么
- 父子组件间如何通过事件进行通信
- 组件间双向绑定高级内容
- 使用匿名插槽和具名插槽解决组件内容传递问题
- 作用域插槽
- 动态组件和异步组件
- 基础语法知识点查缺补漏
- Vue 中的动画
- 使用 Vue 实现基础的 CSS 过渡与动画效果
- 使用 transition 标签实现单元素组件的过渡和动画效果
- 组件和元素切换动画的实现
- 列表动画
- 状态动画
- Vue 中的高级语法
- Mixin 混入的基础语法
- 开发实现 Vue 中的自定义指令
- Teleport 传送门功能
- 更加底层的 render 函数
- 插件的定义和使用
- 数据校验插件开发实例
- Composition API
- Setup 函数的使用
- ref,reactive 响应式引用的用法和原理
- toRef 以及 context 参数
- 使用 Composition API 开发TodoList
- computed方法生成计算属性
- watch 和 watchEffect 的使用和差异性
- 生命周期函数的新写法
- Provide,Inject,模版 Ref 的用法
- Vue 项目开发配套工具讲解
- VueCLI 的使用和单文件组件
- 使用单文件组件编写 TodoList
- Vue-Router 路由的理解和使用
- VueX 的语法详解
- CompositionAPI 中如何使用 VueX
- 使用 axios 发送ajax 请求
- Vue3.0(正式版) + TS
- 你好 Typescript: 进入类型的世界
- 什么是 Typescript
- 为什么要学习 Typescript
- 安装 Typescript
- 原始数据类型和 Any 类型
- 数组和元组
- Interface- 接口初探
- 函数
- 类型推论 联合类型和 类型断言
- class - 类 初次见面
- 类和接口 - 完美搭档
- 枚举(Enum)
- 泛型(Generics) 第一部分
- 泛型(Generics) 第二部分 - 约束泛型
- 泛型第三部分 - 泛型在类和接口中的使用
- 类型别名,字面量 和 交叉类型
- 声明文件
- 内置类型
- 总结