Render()函数&JSX

Render()函数&JSX

  本文参考这篇文章 https://www.jianshu.com/p/7353974795dd

1. 前言
1.1 HTML DOM树与和Vue的virtual DOM
  • 我们知道,浏览器在解析HTML文件时,会将HTML标签解析成一个DOM树(tree of DOM nodes)
    。通过结构化的组织节点元素,浏览器可以很方便的跟踪整个页面的情况,但频繁的局部更新节点代价很高。
  • 为了更高效的渲染HTML,Vue.js和React以及Ember.js一样,根据真实DOM的映射构建对应的JS对象,也就是虚拟DOM(Virtual
    DOM)。在数据和DOM之间创建一个缓冲地带,不用每次都更新DOM,详情见下面React Virtual DOM的示意图:

在这里插入图片描述

1.2 Vue组件中的Slot
  • Vue.js的Slot机制是指,父组件在嵌套使用子组件时,可以传递HTML代码片段给子组件渲染
2.render()函数
2.1 为什么要使用render()函数
  • 使用过Vue.js的朋友都知道,好像大部分时间都在使用template的方式来创建HTML,因为vue提供了v-if、v-for等一系列的控制指令,让我们开发体验轻松又愉悦。
  • 但除此之外,其实Vue.js还提供了render()函数来创建HTML。让我们可以通过JS逻辑代码,更灵活的创建HTML。
  • 如同vue官网的例子所说:在封装文章标题的<Title>组件时,不确定最终生成的,具体是h1~h6的标签。这个时候,如果用v-if做判断,那么代码量太大,但用render()函数来实现就很简单。
  // 1.tempalge 实现
	<script type="text/x-template" id="test-template">
	  <h1 v-if="level === 1">
	    <slot></slot>
	  </h1>
	  <h2 v-else-if="level === 2">
	    <slot></slot>
	  </h2>
	  <h3 v-else-if="level === 3">
	    <slot></slot>
	  </h3>
	  <h4 v-else-if="level === 4">
	    <slot></slot>
	  </h4>
	  <h5 v-else-if="level === 5">
	    <slot></slot>
	  </h5>
	  <h6 v-else-if="level === 6">
	    <slot></slot>
	  </h6>
	</script>
	<script>


    Vue.component('test', {
      template: '#test-template',
      props: {
        level: {
          type: Number,
          required: true
        }
      }
    })
   // 2.用render() 函数实现
  Vue.component('test', {
    render: function (createElement) {
      return createElement(
        'h' + this.level,   // 标签名称
        this.$slots.default // 由子节点构成的数组
      )
    },
    props: {
      level: {
        type: Number,
        required: true
      }
    }
  })
2.2 render()函数的传参

render(crateElmentFn,context){}

  • createElemntFn是Vue用来动态创建HTML的核心方法【注意:把createElement函数命名为h是vue.js的通用惯例,记得两者是同一个东西】
  • context是组件实例的上下文环境,包括了组件实例的所有属性
    • props: 提供props 的对象
    • children: VNode 子节点的数组
    • slots: slots 对象
    • data: 传递给组件的 data 对象
    • parent: 对父组件的引用

  render方法的实质就是生成template模板;
  render函数生成的内容相当于template的内容,所以使用render函数时,在.vue文件中需要先把template标签去掉。只保留逻辑层

3. createElement()函数

createElemnt(newNode,newNodeConfig,childVNodeList)

3.1 createElement() 传参
  • createElemnt()函数主要有三个参数:
    • newNode:要创建的节点【必填参数】
      • 参数类型: {String | Object | Function},可以是要创建的HTML标签名称,也可以是组件对象,也可以是返回为String或Vue Object的异步函数
    • newNodeConfig:新节点的配置对象【选填】
    • childVNodeList:新节点要包含的子节点集合【选填】
      • 参数类型: {String | Array}
      • 注意事项:vue官方教程标明传递的VNodes必须是唯一的,如果想重复创建相同的HTML元素,需要用工厂函数来实现
  • 调用示例:
