写不好 render? 让我通过几个例子帮你梳理其中的逻辑。

前言

之前在通过官方文档学习 vue 的时候,关于《渲染函数&JSX》章节的内容,只是简单看看就一笔带过了,也没有真正的使用 render 函数去做一些 demo,练练手。

最近温习了一遍官方文档,读到这个章节决定使用 render 函数写几个 demo 试试手。不上手觉得没有什么难的,一上手人傻了。大脑就跟短路一样,不知道如何该往下写了。

写下这篇文章,梳理一下逻辑,也给初学者排一排逻辑认知上的错误(笔者也是菜鸟)。正式内容开始前,笔者假设各位看官已经知道了 vue 中的普通语法也能够简单的使用。

正文

首先,我们都知道 render 函数位于 vue 配置项的顶层,优先级大于 template。该函数中的 createElement 参数(本文中简称 h)是其中的关键。h 函数的第一个参数接收一个 HTML 标签字符串 或者是注册组件的自定义元素名 或者是单文件组件对象(单文件组件的情况下)。

js 复制代码
import single from "./xxxx.vue"
export default {
    render(h){
        h("div")         // 普通的 HTML 标签
        h("available")   // 已经注册的组件名
        return h(single)     // 引入的单文件组件
    },
    components:{
        "available":{
            template:`<div>custom-element</div>`,
            name:"unavailable"
        }
    }
}

第二个参数是一个数据对象,第三个参数是第一个参数元素或组件内的子节点,可以是文本内容、另一个HTML元素、另一个组件。

js 复制代码
render(h){
    return h("div", [ "html inner value" , h("h1", "title inner value") , h(some)] )
    //                 第一个子节点           第二个子节点                第三个子节点
}

其中较为重要的便是第二个参数,一个数据对象。其内的数据项并不是所有情况下都可以使用的,我就是在写 demo 的时候没有意识到这一点,导致"大脑短路"。

这里分为两种情况去解释遇到的问题。第一种情况便是 render 是作为从组件供别的组件来使用的,这种情况下,数据对象中的数据用到的就很少。你完全就可以像写普通组件那样写,只是普通组件中的 template 内容需要通过 render 来实现。

第二种情况便是 render 作为主组件去使用别的组件,h 函数的第二个参数中的大部分就是为了这种情况而准备的。以下通过几个例子展示两种情况下如何实现普通组件的功能。

v-bind

