12.Vue技术栈开发实战-渲染函数和JSX快速掌握

官方文档

jsx:

https://cn.vuejs.org/v2/guide/render-function.html#JSX

函数式组件

https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6

 

 

本节内容

如果用渲染函数来创建视图模板。
JSX语法
补充讲解函数式组件作用域插槽

render函数

路由列表内线添加一个路由配置render-page

创建这个页面

页面现在是空的

我们在main.js里面学习render函数的使用

render这里是一个回调函数,


h是一个方法,我们使用这个方法,可以创建一个虚拟节点

上面是下面这种方式的简写形式.如果返回的结果一行代码,就可以省略花括号了。这是ES6的简写的形式。

h这个方法里面有三个参数,第一个是必选参数,后面是可选参数。
参数1就是你当前要渲染的组件,或者标签的字符串。
整个系统App作为根组件在这里渲染。

改成一个简单的div标签。

整个页面只有一个div

第二个参数是一个配置对象,它里面可以传入一些参数。作为一个div标签它应该可以传入像id这种的元素属性。

属性叫做attrs里面设置id。

还可以设置style样式

在div里面包含东西,它可以是一个字符串,也可以是一个数组。
下面设置div里面包含一个文字。

里面如果需要多个元素的话,那么参数3就必须是一个数组

 

 render: h => {
    return h('div', {
      attrs: {
        id: 'box'
      },
      style: {
        color: 'red'
      }
    }, 'wjw')
  }

 


第一个参数,它可以是一个组件,可以是一个对象。我们使用我们之前封装的组件叫做count-to

在上面引入这个组件

参数1就用countTo

报错是因为我们有些属性没有传

endValue是必传的属性

使用props传入endVal这必须要传的属性值。

 

 render: h => {
    return h(CountTo, {
      attrs: {
        endVal: 100
      }
    })
  }

 



如果想绑定一个事件,我们之前组件定义内有事件on-animation-end

我们在on里面定义事件,

 

 render: h => {
    return h(CountTo, {
      attrs: {
        endVal: 100
      },
      on: {
        'on-animation-end': (val) => {
          console.log('animation end!')
        }
      }
    })
  }

 


这样刷新页面,数字在到100以后,就console输出

想给这个组件最外层绑定一个click事件。click事件不是组件内定义的事件。所以这里要用nativeOn

注意事项nativeOn添加click事件不用加单引号:

用的vue Cli 4.5.6不支持上面这种写法了

 

使用自动修复,修复这个问题。

click事件,不用加单引号

继续


点击触发这个click

class,给它添加一个类名,那么这个类名可以添加到组件的最外层。class作为一个保留字,所以这里一定要添加上引号。

直接字符串的形式,这是第一种方式

加class属性,vue 4.5.6必须要这么写了

 

继续代码

第二种写法
对象的写法,后面是一个逻辑表达式



后面还可以写1===1。后面为true那么前面就会返回这个class属性了。



如果写1!==1,那么这个类名就没有


