【vue学习】渲染函数&jsx

Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数

先贴官网地址:渲染函数&jsx

官网简单案例h1~h6

  1. 模板写法
  2. render写法
  3. 调用

Vue-render函数

createElement创建虚拟Dom
  1. createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。
  2. h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。
  3. vue核心之虚拟DOM:这块具体内容另外文章另行学习。
render函数的三个参数

  1. 第一个参数:看我下面截图中的Object类型和Function类型,我的环境中报错了;
    了解vue里的Runtime Only和Runtime+Compiler
  2. 第二个参数(可选),配置各种属性/样式/事件,自己看官网吧。有一个说明下:

2.3.0 及以上版本,可以省略 props 选项,所有组件上的特性都会被自动隐式解析为 prop

  1. 第三个参数(可选),多个子标签组成的数组,标签的创建方式可以和父标签一致。这里有个注意点:VNode必须唯一。啥意思呢?看下面代码
  2. 如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了 20 个相同的段落:

模板文件中的v-ifv-forv-modelrender中怎么写

  1. if/for其实很简单,就原生javaScript中的写法喽
  2. v-model就是自定义指令那块内容:
  3. 再贴一个:renderslot的用法
  4. render中的事件修饰符:大部分用原生的呗,如event.stopPropagation();还有个把是在事件前加特有前缀;具体查看官方文档

函数式组件functional

第一个案例中的标题组件是比较简单,没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数。
在这样的场景下,我们可以将组件标记为 functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。

来看个el-button的简单案例,分别是模板写法和render写法

注意:

  1. template中的组件el-button如果是通过components: { elButton }则报错。为啥呢?回头看下函数组件的说明,没有状态,没有实例属性等,当然components是失效的喽。
  2. v-bind="data.attrs" v-on="listeners" 这里可以固定这样写,表示父组件中传过来的属性及事件。

调用组件:

来段官方说明,根据例子自行领会下:

render组件需要的一切都是通过 context 参数传递,它是一个包括如下字段的对象:

  • props:提供所有 prop 的对象
  • children: VNode 子节点的数组
  • slots: 一个函数,返回了包含所有插槽的对象
  • scopedSlots: 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
  • data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
  • parent:对父组件的引用
  • listeners: 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
  • injections: 如果使用了 inject 选项,则该对象包含了应当被注入的属性。

在添加 functional: true 之后,需要更新我们的锚点标题组件的渲染函数,为其增加 context 参数,并将 this.$slots.default 更新为 context.children,然后将 this.level 更新为 context.props.level

element-ui表单的一个二次封装

日期控件:

<template>
    <el-date-picker
        v-model="modelForm[attr.id]" 
        v-bind="attr"
        v-on="$listeners">
    </el-date-picker>
</template>
<script>
export default {
    name: 'LwFormDate',
    props: {
        attr: Object,
        modelForm: Object,
    }
}
</script>

单选控件

<template>
    <el-radio-group 
        v-model="modelForm[attr.id]" 
        v-bind="attr"
        v-on="$listeners">
        <!-- 按钮形式显示 -->
        <div v-if="attr.buttonType">
                <el-radio-button 
                v-for="(e, i) in attr.options" 
                :key="i" 
                :label="e.value" 
                :disabled="e.disabled"> 
                {{ e.label }}
            </el-radio-button>
        </div>
        <div v-else>
            <el-radio 
                v-for="(e, i) in attr.options" 
                :key="i" 
                :label="e.value" 
                :disabled="e.disabled" 
                :border="e.border"> 
                {{ e.label }}
            </el-radio>
        </div>
    </el-radio-group>
</template>
<script>
export default {
    name: 'LwFormRadio',
    props: {
        attr: Object,
        modelForm: Object,
    }
}
</script>

下拉控件

<template>
    <el-select 
        v-model="modelForm[attr.id]" 
        v-bind="attr"
        v-on="$listeners">
            <el-option v-if="attr.includeEmpty" value=""></el-option>
        <!-- 选项分组 groupBy:true -->
        <div v-if="attr.groupBy">
            <el-option-group 
                v-for="(group,i) in attr.groups"
                :key="i" :label="group.label" 
                :disabled="group.disabled">
                <el-option  
                    v-for="(e, j) in group.options" 
                    :key="j" 
                    :label="e.label" 
                    :value="e.value" 
                    :disabled="e.disabled">
                    <!-- option选项自定义模板 -->
                    <div v-if="attr.reRender">{{ optRender(e) }}</div>
                </el-option>
            </el-option-group>
        </div>
        <!-- 选项不分组 -->
        <div v-else>
            <el-option  
                v-for="(e, j) in attr.options" 
                :key="j" 
                :label="e.label" 
                :value="e.value" 
                :disabled="e.disabled">
                <!-- option选项自定义模板 -->
                <div v-if="attr.reRender">{{ optRender(e) }}</div>
            </el-option>
        </div>
    </el-select>
