zoukankan      html  css  js  c++  java
  • Vue.js 2.x render 渲染函数 & JSX

    Vue.js 2.x render

    渲染函数 & JSX

    Vue绝大多数情况下使用template创建 HTML。但是比如一些重复性比较高的场景,需要运用 JavaScript 的完全编程能力,可以使用render函数。

    1. 节点、树以及虚拟DOM

    每个元素都是一个节点。每片文字也是一个节点。甚至注释也都是节点。
    一个节点就是页面的一个部分。每个节点都可以有子节点。

    比如上面的节点树就表示下面的代码:

    <div>
        <h1>My title</h1>
        Some text content
        <!-- TODO: 添加标签行 -->
    </div>
    

    1.1 虚拟 DOM

    Vue 通过建立一个虚拟 DOM 对真实 DOM 发生的变化保持追踪。
    虚拟 DOM 是对Vue组件树建立起来的整个 VNode(Virtual Node)树的称呼。

    1.1.1 createElement 参数
    // @returns {VNode}
    createElement(
        // {String | Object | Function}
        // 一个 HTML 标签字符串,组件选项对象,或者
        // 解析上述任何一种的一个 async 异步函数
        // 必须参数
        'div',
        
        // {Object}
        // 一个包含模板相关属性的数据对象
        // 你可以在 template 中使用这些特性
        // 可选参数
        { 
            // 属性参见 1.1.2 深入 data 对象
        },
        
        // {String | Array}
        // 子虚拟节点(VNodes) 由 createElement() 构建而成
        // 也可以使用字符串来生成“文本虚拟节点”
        // 可选参数
        [
            '文字',
            createElement('h1', '一个标题'),
            createElement(MyComponent, {
                props: {
                    someProp: 'foo'
                }
            })
        ]
    )
    
    1.1.2 深入 data 对象

    在 VNode 数据对象中,下列属性名是级别最高的字段。该对象也允许绑定普通的 HTML 特性,就像 DOM 属性一样,比如innerHTML(这会取代v-html指令)。

    {
        // 和 v-bind:calss 一样的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
        },
        
        // 自定义指令
        // 注意你无法对 binding 中的 oldValue 赋值
        // 因为 Vue 已经自动进行了同步
        directives: [
            {
                name: 'my-custom-directive',
                value: '2',
                expression: '1+1',
                arg: 'foo',
                modifiers: {
                    bar: true
                }
            }
        ],
        
        // 作用域插槽格式
        // { name: props => VNode | Array<VNode> }
        scopedSlots: {
        default: props => createElement('span', props.text)
        },
        
        // 如果组件时其他组件的子组件 需要为插槽指定名称
        slot: 'name-of-slot',
        
        // 其他特殊顶层属性
        key: 'myKey',
        ref: 'myRef',
        
        // 如果在渲染函数中向多个元素都应用了相同的 ref 名
        // 那么 $refs.myRef 会变成一个数组
        refInfor: true
    }
    
    1.1.3 完整示例

    一个生成锚点标题的组件:

    var getChildrenTextContent = function (children) {
      return children.map(function (node) {
        return node.children
          ? getChildrenTextContent(node.children)
          : node.text
      }).join('')
    }
    
    Vue.component('anchored-heading', {
      render: function (createElement) {
        // 创建 kebab-case 风格的ID
        var headingId = getChildrenTextContent(this.$slots.default)
          .toLowerCase()
          .replace(/\W+/g, '-')
          .replace(/(^-|-$)/g, '')
    
        return createElement(
          'h' + this.level,
          [
            createElement('a', {
              attrs: {
                name: headingId,
                href: '#' + headingId
              }
            }, this.$slots.default)
          ]
        )
      },
      props: {
        level: {
          type: Number,
          required: true
        }
      }
    })
    

    使用效果:

    <div id="app">
        <anchored-heading :level="1">first</anchored-heading>
        <anchored-heading :level="2">second</anchored-heading>
        <anchored-heading :level="3">
            third
            <small>thirdSub</small>
        </anchored-heading>
    </div>
    

    1.1.4 约束

    组件树中的所有 VNodes 必须是唯一的。

    比如这样写是错误的:

    render: function (createElement) {
      var myParagraphVNode = createElement('p', 'hi')
      return createElement('div', [
        // 错误-重复的 VNodes
        myParagraphVNode, myParagraphVNode
      ])
    }
    

    如果需要重复多次元素/组件,可以用工厂函数来实现:

    render: function(createElement) {
        return createElement('div',
            Array.apply(null, { length: 20 }.map(function() {
                return createElement('p', 'hello')
            }))
        );
    }
    

    1.2 使用 JavaScript 代替模板功能

    1.2.1 v-if 和 v-for

    只要在原生的 JavaScript 中可以轻松完成的操作,Vue的render函数就不会提供专有的替代方法。
    比如这里的v-ifv-for

    <ul v-if="items.length">
        <li v-for="item in items">{{ item.name }}</li>
    </ul>
    <p v-else>No items found.</p>
    

    使用 render 方法重写:

    props: ['items'],
    render: function(createElement) {
        if (this.items.length) {
            return createElement('ul', this.items.map(function(item) {
                return createElement('li', item.name);
            }))
        } else {
            return createElement('p', 'No items here.');
        }
    }
    
    1.2.2 v-model

    render 函数中没有与 v-model直接对应的方法,需要手动实现:

    props: ['value'],
    render: function (createElement) {
        var self = this;
        return createElement('input', {
            // DOM属性
            domProps: {
                value: self.value
            },
            on: {
                input: function(e) {
                    self.$emit('input', e.target.value);
                }
            }
        })
    }
    

    1.3 事件&按键修饰符

    对于.passive.capture.once事件修饰符,Vue提供了相应的前缀可以用于on

    Modifier(s) Prefix
    .passive &
    .capture !
    .once ~
    .capture.once.once.capture ~!

    比如:

    on: {
        '!click': this.doThisInCapturingMode,
        '~keyup': this.doThisOnce,
        '~!mouseover': this.doThisOnceInCapturingMode
    }
    

    对于其他的修饰符,可以在事件处理函数中使用事件方法:

    Modifier(s) Equivalent in Handler
    .stop e.stopPropagation()
    .prevent e.preventDefault()
    .self if (e.target !== e.currentTarget) return;
    .enter, .13 e.keyCode === 13

    比如:

    on: {
        keyup: function (event) {
            // 如果触发事件的元素不是事件绑定的元素
            // 则返回
            if (event.target !== event.currentTarget) return
            // 如果按下去的不是 enter 键或者
            // 没有同时按下 shift 键
            // 则返回
            if (!event.shiftKey || event.keyCode !== 13) return
            // 阻止 事件冒泡
            event.stopPropagation()
            // 阻止该元素默认的 keyup 事件
            event.preventDefault()
            // ...
        }
    }
    

    1.4 插槽

    可以通过this.$slots访问静态插槽内容,得到的是一个 VNodes 数组:

    render: function(createElement) {
        // `<div><slot></slot></div>`
        return createElement('div', this.$slots.default)
    }
    

    也可以通过this.$scopeSlots访问作用域插槽,得到的是一个 VNodes 的函数:

    props: ['message'],
    render: function(createElement) {
        // `<div><slot :text="message"></slot></div>`
        return createElement('div', [
            this.$scopedSlots.default({
                text: this.message
            })
        ])
    }
    

    如果要用渲染函数句子向子组件中传递作用域插槽,可以利用 VNode 数据对象中的scopedSlots域:

    render: function(createElement) {
        return createElement('div', [
            createElement('child', {
                // 在数据对象中传递 scopedSlots
                // 格式:{ name: props => VNode | Array<VNode> }
                scopedSlots: {
                    default: function(props) {
                        return createElement('span', props.text)
                    }
                }
            })
        ])
    }
    

    2. JSX

    在Vue中使用JSX语法,可以避免书写繁杂冗余的render代码,Babel插件用于在Vue中使用JSX语法。

    使用文档

    import AnchoredHeading from './AnchoredHeading.vue'
    
    new Vue({
        el: '#app',
        render: function(h) {
            return (
                <AnchoredHeading level={1}>
                    <span>Hello</span> world
                </AnchoredHeading>
            )
        }
    })
    

    3. 函数式组件

    一个函数是组件,意味着它是无状态(没有响应式数据),无实例(没有this上下文)的:

    Vue.component('my-component', {
        functional: true,
        // props可选 v2.3.0^
        props: {},
        // 为了弥补缺少的实例
        // 提供第二个参数作为上下文
        render(createElement, context) {
        
        }
    })
    

    在v2.5.0^版本中,单文件组件基于模板的函数式组件可以这样声明:

    <template functional></template>
    

    组件需要的一切都是通过上下文来传递:

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

    之前的锚点标题组件可以改为如下的函数式组件:

    ...
    
    Vue.component('anchored-heading', {
      functional: true,
      render: function (createElement, context) {
        // 创建 kebab-case 风格的ID
        var headingId = getChildrenTextContent(context.children)
          .toLowerCase()
          .replace(/\W+/g, '-')
          .replace(/(^-|-$)/g, '')
    
        return createElement(
          'h' + context.props.level,
          [
            createElement('a', {
              attrs: {
                name: headingId,
                href: '#' + headingId
              }
            }, context.children)
          ]
        )
      },
      props: {
        level: {
          type: Number,
          required: true
        }
      }
    })
    

    3.1 想子组件或子元素传递特性和事件

    在普通组件中,没有被定义为 prop 的特性会自动添加到组件的根元素上,将现有的同名特性替换或智能合并。
    而函数式组件要求必须显示定义该行为:

    Vue.component('my-functional-button', {
        functional: true,
        render: function(createElement, context) {
            return createElement('button', context.data, context.children)
        }
    })
    

    如果使用模板的函数式组件,还需要手动添加特性和监听器。

    <template functional>
      <button
        class="btn btn-primary"
        v-bind="data.attrs"
        v-on="listeners"
      >
        <slot/>
      </button>
    </template>
    

    3.2 slots()和children对比

    slots()和children对比

    总的来说个人理解,slots()可以拿到具名插槽,children拿到所有。

    4. 模板编译

    模板编译

    The end
    2019-8-13 15:45:59

  • 相关阅读:
    CSS同时选择器
    create-react-app之proxy
    mysql limit语句
    给tbody加垂直滚动条的具体思路
    MySql数据类型范围
    block、inline、inline-block
    javascript sourcemap
    session of express
    React中innerHTML的坑
    box-sizing
  • 原文地址:https://www.cnblogs.com/jimmyguu/p/11346358.html
Copyright © 2011-2022 走看看