render: h => {
    return h(CountTo, {
      class: { 'count-to': 1===1 },
      attrs: {
        endVal: 100
      },
      on: {
        'on-animation-end': (val) => {
          console.log('animation end!')
        }
      },
      nativeOn: {
        click: () => {
          console.log('click!')
        }
      }
    })

 

 

指令

指令,它是一个数组,在里面可以自定义指令

slot

key

key是给这个组件设置一个固定的key,经常在v-for循环里面见,要求给循环里面每项设置一个key值,而且key值要互相不相等

ref

通过ref可以获取到html标签的dom。也可以获取一个组件的实例

domProps

下图的props原来上面的代码用的是attrs,这里又换成了props,我测试了下 attrs和props都可以显示出来组件的效果。


它是dom上的一些属性,比如我们的innerHTML,它可以设置标签里面的一些内容。

这里设置上就报错,先屏蔽掉

先注释掉。

render函数能够设置的值,基本上就是这些

 

render: h => {
    return h(CountTo, {
      class: { 'count-to': true },
      style: {},
      props: {
        endVal: 100
      },
      domProps: {
        // innerHTML: 1000
      },
      on: {
        'on-animation-end': (val) => {
          console.log('animation end!')
        }
      },
      nativeOn: {
        click: () => {
          console.log('click!')
        }
      },
      directives: [],
      slot: '',
      key: '',
      ref: ''
    })
  }

 

 

添加子元素

之前的代码注释掉,我们这里重新写一下render函数。

就一个div标签,里面是123

如果想让他里面有多个标签,每一个元素都是一个h方法。用h方法创建两个span标签。

 

 render: h => h('div', [
    h('span', '111'),
    h('span', '222')
  ])

 



这里要么是字符串,要么就是个数组

如果想在这里用for循环生成一个数组,数组里面每一个元素都是这种标签的话。

reder-page.vue页面内。



上面循环li标签里面,用到了item.name,这里截图没法截全,胡须自己写代码的时候把这个地方的截图补全



这里先注释掉

改成默认的




加一个click事件

把这个事件对象打印出来看一下。

为了演示给每一个li也绑定一个事件

点击一下触发了两个click事件。

事件的冒泡,点击里面的,上面ul的click事件也会触发

为了防止事件冒泡,可以用.stop修饰

这样li的事件就不会往上冒泡了。

render函数实现循环

如果我们想通过render函数实现这么个逻辑的话

首先最外层是一个div,div里面有一个url

给这个url绑定一个click事件,因为它是一个原生的html标签,所以我们绑定事件直接用on

在ul里面有子元素,子元素是一个数组

把点击事件抽出来 放到最上面。

事件,单独抽出来了




要做v-for循环生成li。在render里面我们是没法使用render这个指令的。
map会遍历数组,item就是数组类里面的每一项,最后它会把return 出去的东西冲洗组装成一个数组。然后这里我们返回了h方法的调用。

 

const list = [{ name: 'lison' }, { name: 'lili' }]
const getLiEleArr = (h) => {
  return list.map(item => h('li', {
    on: {
      click: handleCLick
    }
  }, item.name))
}

 


调用这个方法,把h传进去

下面的on里面的click,加不加单引号都可以。只不过加了单引号会有eslint的错误红线的提示。

render: h => h('div', [
    h('ul', {
      on: {
        'click': handleCLick
      }
    }, getLiEleArr(h))
  ])

 


点一下li,输出了两次click事件

原生阻止冒泡stopPropagation

 

这里我们是没法使用.stop这个修饰符的。那么在这里我们是如何的组织冒泡呢?


这样它就会阻止冒泡了。

注意事项:div的click和 下面的ui里面的li的click,要么都加了单引号,要么都不加单引号,否则阻止冒泡stopPropagation会不起作用。

key值问题

每一个循环生成的li应该给他一个key值

main.js的代码

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Bus from './lib/bus'
import CountTo from './components/count-to/index'

Vue.config.productionTip = false
Vue.prototype.$bus = Bus

const handleClick = event => {
  console.log(event)
  event.stopPropagation()
}
let list = [{ name: 'lison' }, { name: 'lili' }]
const getLiEleArr = (h) => {
  return list.map((item, index) => h('li', {
    on: {
      'click': handleClick
    },
    key: `list_item_${index}`
  }, item.name))
}
new Vue({
  router,
  store, // 挂载到vue的根组件实例上
  // render: h => {
  //   // return h(CountTo, {
  //   //   attrs: {
  //   //     id: 'box'
  //   //   },
  //   //   style: {
  //   //     color: 'red'
  //   //   }
  //   // }, 'lison')
  //   return h(CountTo, {
  //     // 'class': 'count-up wrapper',
  //     'class': [],
  //     props: {
  //       endVal: 100
  //     },
  //     // domProps: {
  //     //   innerHTML: ''
  //     // },
  //     on: {
  //       'on-animation-end': (val) => {
  //         console.log('animation end!')
  //       }
  //     },
  //     nativeOn: {
  //       'click': () => {
  //         console.log('click!')
  //       }
  //     },
  //     directives: [],
  //     slot: '',
  //     key: '',
  //     ref: ''
  //   })
  // }
  // render: h => h('div', [
  //   h('span', '111')
  // ])
  // render: h => h(App)
  render: h => h('div', [
    h('ul', {
      on: {
        'click': handleClick
      }
    }, getLiEleArr(h))
  ])
}).$mount('#app')

 

页面里面使用render

恢复原来的h(App)‘

函数式组件

封装一个简单的组件,叫做list





list属性是一个数组,默认是一个空的数组,所以返回[]

使用v-for循环这个list数组,一定要加一个:key值

views/render-page.vue页面
页面引入这个组件,引入这个组件,注册这个组件。

使用这个list组件,传入list属性

http://localhost:8080/#/render_page

http://localhost:8080/#/render_page

 


有时候用户传进来的这个内容,不想让它包括在span标签内,我想给他更多的特性。
按照自己的意愿去包装这个名字。这个时候可以传入一个render函数,用户通过render函数自己去定义怎么去渲染这个名字。

这个render函数是一个方法。render函数第一个参数是h

我们肯定是要在组件内部,循环的时候取到当前的那一条内容,

我们这里是一个v-for循环生成的东西,那么我们是要知道当前循环生成的这一条。


函数式组件


这里我们就要用到一个函数式的组件。新加一个render-dom.js

导出一个默认的对象,

我们把它叫做函数式组件

可以给它设置一个属性值,还可以给它设置一个render。
函数式组件呢,我们只给它传入一些数组,但是它不做任何的响应式的操作,不监听传递给它的状态,这个组件它没有生命周期那些钩子函数,它只是作为一个接收参数的函数,

当你设置functional为true的时候呢,就意味着它没有状态,它是一个没有状态的这么一个组件,

没有实例,它就是这么一个普通的对象

当你把这个对象引进到别的页面里把它当一个组件去用的时候呢?vue会把它做一个处理。
vue会把render函数里面的逻辑,里面返回的虚拟节点,做一个渲染, 

再加一个属性叫做renderFunc它是一个function类型的





那么就用到函数式组件了。参数1:h是渲染函数。参数2:ctx是实例因为我们的函数式组价是没有实例的,所以它用ctx指代当前的这个对象,那么我们就可以获取到属性里面的renderFunc这个渲染函数。

使用ctx代表当前实例。这个就是那个渲染函数啦。

我们调用这个函数。

这个值如果供用户使用呢,把它传进去

终于使用这个函数式组件

这个组件同样需要引进来,然后注册一下。

使用render-dom这个函数式组件,然后传入属性值renderFunc,在组件内我们使用的驼峰的形式,在调用的时候传值的时候,我们建议使用横线的这种形式。

传入的render函数,就是 用户在调用的时候传入的render函数

传入,第二个属性name值

当用户定义了render的时候,我们就要显示用户自己定义的render,如果没有设置render的话,我们就是用span标签。

那么我们就 用v-if来判断

第一个参数是渲染函数,那么这里的第二个参数就是我们render-dom里面

参数2就是render里面返回的name值。





render-dom 这里应该把渲染函数也传进去。要不然就获取不到这个渲染函数。


i标签渲染成功了,但是name值没有渲染。

这个地方应该是ctx.props.name




总结:使用这种方式,可以让用户自己定义用什么方式来展现,用render这种方式是比较繁琐的。还可以使用JSX

函数式组组件代码

reder.dom.js

export default {
  functional: true,
  props: {
    name: String,
    renderFunc: Function
  },
  render: (h, ctx) => {
    return ctx.props.renderFunc(h, ctx.props.name)
  }
}

 

list.vue

<template>
  <ul>
    <li v-for="(item, index) in list" :key="`item_${index}`">
      <span v-if="!render">{{ item.name }}</span>
      <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: {
      type: Function,
      default: () => {}
    }
  }
}
</script>