</template>
<script>
export default {
    name: 'LwFormSelect',
    props: {
        attr: Object,
        modelForm: Object,
        optRender: {  //下拉选项重新渲染
            type: Function,
            default(e) {
                return `${e.value}-${e.label}`
            }
        },
    }
}
</script>

打分控件

<template>
    <el-color-picker 
        v-model="modelForm[attr.id]"
	    v-bind="attr"
        v-on="$listeners">
    </el-color-picker>
</template>
<script>
export default {
    name: 'LwFormColor',
    props: {
        attr: Object,
        modelForm: Object,
    }
}
</script>

其他就不一一例举了,都差不多,再来个根据xtype选择哪个控件的render函数

export default {
    name: 'LwFormAitem',
    props:{
        xtype: {
            type: String,
            default: 'input'
        },
        modelForm: Object,
        attr: Object,
    },
    render: function (h) {
        return h(
            'lw-form-' + this.xtype,   // 标签名称
            {
                props:{
                    modelForm: this.modelForm,
                    attr:this.attr
                }
            }
        )
    }
}

再将label名称放到一起

<template>
    <el-form-item 
        :label="attr.label+splitSymbol"
        :prop="attr.id"
        :required="attr.required"
        :rules="attr.rules">
        <lw-form-aitem 
            :attr="attr" 
            :modelForm="modelForm" 
            :xtype="xtype">
        </lw-form-aitem>
    </el-form-item>
</template>
<script>
export default {
    name: 'LwFormBitem',
    props: {
        splitSymbol:{
            type: String,
            default: ':'
        },
        xtype: String,
        attr: Object,
        modelForm: Object, 
    },
}
</script>

最后再来完整的form

<template>
    <el-form 
        :model="modelForm" 
        v-bind="$attrs"
        ref="lwform">
        <lw-form-bitem 
            v-for="(attr, i) in attrs"
            :key="i"
            :attr="attr" 
            :modelForm="modelForm" 
            :xtype="attr.xtype"
            :splitSymbol="splitSymbol">
        </lw-form-bitem>
        <slot name="formBtn"></slot>
    </el-form>
</template>
<script>
export default {
    name: 'LwForm',
    props: {
        splitSymbol:String,  //标签名称后的符号,默认为冒号
        modelForm: Object,   //表单绑定值
        attrs: Array,        //各字段表单控件对应的属性
    },
    methods: {
        validate(callback) {
            this.$refs.lwform.validate(callback);
        },
        resetFields() {
            this.$refs.lwform.resetFields();
        }
    }
}
</script>

使用上述表单的案例:

<template>
    <div style="width:50%">
        <lw-form 
            :modelForm="modelForm" 
            :attrs="attrs" 
            labelWidth="100px"
            ref="formlw">
            <el-form-item slot="formBtn">
                <el-button type="primary" @click="submitForm">提交</el-button>
                <el-button @click="resetForm">重置</el-button>
                <el-button @click="mockTest">mock测试</el-button>
            </el-form-item>
        </lw-form>
    </div>
</template>
<script>
import axios from 'axios'
export default {
    data() {
        return {
            modelForm:{
                name: null,
                age: null,
                sex: null,
                city: null,
                enjoy:[],
                rate: null,
            },
            attrs:[
                {id:"name", label:"姓名", required:true},
                {id:"birth", label:"生日", xtype:'date'},
                {id:"age", label:"年龄", xtype:"number"},
                {id:"sex", label:"性别", xtype:"radio", 
                    options:[{label:"男", value:"1"},{label:"女", value:"2"}]},
                {id:"city", label:"省份", xtype:"radio", 
                    options:[{label:"浙江", value:"zj"},{label:"江苏", value:"js"},{label:"四川", value:"sc"}]},
                {id:"enjoy", label:"爱好", xtype:"checkbox", 
                    options:[{label:"读书", value:"0"},{label:"旅游", value:"1"},{label:"唱歌", value:"2"}]},
                {id:"rate", label:"自我评测", xtype:"rate", allowHalf:true}
            ]
        };
    },
    methods: {
        submitForm() {
            this.$refs.formlw.validate(valid => {
                if (valid) {
                    console.log(this.modelForm)
                    alert("请到控制台查看表单信息!")
                }
            })
        },
        resetForm() {
            this.$refs.formlw.resetFields();
        },
        mockTest() {
            axios.get("/student/list").then(res=>{
                console.log(res)
            })
        }
    }
}
</script>

模板编译

你可能会有兴趣知道,Vue 的模板实际上被编译成了渲染函数。这是一个实现细节,通常不需要关心。但如果你想看看模板的功能具体是怎样被编译的,可能会发现会非常有意思

从这个文件src/platforms/web/entry-runtime-with-compiler.js入口开始学习吧。

先看看VNode和diff算法,再来看这个,后面学习了再进行总结喽…

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值