Render 函数及动态组件

By TommyShao

©️广州筷子信息科技

Render函数

  • createElement
  • JavaScript代替模板
  • JSX模板
  • 函数化组件
  • 模板编译
  • 案例分析

动态组件

  • 循环子组件
  • is 扩展组件
  • 子组件实例化
  • 案例分析

Render工厂🏭函数

v-dom组件的渲染

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);

v-dom组件编译

var CommentBox = React.createClass({displayName: 'CommentBox',
  render: function() {
    return (
      React.createElement('div', {className: "commentBox"},
        "Hello, world! I am a CommentBox."
      )
    );
  }
});

ReactDOM.render(
  React.createElement(CommentBox, null),
  document.getElementById('content')
);

Vue 2.x 的 Render 函数✈️

Vue.component('anchored-heading', {
  render: function (createElement) {
    return createElement(
      'h' + this.level,   // tag name 标签名称
      this.$slots.default // 子组件中的阵列
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

createElement参数📽

createElement(
  // {String | Object | Function}
  // 一个 HTML 标签字符串,组件选项对象,
  // 或者一个返回值类型为String/Object的函数,必要参数
  'div',
  // {Object}
  // 一个包含模板相关属性的数据对象
  // 这样,您可以在 template 中使用这些属性.可选参数.
  {
    // data object
  },
  // {String | Array}
  // 子节点(VNodes),可以是一个字符串或者一个数组. 可选参数.
  [
    createElement('h1', 'hello world'),
    createElement(MyComponent, {
      props: {
        someProp: 'foo'
      }
    }),
    'bar'
  ]
)

data object参数

{
  // 和`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。
  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 slots in the form of
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => h('span', props.text)
  },
  // 如果组件是其他组件的子组件,需为slot指定名称
  slot: 'name-of-slot'
  // 其他特殊顶层属性
  key: 'myKey',
  ref: 'myRef'
}

JavaScript代替模板


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

slots 内容

// slots
render: function (createElement) {
  // <div><slot></slot></div>
  return createElement('div', this.$slots.default)
}

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

函数化组件

Vue.component('my-component', {
  // 标记组件为 functional, 
  // 这意味它是无状态(没有 data),无实例(没有 this 上下文)
  functional: true,
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  },
  // Props 可选
  props: {
    // ...
  }
})

模板编译

  • Vue.compile

var res = Vue.compile('<div><span>{{ msg }}</span></div>')
new Vue({
  data: {
    msg: 'hello'
  },
  render: res.render,
  staticRenderFns: res.staticRenderFns
})

模板编译

  • *.vue单组件

    • 当声明 template 标签,则由 vue-loader 的 Vue-compile 方法重写
    • 当没有声明 template 标签,则组件内的 render 函数起作用
    • 子组件声明可以使用 render 函数

模板编译

  • *.vue单组件

    • 当声明 template 标签,则由 vue-loader 的 Vue-compile 方法重写
    • 当没有声明 template 标签,则组件内的 render 函数起作用
    • 子组件声明可以使用 render 函数
// Render.js
export default {
  data () {
    return {
      text: 'Render'
    }
  },
  render (createElement) {
    const self = this._self
    return createElement(
      'div',
      {
        'class': 'class-render'
      },
      `${self.text}`
    )
  }
}

模板编译

  • *.vue单组件

    • 当声明 template 标签,则由 vue-loader 的 Vue-compile 方法重写
    • 当没有声明 template 标签,则组件内的 render 函数起作用
    • 子组件声明可以使用 render 函数
<template>
  <div>
    {{ title }}
    <p>{{ now }}</p>
    <button @click="handleClick">click</button>
    <hr />
    <child></child>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        title: 'Parent',
        now: ''
      }
    },
    methods: {
      handleClick () {
        this.title = 'Parent click Title'
      }
    },
    components: {
      child: {
        data () {
          return {
            title: 'Child'
          }
        },
        methods: {
          handleClick () {
            const now = new Date().getTime()
            this.title = this.title.replace(/-.*$/, '') + '-' + now
            this.$parent.now = now
          }
        },
        created () {
          this.$on('change', (val) => {
            console.log('parent title change:', val)
          })
        },
        render (createElement, context) {
          const self = this._self
          const h = self.$createElement
          const parent = self.$parent

          return h(
            'div',
            [
              h('h2', parent.title),
              h('h3', self.title),
              h('button', {
                on: {
                  click: self.handleClick
                }
              },
              'child click'),
              h('button', {
                on: {
                  click: () => {
                    parent.title = 'child click title'
                    this.$emit('change', parent.title)
                  }
                }
              },
              'click child title')
            ]
          )
        }
      }
    }
  }
</script>

模板编译

  • *.vue单组件

    • 当声明 template 标签,则由 vue-loader 的 Vue-compile 方法重写
    • 当没有声明 template 标签,则组件内的 render 函数起作用
    • 子组件声明可以使用 render 函数

案例1

vue-JSX 代替createElement

render (h) {
  return (
    <div
      // 自定义属性
      id="foo"
      // DOM 属性
      domPropsInnerHTML="bar"
      // 事件定义
      onClick={this.clickHandler}
      // 原生事件定义
      nativeOnClick={this.nativeClickHandler}
      // 特殊的顶级属性
      class={{ foo: true, bar: false }}
      style={{ color: 'red', fontSize: '14px' }}
      key="key"
      ref="ref"
      // assign the `ref` is used on elements/components with v-for
      refInFor
      slot="slot">
    </div>
  )
}

vue-jsx 与 React jsx的差异

  1. vNode 数据格式差异
  2. createElement 参数差异

vue-jsx 特征

  1. 可以使用不注册的组件
  2. 属性展开
  3. 指令的定义

vue-jsx 特征

  1. 可以使用不注册的组件
  2. 属性展开
  3. 指令的定义
import Todo from './Todo.js'


export default {
  render (h) { // createElement函数自动注入
    const data = {
      class: ['b', 'c']
    }
    
    // 定义指令
    const directives = [
      { name: 'my-dir', value: 123, modifiers: { abc: true } }
    ]
    
    // data自动展开,class 合并到一起
    // 以首写字母大写开头,不需注册 Todo 组件,自动注册
    return <Todo class="a" {...data} {...{ directives }} />
  }
}

案例2

动态组件

循环子组件

  • 顾名思义通过 v-for 循环对组件的增加和删减

is 组件

  • 通过 component 组件标签绑定:is属性来决定生成属于哪个组件,比如router-view 的实现方式。

子组件实例化

  • 通过子组件的实例化对象,通过 dom 操作 append 到父组件中,并使用实例化对象的函数进行父子组件之间的通信,不继续父组件特性,比如 vuex 的实例对象 store,vue-router 的$router等非全局注册对象
  • 通过 $mount() 函数将子组件挂载到父组件中,继承父组件特性

实例 - 云创意库组件

Vue-Render-dynamic-component

By Tomi Eric

Vue-Render-dynamic-component

Vue-Render & dynamic-component

  • 1,692