<style>

</style>

 

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: 'lison' },
        { name: 'lili' }
      ]
    }
  },
  components: {
    List
  },
  methods: {
    renderFunc (h, name) {
      return h('i', {
        style: {
          color: 'pink'
        }
      }, name)
    }
  }
}
</script>

<style>

</style>

 

 

JSX

jsx是react最先提出的。在js里面写html标签,它有些特定的语法,最后把字符串转义成js,用render函数去渲染,

之前这里把h换成createElement,那么调用也是createElement。也就是说参数名可以随便的定义。不是原来必须用h当参数1的名称

下面我们要写jsx的时候,这里的参数必须是h

在vue中,我们如果给他设置一个样式是这么去写的,

在jsx里面呢,变量都是要用花括号包起来的。首先用花括号包起来。

花括号包起来的里面是对象,对象又有个花括号,



在i标签中,要显示我们的name值,也是个变量,所以也要用花括号,括起来。

改成粉色

使用jsx非常的简单,就像写html标签一样。如果想给他绑定一个事件呢。首先要用到on前缀

如果想绑定click事件,那么就是后面click


 

 methods: {
    renderFunc (h, name) {
      return (
        <i on-click={this.handleClick} style={{color: 'pink'}}>{name}</i>
      )
    },
    handleClick (event) {
      console.log(event)
    }
  }

 


如何在jsx里面渲染一个组件

就是在下面这个位置。

我们要渲染CountTo组件

在render里面要渲染的组件不需要在Components,jsx里面渲染的同样是不需要注册



接下来把list的值改成数字类型的

