【vue学习】渲染函数&jsx

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

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

官网简单案例h1~h6

  1. 模板写法


    image
  2. render写法
    image
  3. 调用


    image

Vue-render函数

image

createElement创建虚拟Dom

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

render函数的三个参数

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

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

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


    image

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

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

函数式组件functional

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

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

image

注意:

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

调用组件:

image

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

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算法,再来看这个,后面学习了再进行总结喽...

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 回忆 首先,render函数中手写h=>h(app),new Vue()实例初始化init()和原来一样。$mou...
    LoveBugs_King阅读 2,270评论 1 2
  • 前几天想学学Vue中怎么编写可复用的组件,提到要对Vue的render函数有所了解。可仔细一想,对于Vue的ren...
    kangaroo_v阅读 116,012评论 13 171
  • 探索Vue高阶组件 高阶组件(HOC)是 React 生态系统的常用词汇,React 中代码复用的主要方式就是使用...
    君惜丶阅读 963评论 0 2
  • 1.vue data属性里面的getter和setter data的每个属性都有两个相对应的get和set属性。 ...
    GXW_Lyon阅读 676评论 0 0
  • 回忆 这里我们将对render函数把template转化成vnode的过程进行介绍。 Vue.prototype....
    LoveBugs_King阅读 926评论 0 0