vlambda博客
学习文章列表

手摸手教你玩转 vue render 函数


鱼头的Web海洋
一个名为Web的海洋世界
69篇原创内容
Official Account
  • 作者:前端自学驿站
  • 原文:手把手教你玩转render函数
  • 首发:https://juejin.cn/post/6969226302767235108?share_token=224203c3-bff9-4a64-b278-8ba0ab8cb824

前言

本文及之后所有系列文章组件封装都是基于element-ui组件库进行封装,里面并不会去讲element-ui的API, 本文要探讨是render函数在封装组件中的一些技巧思维且可以用于生产项目的所以并没有用Vue3, 后面会慢慢封装成一个中后台通用表单集成组件, 本文先从输入框开始,

支持的类型

  • text
  • input
  • number
  • password
  • email
  • textarea

Input Attributes

参数 说明 类型 默认值
value / v-model 绑定值 [String, Number] ''
type 表单类型 ["input", "text", "number", "password", "email", "textarea"] text
支持el-input所有参数


dynamic-input

<script>
// 供全局使用
let h

// 支持的类型(只做输入框,radio/checkbox另外拆开)
const selectInputType = [
  'input',
  'text',
  'number',
  'password',
  'email',
  'textarea'
]
export default {
  name'DynamicInput',
  // attrs不显示在dom上
  inheritAttrsfalse,
  props: {
    // 类型
    type: {
      default'text',
      validatortypeVal => {
        return selectInputType.includes(typeVal)
      }
    },
    // 绑定值
    value: {
      type: [StringNumber],
      default''
    },

    // 支持el-input所有参数
  },
  computed: {
    newValue: {
      get({ value }) {
        return value
      },
      set(val) {
        this.$emit('input', val)
      }
    },
  },
  methods: {
    onInputHandle(val) {
      this.newValue = val
    }
  },
  render() {
    h = this.$createElement
    const {
      onInputHandle,
      $attrs,
    } = this

    return h('el-input', {
      props: {
        typethis.type,
        valuethis.newValue,
        ...this.$attrs
      },
      on: {
        input: onInputHandle,
      },
    })
  }
}

</script>

可以Get到的技巧

  • 借助computed来实现双向绑定
  • 通过 $attrs来进行参数透传,可以省略 prop不必传的参数

这里讲下inheritAttrs,这个参数就是是否将$attrs中定义的数据挂载到dom层面上,直接上图

手摸手教你玩转 vue render 函数

表单支持的修饰符

  • number:通过 parseFloat()解析之后的字符串数值
  • trim:过滤首尾空白字符
  • lazy:将事件触发从input从而转为在「 类似change」在值确认之后响应(当输入法没有按下时不做值变动可以使用这个)

最常见的就是当我们使用number类型输入框但输入框最后给定的值是字符串,这个时候可以通过添加.number修饰符内部帮你自动转换为number类型

<dynamic-input
  v-model="testVal"
  type="number"
 />

<!-- testVal => 'string' -->

<dynamic-input
  v-model.number="testVal"
  type="number"
 />

<!-- testVal => 'number' -->

「lazy修饰符的效果」

手摸手教你玩转 vue render 函数
lazy修饰符

Input Events

支持el-input所有事件

<template>
  <dynamic-input
    ref="dynamicInput"
    v-model.lazy="testVal"
    :type="testType"
    :class="computedClass"
    @change="changeMethod"
    @input.native="inputMethod"
  />

</template>

<script>
import dynamicInput from '@/components/common/dynamic-input'
export default {
  name'CkTestInput',
  components: {
    'dynamic-input': dynamicInput
  },
  data() {
    return {
      testType'text',
      testVal'',
      restaurants: []
    }
  },
  computed: {
    computedClass({ testVal }) {
      return { 'is-fill': testVal }
    }
  },
  methods: {
    changeTestVal(changeVal) {
      this.testVal = changeVal
    },
    inputMethod(val) {
      console.log(val, '-->')
    },
    changeMethod(val) {
      console.log(val, '-->')
    },
  }
}
</script>

同样支持原生修饰符事件

手摸手教你玩转 vue render 函数

Input Methods

支持el-input所有方法,前提得通过ref去引用dynamic-input组件,组件封装的el-input默认取名elInput

<template>
  <dynamic-input
    ref="dynamicInput
    value="
test"
  />

</template>

<script>
export default {
  mounted() {
    // 可以获取到dynamicInput组件内封装的elInput组件
    this.$refs.dynamicInput.$refs.elInput
  }
}
</script>

你也可以通过定义refName去重命名内部elInput组件的ref值

<template>
  <dynamic-input
    ref="dynamicInput"
    refName="child-input'
    value="
test"
  />

</template>

<script>
export default {
  mounted() {
    // 可以获取到dynamicInput组件内封装的elInput组件
    this.$refs.dynamicInput.$refs['child-input']
    const {
      dynamicInput
    } = this.$refs
    // 可以通过去调用el-input组件提供方法
    dynamicInput.$refs['child-input'].focus() 
  }
}
</script>

