终于搞懂了vue 的 render 函数(二)(๑•̀ㅂ•́)و✧


第一篇传送门:终于搞懂了vue 的 render 函数(一) -_-|||


注:本文代码都是在单文件组件中编写。代码地址

先来了解下 vm.$scopedSlots

vm.$scopedSlots

用来访问作用域插槽。对于包括 默认 slot 在内的每一个插槽,该对象都包含一个返回相应 VNode 的函数。

注意: 从 2.6.0 开始,这个 property 有两个变化:

  1. 作用域插槽函数现在保证返回一个 VNode 数组,除非在返回值无效的情况下返回 undefined
  2. 所有的 $slots 现在都会作为函数暴露在 $scopedSlots 中。如果你在使用渲染函数,不论当前插槽是否带有作用域,我们都推荐始终通过 $scopedSlots 访问它们。这不仅仅使得在未来添加作用域变得简单,也可以让你最终轻松迁移到所有插槽都是函数的 Vue 3。

也就是说通过 $scopedSlots 就可以访问所有插槽,只不过原来 $slots 是直接返回 VNode,而 $scopedSlots 是返回一个 VNode 函数(函数的返回值是 VNode)。

⚠️ 修改代码之前先要把 vue 升级到 2.6.0 之后才可以,vue-template-compiler 版本也必须与 vue 版本保持一致。
如果你的项目报这个错 Error: [vue-loader] vue-template-compiler must be installed as a peer depend 那就是它俩的版本不一致导致的。升级命令:

npm update vue vue-template-compiler

上一章的 🌰 只需把 headerdefault 分别改为:

- let _header = _this.$slots.header
+ let _header = _this.$scopedSlots.header()

- _this.$slots.default
+ _this.$scopedSlots.default()

深入数据对象

表格中渲染操作按钮的时候用到的比较多,我们直接看 🌰:

// 按钮组件 BaseButton.vue
<template>
  <div class="button-card">
    <div>
      <slot name="title"></slot>
    </div>
    <button :class="[`${type}`, `${size}`]" @click="handleClick">
      <slot name="button" v-bind:children="contentObj">{{ contentObj.text }}</slot>
    </button>
    <p>
      <slot>help</slot>
    </p>
  </div>
</template>

<script>
export default {
  name: 'BaseButton',
  props: {
    type: {
      type: String,
      default: ''
    },
    size: {
      type: String,
      default: ''
    },
    contentObj: {
      type: Object,
      default() {
        return {}
      }
    }
  },
  methods: {
    handleClick() {
      this.$emit('click')
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.button-card {
  background: #eee;
  border-radius: 8px;
  padding: 24px;
}
button {
  border-radius: 4px;
}
.success {
  background-color: rgb(149, 204, 149);
  border: 1px solid rgb(149, 204, 149);
  color: #fff;
}
.warning {
  background-color: orange;
  border: 1px solid orange;
  color: #fff;
}
.danger {
  background-color: red;
  border: 1px solid red;
  color: #fff;
}
.small {
  height: 20px;
  padding: 0 8px;
}
.middle {
  height: 32px;
  padding: 0 12px;
}
.large {
  height: 40px;
  padding: 0 20px;
}
</style>
// 父组件

// 1. 引入组件
import BaseButton from './BaseButton'
// 2. render 函数中渲染
renderButton: {
  render: function(createElement) {
    const _this = this['$options'].parent
    return createElement(BaseButton, {
      // 与 `v-bind:class` 的 API 相同,接受一个字符串、对象或字符串和对象组成的数组
      class: {
        'base-button': true,
        'ui-button': false
      },
      // 与 `v-bind:style` 的 API 相同,接受一个字符串、对象,或对象组成的数组
      style: {
        // color: 'red',
        fontSize: '14px'
      },
      // 普通的 HTML attribute,会加到 BaseButton 最外层的元素上
      attrs: {
        id: 'base-button'
      },
      // 组件 prop,就是我们正常使用子组件时的那些 props
      props: {
        type: 'warning',
        size: 'large',
        contentObj: {
          text: 'Confirm',
          icon: '❓'
        }
      },
      // DOM property,以子组件最外层元素为父元素,将其内容替换为 `innerHTML` 中的内容
      // domProps: {
      //   innerHTML: 'button 没了,变成这段文字'
      // },
      // 事件监听器在 `on` 内,
	  // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
	  // 需要在处理函数中手动检查 keyCode。
	  // 子组件 `$emit` 的事件才能接收到,否则在下一个属性才能监听到
      on: {
        click: _this.clickHandler,
        dblclick: _this.dblclickHandler
      },
      // 仅用于组件,用于监听原生事件,而不是组件内部使用 `vm.$emit` 触发的事件。
      nativeOn: {
        dblclick: _this.nativeClickHandler
      },
      // 自定义指令。略
      // directives: [],
      // 作用域插槽的格式为:{ name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: props => {
          if (props.children) {
            return createElement('span', props.children.text + ' ?')
          }
          return createElement('span', 'parent slot defult')
        }
      },
      // 如果组件是其它组件的子组件,需为插槽指定名称。这个属性我也没明白
      slot: 'name-of-slot'
      // 其它特殊顶层 property。略
      // key: '',
      // ref: '',
      // refInFor: true
    })
  }
},
// 3. 使用组件 `renderButton`
<renderButton />

JSX

render 函数虽然解决了我们的问题,但实在是太麻烦了。

这就是为什么会有一个 Babel 插件,用于在 Vue 中使用 JSX 语法,它可以让我们回到更接近于模板的语法上。

使用 JSX:

  1. 安装插件
    npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
    
  2. 新建一个 .babelrc 文件(如果你的项目里没有的话),并添加以下配置
    {
      "presets": ["@vue/babel-preset-jsx"]
    }
    

现在我们将上面的组件改成 JSX 的写法:

renderButtonWidthJSX: {
  render(h) {
    const _this = this['$options'].parent
    const contentObj = { text: 'jsx' }

    return (
      <BaseButton
        type="danger"
        size="small"
        contentObj={contentObj}
        style={{ fontSize: '12px' }}
        class="jsx-button"
        onclick={_this.clickHandler}
        scopedSlots={{
          title: () => <h1>jsx title</h1>,
          // button: () => 'Delete',
          default: () => <span>default</span>
        }}
      />
    )
  }
}

也可以这样写:

renderButtonWidthJSX: {
  render(h) {
    const _this = this['$options'].parent
    const contentObj = { text: 'jsx' }
    return (
      <BaseButton
        {...{
          props: {
            type: 'danger',
            size: 'small',
            contentObj,
            style: { fontSize: '12px' },
            class: 'jsx-button'
          },
          on: {
            click: _this.clickHandler,
            dblclick: _this.dblclickHandler
          },
          nativeOn: {
            dblclick: _this.nativeClickHandler
          },
          scopedSlots: {
            title: () => <h1>jsx title</h1>,
            // button: () => 'Delete',
            default: () => <span>default</span>
          }
        }}
      />
    )
  }
}

⚠️ render 函数虽然没有用到 h 参数还是要传,不然会报错:

h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。从 Vue 的 Babel 插件的 3.4.0 版本开始,我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 自动注入 const h = this.$createElement,这样你就可以去掉 (h) 参数了。对于更早版本的插件,如果 h 在当前作用域中不可用,应用会抛错。

  • 13
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值