js 复制代码
// v-bind
// 从组件(子组件)
{
  props:["propsData"],
  render(h){
    return h("div",[this.propsData]) // template:`<div>{{ propsData }}</div>`
  }
}
// 主组件(父组件)
import compo from "./xxx.vue"
{
  data(){
    return {
      dataValue:"principal"
    }
  },render(h){
    return h("div",{class:[this.dataValue},[h(compo,{props:{propsData:this.dataValue})])
    // <div class="principal"><compo v-bind:propsData="principal"></compo></div>
  }
}

v-model

js 复制代码
// v-model
// 从组件
export default {
  props:["outerValue"],
  render(h){ // <input :value="outerValue" @input="$emit('input',$event.target.value)">
    return h("input",{
      on:{
        input:(e)=>{
          this.$emit("input",e.target.value);
        }
      },domProps:{ // 此处替换为 attrs 也同样有效。
        value:this.outerValue
      }
    })
  }
}
// 主组件
import compo from './components/some.vue'
export default {
  data() {
    return {
      innerValue: ""
    }
  }, render(h) { 
    // <compo v-bind:outerValue="innerValue" @input="innerValue = $event"></compo>
    // <compo v-model:outerValue="innerValue"></compo>
    return h(compo, {
      props: { outerValue: this.innerValue }, on: {
        input: (value) => {
          this.innerValue = value
        }
      }
    })
  }
}

非组件中双向数据绑定.lazy修饰符,在普通组件中的实现为主组件配置对象中的model配置项。在 render 模式下,使用上面的例子从组件需要将监听事件以及抛出事件更改为 change,父组件监听的从组件事件也需要改为 change。其他修饰符就需要自己在从组件中的事件抛出文件做处理了。

v-model 的例子中我们便遇到了第一个只有在主组件 的情况下才可以使用的配置项nativeOn。主组件若是想要直接监听从组件 的原生事件,而不是抛出的事件,就可以在nativeOn配置项中填入指定的处理函数,其效果就类似于<base-input @focus.native="handler">

从组件 中 input 并不是根元素,那么主组件 的 nativeOn 配置项中的事件监听函数便会失效。解决办法便是原生事件的监听函数不要放到nativeOn而放到on配置项中,然后从组件on配置项中注册这个事件,其值为 this.$listeners.focus

js 复制代码
// 主组件
{
// ...
  return h(compo,{
    on:{
      focus(){}
    }
  })
// ...
}
// 从组件
{
// ...
  return h("div",[ h("input",{ on:{focus:this.$listeners.focus} }) ])
// ...
}

slot

js 复制代码
// 从组件
{
  render(h) {
    let slot_default = this.$slots.default
    let slot_header = this.$slots.header
    return h('div', [slot_default, slot_header])
    // <div><slot name="default"></slot><slot name="header"></slot></div>
}
// 主组件
import compo from './components/some.vue'
export default{
  render(h){
    return h(compo, [
      h("div", { class: "default", slot: 'default' }, ["default"]),
      h("div", { class: "header", slot: 'header' }, ["header"]),
    ])
    // <compo><template>default</template><template v-slot:header></template></compo>
  }
}

需要注意的是,主组件 中渲染从组件 的渲染函数中的子节点指定{slot:"default"}有效,若是子节点下的节点指定了 slot 便不会生效。

js 复制代码
// 主组件
return h(compo,[
  h("div",{slot:"default"},[h("div",{slot:"header"},"header")]),
  // <div>
  //   <div> <-- 该元素位与 default 插槽内。header 插槽内没有内容,因为组件直接子元素下并没有声明为 slot:header 的内容
  //     <div>
  //       header
  //     </div>
  //   </div>
  // </div>
])

scopedSlot

插槽作用域的写法与普通插槽的写法非常相似,slot替换为scopedSlot,插槽名从对象内的属性变为对象内的方法,将数据通过参数传递进去。

主组件的实现规则与 slot 写法中主组件实现一样,也可以通过指定slot:default将组件的直接子元素内容渲染到某个插槽内,只是无法访问到插槽数据。

若要访问到插槽数据,还是需要使用scopedSlot:{default(props){return h()}}这种方式。

js 复制代码
// 从组件
{
  render(h) {
    let scope_slot_default = this.$scopedSlots.default(this.defaultValue)
    let scope_slot_header = this.$scopedSlots.header(this.headerValue)
    return h('div', [scope_slot_default, scope_slot_header])
  }, data() {
    return {
      defaultValue: {slot_name:"default"},
      headerValue: {slot_name:"header"}
    }
  }
}
// 主组件
import compo from './components/some.vue'
export default {
  render(h) {
    return h(compo, {
      scopedSlots: {
        default(props) {
          return h("p", [props.slot_name])
        },
        header(props) {
          return h("p", [props.slot_name])
        }
      }
    })
  }
  // render(h) { 通过 slot 的方式将内容填入了指定的作用域插槽内。
  //   return h(compo, [
  //     h("p"),
  //     h("p",{slot:"header"})
  //   ])
  // }
}

作用域插槽不像普通插槽,从组件中声明了主组件可以不去使用。从组件声明了作用域插槽,主组件必须使用对应的插槽,无论是通过 scopedSlot 还是 slot 的方式。

最后再次提醒各位读者,插槽的内容必须要在主组件渲染从组件时,在其从组件的直接子元素列表中指定。

结语

好记性不如烂笔头,各位读者在学习的时候一定要动手多练,不要抱有侥幸心理也不要对自己的记忆力抱有太大的希望(因人而异,笔者的记忆力不是特别好。)

最后,笔者是一个菜鸟,这是我的第二篇笔记,各位看官若是觉得有用不妨点个赞,让笔者码了一天的字觉得没有白费。点个赞,给笔者一点鼓励,有动力好产出更多文章。

相关推荐
迂 幵20 分钟前
vue el-table 超出隐藏移入弹窗显示
javascript·vue.js·elementui
上趣工作室24 分钟前
vue2在el-dialog打开的时候使该el-dialog中的某个输入框获得焦点方法总结
前端·javascript·vue.js
家里有只小肥猫24 分钟前
el-tree 父节点隐藏
前端·javascript·vue.js
_xaboy2 小时前
开源项目低代码表单设计器FcDesigner扩展自定义的容器组件.例如col
vue.js·低代码·开源·动态表单·formcreate·低代码表单·可视化表单设计器
_xaboy2 小时前
开源项目低代码表单设计器FcDesigner扩展自定义组件
vue.js·低代码·开源·动态表单·formcreate·可视化表单设计器
mez_Blog2 小时前
Vue之插槽(slot)
前端·javascript·vue.js·前端框架·插槽
爱睡D小猪2 小时前
vue文本高亮处理
前端·javascript·vue.js
paopaokaka_luck2 小时前
基于Spring Boot+Vue的多媒体素材管理系统的设计与实现
java·数据库·vue.js·spring boot·后端·算法
开心工作室_kaic2 小时前
ssm102“魅力”繁峙宣传网站的设计与实现+vue(论文+源码)_kaic
前端·javascript·vue.js
放逐者-保持本心,方可放逐2 小时前
vue3 中那些常用 靠copy 的内置函数
前端·javascript·vue.js·前端框架