🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[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 **作用域插槽**应用场景:**父组件插槽插入的内容中** 使用到了 **组件内的数据** 。