从平时应用中引出

实际应用

渲染价格组件,价格中的每个数字都是由svg图片构成。如21.9,就是由三个2,1,“.”构成

  1. export default {
  2. name: 'ViewPrice',
  3. // 函数式组件: 无状态,无this上下文
  4. functional: true,
  5. props: {
  6. price: {
  7. type: String,
  8. default: '1'
  9. }
  10. },
  11. render(h, context) {
  12. let strArr = context.props.price.split('');
  13. let renderChildren = function() {
  14. let arr = [];
  15. strArr.forEach(item => {
  16. let t = item === '.' ? 'fg' : item;
  17. arr.push(h('img', { attrs: { src: require(`../../assets/img/number${t}.svg`) } }));
  18. });
  19. return arr;
  20. };
  21. return h('div', { class: 'view-price' }, renderChildren());
  22. }
  23. };

render函数介绍

render函数的作用是Vue触发生成VNode节点集合的函数
VNode集合是真实DOM的一个对象映射,通过VNode集合来生成真实的DOM结构。
我们知道Vue 推荐在绝大多数情况下使用HTML创建模板。
但是在某些场景下,我们需要使用JavaScript来实现比模板实现更加灵活和更有效率。在这些场景中,我们可以使用render函数,render函数更接近Vue编译器,减少解析模板的时间,提升效率。

使用介绍

绑定事件、添加样式、添加属性等等

  1. new Vue({
  2. el: '#app',
  3. data() {
  4. return {
  5. clickCount: 0,
  6. }
  7. },
  8. methods: {
  9. onClick() {
  10. this.clickCount += 1;
  11. }
  12. },
  13. render(createElement) {
  14. const button = createElement('button', {
  15. on: {
  16. click: this.onClick
  17. },
  18. attrs: {
  19. class: ""
  20. }
  21. }, 'Click me');
  22. const counter = createElement('span', [
  23. 'Number of clicks:',
  24. this.clickCount
  25. ]);
  26. return createElement('div', [
  27. button, counter
  28. ])
  29. }
  30. });

createElement(常用h来简写)

这个是用来创建元素的方法。

  1. // @returns {VNode}
  2. createElement(
  3. // {String | Object | Function}
  4. // 一个 HTML 标签名、组件选项对象,或者
  5. // resolve 了上述任何一种的一个 async 函数。必填项。
  6. 'div',
  7. // {Object}
  8. // 一个与模板中属性对应的数据对象。可选。
  9. {
  10. // (详情见下一节)
  11. },
  12. // {String | Array}
  13. // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  14. // 也可以使用字符串来生成“文本虚拟节点”。可选。
  15. [
  16. '先写一些文字',
  17. createElement('h1', '一则头条'),
  18. createElement(MyComponent, {
  19. props: {
  20. someProp: 'foobar'
  21. }
  22. })
  23. ]
  24. )

数据对象

用来给元素添加属性,事件和子元素等。下面是官方给全面数据。

  1. {
  2. // 与 `v-bind:class` 的 API 相同,
  3. // 接受一个字符串、对象或字符串和对象组成的数组
  4. 'class': {
  5. foo: true,
  6. bar: false
  7. },
  8. // 与 `v-bind:style` 的 API 相同,
  9. // 接受一个字符串、对象,或对象组成的数组
  10. style: {
  11. color: 'red',
  12. fontSize: '14px'
  13. },
  14. // 普通的 HTML attribute
  15. attrs: {
  16. id: 'foo'
  17. },
  18. // 组件 prop
  19. props: {
  20. myProp: 'bar'
  21. },
  22. // DOM 属性
  23. domProps: {
  24. innerHTML: 'baz'
  25. },
  26. // 事件监听器在 `on` 属性内,
  27. // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  28. // 需要在处理函数中手动检查 keyCode。
  29. on: {
  30. click: this.clickHandler
  31. },
  32. // 仅用于组件,用于监听原生事件,而不是组件内部使用
  33. // `vm.$emit` 触发的事件。
  34. nativeOn: {
  35. click: this.nativeClickHandler
  36. },
  37. // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  38. // 赋值,因为 Vue 已经自动为你进行了同步。
  39. directives: [
  40. {
  41. name: 'my-custom-directive',
  42. value: '2',
  43. expression: '1 + 1',
  44. arg: 'foo',
  45. modifiers: {
  46. bar: true
  47. }
  48. }
  49. ],
  50. // 作用域插槽的格式为
  51. // { name: props => VNode | Array<VNode> }
  52. scopedSlots: {
  53. default: props => createElement('span', props.text)
  54. },
  55. // 如果组件是其它组件的子组件,需为插槽指定名称
  56. slot: 'name-of-slot',
  57. // 其它特殊顶层属性
  58. key: 'myKey',
  59. ref: 'myRef',
  60. // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  61. // 那么 `$refs.myRef` 会变成一个数组。
  62. refInFor: true
  63. }