我们只要在render函数中添加这一行判断就行

<script>
 export default {
  render() {
    let {
      $attrs
    } = this
    return h('el-input', {
      // ...
      ref: $attrs['ref-name'] || 'elInput'
    })
  }
}
</script>

Input Slots

支持所有el-input提供的内置slot

<template>
  <dynamic-input
    ref="dynamicInput"
    refName="child-input'
    value="
test"
  >

    <template slot="prepend">Http://</template>
    <i slot="prefix" class="el-input__icon el-icon-search" />
    <i slot="suffix" class="el-input__icon el-icon-date" />
  </dynamic-input>
</template>

对于插槽解析这一块我们需要注意一下,官方文档对于render函数写slot没有列子,对于怎么去实现这一块也写的很晦涩,需要注意的是render函数中的第三个参数是描述当前组件的子内容,虽然slot是当前组件提供的内置内容,让你可以渲染到当前组件的指定内容,但是并不是这样就能实现的

<script>
export default {
  render() {
    h = this.$createElement
    return h('el-input', {
      // ....
    }, Object.values(this.$slots))
  }
}
</script>

这里第三个参数最后的结果是 => [VNode, VNode], 但是要注意VNode并没有指定插槽名称

手摸手教你玩转 vue render 函数

文档只在这里很浅的带过了一下 深入数据对象👉

所以渲染el-input提供的内置插槽内容的时候我们需要去定义一个提供slot名称的数据对象来渲染VNode,这里我们借助一个无状态的函数式组件做件事

slotContent.js

// 用于处理插槽
export default {
  name'SlotContent',
  // 声明这是一个函数式组件
  functionaltrue,
  props: {
    render: {
      typeFunction,
      requiretrue
    },
    dataObject
  },
  render(h, ctx) => {
    // 函数式组件中没有this, 所有可用的API都提供在ctx中
    return ctx.props.render(h, ctx.props.data)
  }
}

对于一些不做任何状态处理的组件,可以用函数式组件的方式来生成相对比较「轻量」的组件, 详情看文档描述👉

<script>
// 渲染el-input提供的slot
function renderElInputSlots($scopedSlots{
  const slots = Object.keys($scopedSlots).map(slotName => {
    return h('slot-content', {
      props: {
        render: $scopedSlots[slotName]
      },
      slot: slotName,
      key: slotName
    })
  })

  return slots
}

export default {
  render() {
    h = this.$createElement
    const {
      $scopedSlots,
    } = this

    // 配置插槽(当用户传递了el-input提供的slot才去渲染)
    let elInputSlotsVNode = []
    if (Object.keys($scopedSlots).length) {
      elInputSlotsVNode = renderElInputSlots($scopedSlots)
    }

    return h('el-input', {
      // ...
    }, elInputSlotsVNode)
  }
}
</script>

「效果图💗」

手摸手教你玩转 vue render 函数

Autocomplete

autocomplete 是一个可带输入建议的输入框组件。可用于远程搜索, 通过传递is-autocomplete来确定是否渲染el-autocomplete组件

注意⚠:这里的is-autocomplete是用来判断是否渲染el-autocomplete组件的,并不是input提供的autocomplete属性

参数 说明 类型 默认值
is-autocomplete 是否渲染成autocomplete组件 Boolean false
fetch-suggestions 返回输入建议的方法,仅当你的输入建议数据 resolve 时,通过调用 callback(data:[]) 来返回它 Function 必传

详情请查看Element-Ui官网 https://element.eleme.cn/#/zh-CN/component/input

<script>
export default {
  props: {
    // 是否渲染el-autocomplete组件
    isAutocomplete: {
      typeBoolean,
      defaultfalse
    }
    // 支持el-input所有参数
  },
  computed: {
    // 最终要渲染的组件名称
    componentTag: {
      get({ isAutocomplete, $attrs }) {
        const fetchSuggestions = $attrs['fetch-suggestions']
        // fetchSuggestions 返回输入建议的方法(isAutocomplete为true时必传)
        return isAutocomplete && typeof fetchSuggestions === 'function'
          ? 'el-autocomplete'
          : 'el-input'
      }
    }
  },
  render() {
    h = this.$createElement
    const {
     componentTag
    } = this


    return h(componentTag, {
      // ...
    }, elInputSlotsVNode)
  }
}
</script>

上面实现的就类似于内置动态组件[component]组件的效果

至此这个组件就写完了,总的就100来行但相对来说很灵活了,后面在封装Form组件会将这个组件集成进去,这个时候就能很好的体现出render函数封装组件的灵活性

在线卑微,如果觉得这篇文章对你有帮助的话欢迎大家点个赞👻

写在最后

如果文章中有那块写的不太好或有问题欢迎大家指出,我也会在后面的文章不停修改。也希望自己进步的同时能跟你们一起成长。喜欢我文章的朋友们也可以关注一下

我会很感激第一批关注我的人。此时,年轻的我和你,轻装上阵;而后,富裕的你和我,满载而归。

鱼头的Web海洋
一个名为Web的海洋世界
69篇原创内容
Official Account