createElement(
  'div',
  {},
  [
    'Some text comes first.',
    createElement('h1', 'A headline'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)
  • newNodeConfig参数详解
{
  // 和 `v-bind:class` 的 API 相同【注意:由于是关键字,要用单引号包含】
  'class': {
    foo: true,
    bar: false
  },
  // 和 `v-bind:style` 的 API 相同【注意:由于是关键字,要用单引号包含】
  'style': {
    color: 'red',
    fontSize: '14px'
  },
  // 普通的 HTML 属性
  attrs: {
    id: 'foo'
  },
  // 组件 props
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'baz'
  },
  // 事件处理程序嵌套在 `on` 字段下,
  // 然而不支持在 `v-on:keyup.enter` 中的修饰符。
  // 因此,你必须手动检查
  // 处理函数中的 keyCode 值是否为 enter 键值。
  on: {
    click: this.clickHandler
  },
  // 仅对于组件,
  // 用于监听原生事件,而不是组件内部
  // 使用 `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。
  // 注意,由于 Vue 会追踪旧值,
  // 所以不能对`绑定`的`旧值`设值
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽(scoped slot)的格式如下
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果此组件是另一个组件的子组件,
  // 需要为插槽(slot)指定名称
  slot: 'name-of-slot',
  // 其他特殊顶层(top-level)属性
  key: 'myKey',
  ref: 'myRef'
}
3.2 使用createElement()替代template创建HTML案例

  template代码:

  Vue.component('test', {
      template: `
      <ul v-if="items.length">
        <li v-for="item in items">{{ item.name }}</li>
      </ul>
      <p v-else>No items found.</p>
      `,
      props: {
        level: {
          type: Number,
          required: true
        }
      },
      data() {
        return {
          items: [{name: 1}, {name: 2}, {name: 3}]
        }
      }
    })

  createElement代码:

  Vue.component('test', {
    render: function (createElement) {
      if (this.items.length) {
        return createElement(
        'ul', {}, this.items.map((item)=> {
          return createElement('li',item.name)
        })
      )
      } else {
        return createElement('p', 'No items found.')
      }

    },
    props: {
      level: {
        type: Number,
        required: true
      }
    },
    data() {
      return {
        items: [{name: 1}, {name: 2}, {name: 3}]
      }
    }
  })
3.3 渲染函数实现类似模板v-if和v-for

  只要在原生的 JavaScript 中可以轻松完成的操作,Vue 的渲染函数就不会提供专有的替代方法。比如,在模板中使用的 v-if 和 v-for:
  template代码:

    Vue.component('test', {
      template: `
      <ul v-if="items.length">
        <li v-for="item in items">{{ item.name }}</li>
      </ul>
      <p v-else>No items found.</p>
      `,
      props: {
        level: {
          type: Number,
          required: true
        }
      },
      data() {
        return {
          items: [{name: 1}, {name: 2}, {name: 3}]
        }
      }
    })

  这些都可以在渲染函数中用 JavaScript 的 if/else 和 map 来重写:
  createElement代码:

  Vue.component('test', {
    render: function (createElement) {
      if (this.items.length) {
        return createElement(
        'ul', {}, this.items.map((item)=> {
          return createElement('li',item.name)
        })
      )
      } else {
        return createElement('p', 'No items found.')
      }

    },
    props: {
      level: {
        type: Number,
        required: true
      }
    },
    data() {
      return {
        items: [{name: 1}, {name: 2}, {name: 3}]
      }
    }
  })
3.4 渲染函数实现类似模板v-model

  渲染函数中没有与 v-model 的直接对应——你必须自己实现相应的逻辑:
  template代码:

    Vue.component('test', {
      template: `
      <div>
        <input type="text" :value="iptValue" @input="handleInput">
      </div>
      `,
      props: {
        value: {
          type: String,
          required: false
      },
    },
    data () {
        return {
          iptValue: ''
        }
      },
      methods: {
        handleInput(event) {
          this.$emit('input', event.target.value)
        }
      },
      created() {
        this.iptValue = this.value
      }
    })

  这就是深入底层的代价,但与 v-model 相比,这可以让你更好地控制交互细节。
  createElement代码:

  Vue.component('test', {
    render: function (createElement) {
      let that = this
      return createElement(
        'div', {}, [
          createElement(
          'input', {
              domProps: {
                          value: that.value,
                          type: "text"
                        },
              on: {
                input: function (event) {
                  console.log(event.target.value);
                  that.$emit('input', event.target.value) // 使用input事件实现向外面传值出去。这样使用v-model就可以实现双向绑定了
                }
              }
            }, '')
        ])
    },
    props: {
        value: {
          type: String,
          required: false
        }
    }
  })
3.5 事件绑定

  对于 .passive、.capture 和 .once 这些事件修饰符, Vue 提供了相应的前缀可以用于 on:

修饰符前缀
.passive&
.capture!
.once~
.capture.once 或 .once.capture~!

  例如:

on: {
  '!click': this.doThisInCapturingMode,
  '~keyup': this.doThisOnce,
  '~!mouseover': this.doThisOnceInCapturingMode
}

  对于所有其它的修饰符,私有前缀都不是必须的,因为你可以在事件处理函数中使用事件方法:

修饰符处理函数中的等价操作
.stopevent.stopPropagation()
.preventevent.preventDefault()
.selfif (event.target !== event.currentTarget) return
按键: .enter, .13if (event.keyCode !== 13) return (对于别的按键修饰符来说,可将 13 改为另一个按键码)
修饰键: .ctrl, .alt, .shift, .metaif (!event.ctrlKey) return (将 ctrlKey 分别修改为 altKey、shiftKey 或者 metaKey)
on: {
  keyup: function (event) {
    // 如果触发事件的元素不是事件绑定的元素
    // 则返回
    if (event.target !== event.currentTarget) return
    // 如果按下去的不是 enter 键或者
    // 没有同时按下 shift 键
    // 则返回
    if (!event.shiftKey || event.keyCode !== 13) return
    // 阻止 事件冒泡
    event.stopPropagation()
    // 阻止该元素默认的 keyup 事件
    event.preventDefault()
    // ...
  }
}	
3.6 插槽

  你可以通过 this.$slots 访问静态插槽的内容,每个插槽都是一个 VNode 数组:

  template代码:

    Vue.component('test', {
      template: `
      <div>
        <slot></slot>
      </div>
      `
    })

  createElement代码:

  Vue.component('test', {
    render: function (createElement) {
      let that = this
      return createElement(
        'div', this.$slots.default)
    }
  })

  也可以通过 this.$scopedSlots 访问作用域插槽,每个作用域插槽都是一个返回若干 VNode 的函数(借鉴其他博客的):

props: ['message'],
render: function (createElement) {
  // `<div><slot :text="message"></slot></div>`
  return createElement('div', [
    this.$scopedSlots.default({
      text: this.message
    })
  ])
}

  如果要用渲染函数向子组件中传递作用域插槽,可以利用 VNode 数据对象中的 scopedSlots 字段:

render: function (createElement) {
  return createElement('div', [
    createElement('child', {
      // 在数据对象中传递 `scopedSlots`
      // 格式为 { name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: function (props) {
          return createElement('span', props.text)
        }
      }
    })
  ])
}
3.7 VNode 必须唯一

  组件树中的所有 VNode 必须是唯一的。这意味着,下面的渲染函数是不合法的:

render: function (createElement) {
  var myParagraphVNode = createElement('p', 'hi')
  return createElement('div', [
    // 错误 - 重复的 VNode
    myParagraphVNode, myParagraphVNode
  ])
}

  如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了 20 个相同的段落:

render: function (createElement) {
  return createElement('div',
    Array.apply(null, { length: 20 }).map(function () {
      return createElement('p', 'hi')
    })
  )
}
4

  虽然render()函数创建HTML代码片段很灵活,但整段整段的JS配置代码,的确阅读性很差。为了让代码更简单,我们可以使用JSX,直接在JS代码中书写HTML:

  render: function (h) {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
	
  • 为了支持JSX,需要通过一个JSX的Babel的插件
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值