list.vue内,都改成number的属性名



里面的值我们是通过设置endValue的属性传进去的。





这里的类型改成numer类型就可以了

这样在JSX里面使用组件就写好了。


jsx代码

render-dom.js

export default {
  functional: true,
  props: {
    number: Number,
    renderFunc: Function
  },
  render: (h, ctx) => {
    return ctx.props.renderFunc(h, ctx.props.number)
  }
}

 

list.vue

<template>
  <ul>
    <li v-for="(item, index) in list" :key="`item_${index}`">
      <span v-if="!render">{{ item.number }}</span>
      <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: {
      type: Function,
      default: () => {}
    }
  }
}
</script>

<style>

</style>

 

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) {
      return (
        <CountTo endVal={number} on-click={this.handleClick} style={{ color: 'pink' }}></CountTo>
      )
    },
    handleClick (event) {
      console.log(event)
    }
  }
}
</script>

<style>

</style>

 

 

绑定事件

事件分为两种,一种自定义事件,一种是原生事件,
给组件可以绑定两种事件,第一是我们在里面定义的事件,
比如说在count-to这个组件内我们定义过一个on-animation-end的事件

那么在jsx如果想绑定这个事件,该怎么绑定呢?on开头,后面是我们的自定义事件的名称


<CountTo endVal={number} on-on-animation-end={this.handleEnd} style={{color: 'pink'}}></CountTo>

 

绑定原生click事件

就相当于给这个组件的最外层的div绑定一个事件

我们之前在render的时候讲过,原生的事件是nativeOn

所以在jsx里面,我们也用nativeOn-后面跟着click事件,注意单词不要拼错了:nativeOn-click

点击

jsx就这些。

补充内容

在render函数里面很多vue提供给我们的指令都没法使用了。需要我们自己通过原生的js来实现,
比如这里的阻止冒泡的事件,还有很多需要我们自己做的,在vue的文档上我们可以看到。

比如 用来阻止事件的默认行为的 prevent

比如鼠标可以选中这个文字。

文字不被选中事件


如果我们不想让它选中文字呢?prevent事件,函数里面什么都不用做。


把这两段先注释掉

鼠标放上来就没法选中了。


如果这个效果我们想用render或者JSX来实现。
这里的.prevent去掉。



调用事件对象的preventDefault()

这样也是没法选中的。我们鼠标的默认移动事件就是可以选中内容,当我们调用这个preventDefault()方法就不会选中。阻止它的默认行为

还有很多在template里面使用的指令和修饰符,在我们使用render的时候是使用不了的。那么就需要你去查阅下文档了。

作用域插槽



这里自定义渲染的内容是需要传入自定义函数或者是JSX 这种写法,也是比较的繁琐。不利于我们直观的看。

使用slot

在父组件内使用,两个标签中间的内容就会显示在组件的插槽的位置。

使用作用域插槽实现上面的效果

插槽向外传值可以参考:作用域插槽这块
https://juejin.cn/post/6844904200598454286#heading-7

 

定义一个,传入一个属性number。这里插槽通过v-bind绑定了属性,向外传值。



在父组件内使用



定义一个slot-scope这么一个属性.里面是count。count就相当于接收子组件内通过v-bind的对象。

所以这里就可以使用count.number获取到插槽传递的number值。


讲解上面一顿操作↓
组件内给它绑定了一个:number的属性,属性呢传入一个值item.number

然后在父组件内定义一个slot-scope定义一个名称为count

名称count就会指代你子组件里面绑定的值,这个插槽的属性值。包裹起来的一个对象,这个对象里面有个number属性

意思就是父组件内的count就是你在子组件传入的number的值,只不过包装成了一个对象,想获取这个number的值,就必须用count.number

使用的时候就用这个count指代的对象的

count对象 点 number 就能获取到它的值。那么在这个地方你就可以随便的使用了。

这就是作用域插槽的简单使用

具名插槽

如果有多个插槽就可以使用name来区分

调用的时候就需要加上slot等于那个定义的名称



它就会显示在对应名称的位置了。

本节代码

本节课只需要大家去查阅vue的渲染函数一节。我们在template中可以使用的一些指令还有修饰符,我们在render里面应该,如何去实现,


 

结束

 插槽可以参考

前方高能,这是最新的一波Vue实战技巧,不用则已,一用惊人
https://juejin.im/post/5ef6d1325188252e75366ab5#heading-1

 

posted @ 2020-05-24 18:34  高山-景行  阅读(447)  评论(0编辑  收藏  举报