vue render函数解析

1、使用场景:Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。 render 函数的使用,重点:样式、事件、插槽、指令、props、v-model、函数组件的处理。

2、参数说明:

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中 attribute 对应的数据对象。可选。
  {
    // 与 `v-bind:class` 的 API 相同,
    // 接受一个字符串、对象或字符串和对象组成的数组
    'class': {
      foo: true,
      bar: false
    },
    // 与 `v-bind:style` 的 API 相同,
    // 接受一个字符串、对象,或对象组成的数组
    style: {
      color: 'red',
      fontSize: '14px'
    },
    // 普通的 HTML attribute
    attrs: {
      id: 'foo'
    },
    // 组件 prop
    props: {
      myProp: 'bar'
    },
    // DOM property
    domProps: {
      textContent: 'div 文本',// 优先级高于 v-text
      innerHTML: 'BAR'  // 优先级高于 v-html
    },
    // 事件监听器在 `on` 内,
    // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
    // 需要在处理函数中手动检查 keyCode。
    on: {
      click: this.clickHandler
    },
    // 仅用于组件,用于监听原生事件,而不是组件内部使用
    // `vm.$emit` 触发的事件。
    nativeOn: {
      click: this.nativeClickHandler
    },
    // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
    // 赋值,因为 Vue 已经自动为你进行了同步。
    directives: [
      {
        name: 'my-custom-directive',
        value: '2',
        expression: '1 + 1',
        arg: 'foo',
        modifiers: {
          bar: true
        }
      }
    ],
    // 作用域插槽的格式为
    // { name: props => VNode | Array<VNode> }
    scopedSlots: {
      default: props => createElement('span', props.text)
    },
    // 如果组件是其它组件的子组件,需为插槽指定名称
    slot: 'name-of-slot',
    // 其它特殊顶层 property
    key: 'myKey',
    ref: 'myRef',
    // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
    // 那么 `$refs.myRef` 会变成一个数组。
    refInFor: true
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

3、使用例子:render函数在vue中非常重要,但其实本质上执行渲染工作的是h函数,本质上也就是createElement函数!

{
  title: 'Name',
  key: 'name',
  render: (h, params) => {
    return h('div', [
      h('a',{
          class: {
              'a-font-size': true
          }
      }, params.row.name),
      h('p',{
          class: {
              'p-margintb': true
          }
      }, '从 ' + params.row.gmtModified + ' 到 ' + params.row.endDate ),
      h('p',{
          class: {
              'p-marginb': true
          }
      },'负责人:'+ params.row.users.name)
    ])
  }
}

// 渲染结果
<div>
    <a class="a-font-size">Name</a>
    <p class="p-margintb">从2016年7月1日到2018年3月19日</p>
    <p class="p-marginb">负责人:Frank</p>
</div>

3-1、使用插槽:

//button组件
<template>
  <div>
    <slot name="left"></slot>
    <button>
      <slot v-bind:person="person">
        <span>按钮</span>
      </slot>
    </button>
    <slot name="right" v-bind:age="person.age"></slot>
  </div>
</template>

<script>
  export default {
  name: "MyButton",
    data() {
      return {
        person: {
          name: 'jack',
          age: 23,
        },
      }
    },
  }
</script>

普通命名插槽,使用h('template',{slot:'slotName'},children) 编写,然后放渲染组件的第三个参数里。 

<template>
  <div>
    <comp></comp>
  </div>
</template>
<script>
const comp = {
   render(h) {
    //NOTE h 第一个参数为 template 第二个参数里的 slot 属性指定插槽名称
    const slotLeft = h('template', { slot: 'left' }, '按钮左边')
    const slotRight = h('template', { slot: 'right' }, '按钮右边')
    const slotDefault = h('template', { slot: 'default' }, '默认插槽')
    const children = [slotLeft, slotDefault, slotRight]
    console.log(h(button, {}, children))
    return h(button, {}, children)
  },
}
import button from './button.vue'
export default {
  name: 'UseButton',
  components: {
    comp,
  },
}
</script>

作用域插槽在第二个参数的 scopedSlots 对象里,该对象的每个属性名是组件的插槽名,值是一个函数,参数为插槽绑定的数据。

<template>
  <div>
    <comp></comp>
  </div>
</template>
<script>
const comp = {
   render(h) {
    //NOTE h 第一个参数为 template 第二个参数里的 slot 属性指定插槽名称
    const slotLeft = h('template', { slot: 'left' }, '按钮左边')
    const children = [slotLeft]
    return h(button, {
        scopedSlots: {
          default: props => {
            console.log(props)
            const { person } = props
            const text = `作用域插槽,${JSON.stringify(person)}`
            // 返回 h 创建的 VNode
            return h('span', {}, text)
          },
          right: props => {
            console.log(props)
            const { age } = props
            // 返回 jsx
            return <span>按钮右边 {age} 岁</span>
          },
        },
      }, children)
  },
}
import button from './button.vue'
export default {
  name: 'UseButton',
  components: {
    comp,
  },
}
</script>

3-2、v-model

//1. 在数据对象中使用 model 属性:

{
  model: {
    value: this.value,// value 是 data 里的属性
    callback: value => {
      // 可以再赋值之前做其他逻辑
      // 验证数据
      // 触发事件
      this.value = value
    }
  }
}
//2.  传递 value + 监听 input 事件

{
  props: {
    // value 是 data 中的属性
    value: this.value
  },
  on: {
    input: value => {
      // 可做其他事情
      // 触发事件
      this.value = value
    }
  }
}
//3.  在 jsx 中使用 vModel 属性

// input 是 data 中的属性
<MyInput vModel={this.input} />

4、render 封装一个输入框


export default {
  name: 'MyInput',
  props: {
    // 需要实现 v-model 指令
    value: {
      type: [String, Number],
      default: '',
    },
  },
  render(h) {
    return h('input', {
      class: {
        'my-input': true,
      },
      style: {
        backgroundColor: '#ccc',
      },
      attrs: {
        id: 'my-input',
        class: 'a-my-input',
        'data-key': 'key',
      },
      domProps: {
        value: this.value,
      },
      // 监听 input 的 input 事件
      on: {
        input: ({ target }) => {
          this.$emit('input', target.value)
        },
      },
    })
  },
}

使用该组件

<MyInput v-model="myInput" />

在 render 函数中使用

export default {
  name: 'UseInput',
  data() {
    return {
      input: '',
    }
  },
  render(h) {
    return h('div', {}, [
      h(MyInput, {
        model: {
          value: this.input,
          callback: value => {
            // 可在此做其他事件
            this.input = value
          },
        },
      }),
      h('h3', {}, this.input),
    ])
  },
}

参考:https://segmentfault.com/a/1190000040152547