Loading

9.Render函数

9.1 什么是 Virtrual Dom

Virtual Dom 是基于 JavaScript 计算的,在状态发生变化时,Virtual Dom 会进行 Diff 运算。
img

正常的 DOM 节点在 HTML 中:

<div id="main">
    <p>文本内容</p>
</div>
    <p>文本内容</p>

用 Virtual Dom 创建的 JavaScript 对象一般会是这样的:

var vNode = {
    tag: 'div',
    attributes:{
        id: 'main'
    },
    children:{
        //p 节点
    }
}

Virtual Dom 就是通过一种 VNode 类表达的,每个 DOM 元素或组件都对应一
个 VNode 对象。(关键字: tag,data,childred,text...)

VNode 主要分为如下几类:

  • TextVNode 文本节点
  • ElementVNode 普通元素节点
  • ComponentVNode 组件节点
  • EmptyVNode 没有内容的注释节点
  • CloneVNode 克隆节点,可以是以上任意类型的节点,唯一的区别在于 isCloned 属性为 true 。

多数场景使用 template 就足够,但在一些特定的场景下,使用 Virtual Dom 会更简单。

9.2 什么是 Render 函数

实现锚点标签
  • 使用 template 实现
div#app
    anchor[level="2"][title="特性"]{特性}
    
script[text/x-template][id="anchor"]
    div
        (h$[v-if="level===$"][:id="title"]>a[:href="'#'+title"]>slot )*6

script
    Vue.component("anchor",{
        props:{
            title:{
                type:String,
                default:''
            },
            level:{
                type:Number,
                required:true
            }
        }
    });
    
    var app = new Vue({
        el:'#app'
    })
  • 使用 Render 实现
div#app
    anchor[level="2"][title="特性"]{特性}
script
    Vue.component('anchor',{
        props:{
            level:{
                type:Number,
                required:true
            },
            title:{
                type:String,
                default:''
            }
        },
        render:funciton(createElement){
            return createElement(
                'h'+this.level,
                {
                    attrs:{
                        id:this.title
                    }
                },
                [
                    createElement(
                        'a',
                        {
                            domProps:{
                                href:'#'+this.title
                            }
                        },
                        this.$slots.default
                    )
                ]
            )
        }
    })

9.3 reateElement 用法

9.3.1 基本参数

createElement 构成了 Vue Virtual Dom 的模板,它有 3 个参数:

createElement(
    // {String | Object | Function)
    //一个 HTML 标签,组件选项,或一个函数
    //必须 Return 上述其中一个
    'div',
    
    //{Object}
    // 一个对应属性的数据对象,可选
    //可以在 template 中使用
    {
        //和 v-bind:class 一样的 API
        class:{
            foo : true ,
            bar: false
        },
        //和 v-bind:style 一样的 API
        style : {
            color:'red',
            fontSize:'14px'
        },
        //正常的 HTML 特性
        attrs: {
            id:'foo'
        },
        //组件 props
        props: {
            myProp : 'bar'
        },
        //DOM 属性
        domProps: {
            InnerHTML :'baz'
        },
        //自定义事件监听器"on"
        //不支持如 v-on:keyup.enter 的修饰器
        //需要手动匹配 keyCode
        on: {
            click: this.clickHandler
        },
        //仅对于组件,用于监听原生事件
        //而不是组件使用 vm.$emit 触发的自定义事件
        nativeOn: {
            click: this.nativeClickHandler
        },
        //自定义指令
        directives: [
            name:'my-custom-directive',
            value:'2'
            expression:'1 + 1',
            arg : 'foo',
            modifiers : {
                bar: true
            }
        },
        //作用域 slot
        //{ name: props => VNode | Array<VNode> }
        scopedSlots: {
            default: props => h('span', props text)
        },
        //如果子组件有定义 slot 的名称
        slot:'name-of-slot'
        //其他特殊顶层属性
        key: 'myKey',
        ref: 'myRef'
    },
    
    // {String | Array)
    // 子节点( VNodes ),可选
    [
        createElement ( 'h1','hello world'),
        createElement(MyComponent, {
            props : {
                someProp:'foo'
            }
        }),
        'bar'
    ]
)

第一个参数必选,可以是一个 HTML 标签,也可以是一个组件或函数;第二个是可选参数,数据对象,在 template 中使用。第三个是子节点,也是可选参数,用法一致。

9.3.2 约束

所有组件树中,如果 VNode 是组件或含有组件的 slot ,那么 VNode 必须唯一。

错误的写法:

  1. 重复使用组件
script
    //局部声明组件
    var Child={
        render:function(createElement){
            return createElement('p','text');
        }
    };
    Vue.component('ele',{
        render:function(createElemnt){
            //使用组件 Child, 创建一个子节点
            var ChildNode=createElement(Child);
            return createElement('div',[
                ChildNode,
                ChildNode
            ])
        }
    })
//-----
//改
Array.apply(null,{length:2}).map(function(){
    return createElement(Child)
})
  1. 重复使用含有组件的 slot