约束

所有Vnode节点都是唯一的。想要重复渲染组件,需要使用工厂函数循环生产。

  1. // 重复同一个组件
  2. render: function (h) {
  3. let component = h('div',{class: "hello"}, 'hello world!')
  4. return h('div', [
  5. // 错误 - 重复的 VNode
  6. component, component
  7. ])
  8. }

应该使用工厂函数来循环生成。

  1. render: function (h) {
  2. return h('div',
  3. Array.apply(null, { length: 20 }).map(function () {
  4. return h('p', 'hello')
  5. })
  6. )
  7. }

应用场景

场景1:某些场景下需要使用js编程来实现,相比模板更加灵活更加有效率。

  1. 需要比较复杂的条件判断来渲染不同组件的情况下
  2. 需要高度自定义化支持的功能
  3. 其他

示例1

  • 官方示例 ```javascript

Vue.component(‘anchored-heading’, { render: function (createElement) { return createElement( ‘h’ + this.level, // 标签名称 this.$slots.default // 子节点数组 ) }, props: { level: { type: Number, required: true } } })

  1. - **使用render函数渲染**
  2. ```javascript
  3. render: function (createElement) {
  4. return createElement(
  5. 'h' + this.level, // 标签名称
  6. this.$slots.default // 子节点数组
  7. )
  8. },
  9. props: {
  10. level: {
  11. type: Number,
  12. required: true
  13. }
  14. }

示例2

通过将h函数作为参数创建自义定功能

  • 定义组件

    1. <script>
    2. export default {
    3. name: 'HelloWorld',
    4. props: {
    5. msg: String,
    6. callback: {
    7. type: Function,
    8. require: true
    9. }
    10. },
    11. render(h) {
    12. // 通过把h函数作为参数生成更加灵活的子元素
    13. let children = this.callback.call(null,h);
    14. return h('div', {}, [
    15. h('h2', {class: 'red'}, '组件第一个元素是红色'),
    16. children
    17. ])
    18. }
    19. }
    20. </script>
  • 在父组件中使用

  1. <template>
  2. <div class="home">
  3. <HelloWorld msg="Welcome to Your Vue.js App" :callback="viewChildren"/>
  4. </div>
  5. </template>
  6. <script>
  7. // @ is an alias to /src
  8. import HelloWorld from '@/components/HelloWorld.vue'
  9. export default {
  10. name: 'Home',
  11. components: {
  12. HelloWorld
  13. },
  14. methods: {
  15. // 利用h函数来实现模板里面功能
  16. viewChildren(h) {
  17. console.log(this);
  18. // 这里使用h函数渲染组件
  19. // 充分利用h函数强大功能
  20. return h('h1', {class: 'green'}, '通过传入生成的子元素是绿色的')
  21. }
  22. }
  23. }
  24. </script>

场景2:使用JSX语法糖

写了很多 render 函可能会觉得写起来非常痛苦。这时候使用JSX语法糖,再利用babel插件将JSX转化为Javascript代码。

将 h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。

  1. new Vue({
  2. el: '#demo',
  3. render(h) {
  4. return <h2> hello world! </h2>;
  5. }
  6. })

场景3:函数式组件

函数式组件的特点:

  • 无状态
  • 无生命周期
  • 无上下文

优势

渲染开销低

image.png

基本原理

Vue实例化一个基本流程

Vue执行过程.png

Compile过程

获取到template模板,经过parse生成抽象语法书,然后再进行一定的优化,接着生成Render函数,最后render函数生成VNode集合。

image.png

Render函数基本原理

Render.png

参考资料

Vue-render文档 JSX语法 剖析Vue内部运行机制 Vue进阶系列之render