<-    div#app>ele>div>Child    ->
//全局注册组件
Vue.component('Child',{
    render:function(createElement{
        return createElement('p','text');
    }
});

Vue.component('ele',{
    render:function(createElement){
        return createElement('div',{
            this.$slots.default,
            this.$slots.default
        })
    }
})

上面两个例子中的两个 Child 组件都只能渲染出一个,VNode受到了约束。

如何重复使用组件,或含有组件的 slot。

代码在文件夹 9.3.2 约束 中

通过一个循环和工厂函数可以渲染5个重复的子组件 Child。 p145
深度克隆子节点

9.3.3 使用 JavaScript 代替模板功能

在 Render 函数中,不能使用 Vue 内置的指令,所以可以用原生的 JavaScript 实现。

  • v-if | v-else
  • v-for
  • map
  • v-model
  • 修饰符
修饰符对应的句柄
.stopevent.stopPropagation()
.preventevent.preventDefault()
.selfif(event.target!==event.currentTarget) return
.enter、.13if(event.keyCode!==13) return 替换 13 位需要的 keyCode
.ctrl、.alt、.shift、.metaif(!event.ctrlKey) return 根据需要替换 ctrlKeyaltKeyshiftKeymetaKey
修饰符特殊的前缀(写在 on 里)
.capture!
.once~
.capture.once.once.capture~!
//写在 on 前
on: {
    '!click': this.doThisInCapturingMode,
    '~keyup': this.doThisOnce,
    '~!mouseover':this.doThisOnceInCapturingMode
}
  • slot

9.4 函数化组件

作用:减少开销渲染
做法:Render 提供两个参数:

  • function参数,值为true,使组件无状态和无实例(没有 data this 上下文),这样 render 函数返回的虚拟节点更容易渲染。
  • context参数,提供临时上下文,组件需要的 datapropsslotschildrenparent 都是通过这个上下文来传递。(比如:this.level => contex.props.level

根据 button 选择不同组件的场景

div#app
    smart-item[:data="data"]
    (button[@click="img|video|text"]{切换为。。}) *3
var ImgItem={
    props:['data'],
    render:function(createElement){
        return createElement('div',[
            createElement('p','图片组件'),
            createElement('img',{
                attrs:{
                    src:this.data.url
                }
            })
        ])
    }
};

var VideoItem={
    props:[...],
    render:function(createElement){
        ...
    }
};

var TextItem={
    //这个 data_____  要和 component 的 data_____ 一样
    props:['data_____'],
    render:function(createElement){
        ...
    }
};

Vue.component('smart-item',{
    functional:true,
    render:function(createElement,context){
        function getComponent(){
            //根据 context.props.data 
            if (...)  return ...Item
        }
        return createElement(){
            getComponent(),
            {
                props:{
                    //把 smart-item 的 prop:data 传给上面只能选择的组件
                    data_____: context.props.data
                }
            },
            context.children
        }
    }
});

var app=Vue({
    el:'#app',
    data:{
        data:{}
    },
    methods:{
        change(type){
            type==>data:{type:'...',url|content:'...'}
        }
    }
})

app定义了data=>传给了组件smart-item=>三个对象都接收一个 prop: data

函数化组件不常用,上例可以用组件的 is 特性来动态挂载。函数化组件主要适用于以下两个场景:

  • 程序化的在多个组件中选择一个
  • 在将 children,props,data 传递给子组件之前操作它们

9.5 JavaScript 的语法扩展:JSX

在模板比较简单时使用 Render 函数写起来会比较麻烦。解决:提供 插件babel-plugin-transroe-vue-jsx来支持 JSX 语法。

比较:

  • template
    <Anchor :level="1">
        <span>一级</span>标题
    </Anchor>
  • Render -> createElement 改写
    return createElement('Anchor',{
        props:{
            level:1
        }
    },[
        createElement('span','一级'),
        '标题'
    ])
  • Render -> 使用 JSX 语法
//  需要在 webpack 里配置插件 bebel-plugin-tranform-vue-jsx 编译
//  参数 h 不能省略,否则使用时会触发错误
    new Vue({
        el:'#app',
        render(h){
            return (
                <Anchor level={1}>
                    <span>一级</span>标题
                </Anchor>
            )
        }
    })
  • 使用 createElement 常用配置
    • 正常实现
    render(createElement){
        return createElement('div',{
            props:{
                text:'some text'
            },
            attrs:{
                is:'myDiv'
            },
            domProps:{
                innerHTML:'content'
            },
            on:{
                change:this.changeHandler
            },
            nativeOn:{
                click:this.clickHandler
            },
            class:{
                show:true,
                on:false
            },
            style:{
                color:'#fff',
                background:'#f50'
            },
            key:'key',
            ref:'element',
            refInFor:true,
            slot:'slot'
        })
    }
    
    • 使用 JSX
    render(h){
        return (
          <div
              id="myDiv"
              domPropsInnerHTML="content"
              onChange={this.clickHandler}
              nativeOnClick={this.clickHandler}
              class={{show:true,on:false}}
              style={{color:'#fff',background:'#f50'}}
              key="key"
              ref="element"
              refInFor
              slot="slot">
          </div>
        )
    }
    
    • 注意:如果项目不是 JSX 强驱动,建议还是以模板 template 的方式为主,特殊场景(比如锚点标题)使用 Render 的 createElement 辅助。

9.6 实战:使用 Render 函数开发可排序的表格组件。

posted @ 2025-03-12 22:35  一起滚月球  阅读(19)  评论(0)    收藏  举报