Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

渲染函数 #2

Open
zxw018018 opened this issue Aug 16, 2018 · 0 comments
Open

渲染函数 #2

zxw018018 opened this issue Aug 16, 2018 · 0 comments

Comments

@zxw018018
Copy link
Owner

zxw018018 commented Aug 16, 2018

渲染函数

使用

Element UI 提供了渲染表头的函数render-headerrender-header就是一个render function,格式是这样的:

:render-header="render"
render: function(createElement) {
    return createElement(
        'div', // HTML标签字符串或组件名
        {}, // 属性 
        [
            '我是div里的文字', 
            createElement(), 
            createElement(), 
            ...
        ] // String或者Array
    )
}

这个createElement有三个参数:

  1. 第一个参数是HTML标签字符串,比如'div', 'span',也可以是组件的标签名,比如'el-dropdown'
  2. 第二个参数是一个object,包含了这个标签里的所有属性的数据,下面会详细介绍。
  3. 第三个参数可以是String,也可以是Array。 如果是String的话,就是标签里的字符串。比如,你要创建<span>你好</span>,那么这第三个参数就是'你好'。 如果是Array的话,第一项就是标签里的字符串,后几项都是createElement,代表了这个标签里嵌套的标签们。

第二个参数在官方文档里写的很清楚,这里复制过来:

{
    // 和`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
    },
    // 自定义指令。注意,你无法对 `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'
  }

搞明白了这些,就可以开始写渲染函数了。
比如我要实现一个下拉菜单像这样的下拉菜单:
dropdown
在HTML里是这样写的:

<el-dropdown @command="handleCommand">
    <span class="el-dropdown-link">
        城市<i class="el-icon-arrow-down el-icon--right"></i> 
    </span>
    <el-dropdown-menu slot="dropdown">
        <el-dropdown-item command="1">北京</el-dropdown-item> 
        <el-dropdown-item command="2">南京</el-dropdown-item>
    </el-dropdown-menu>
</el-dropdown>

在vue里就可以写成这样:

renderCityName(createElement) {
    return createElement(
        'el-dropdown',
        { on: { command: this.handleCommand }},
        [
            createElement(
                'span',
                { 'class': 'el-dropdown-link' }, 
                '城市',
                createElement(
                    'i',
                    { 'class': 'el-icon-arrow-down el-icon–right' } 
                )
            ),
            createElement(
                'el-dropdown-menu',
                { slot: 'dropdown' },
                [
                    createElement( 
                        'el-dropdown-item',
                        { props: { command: '1' }}, 
                        '北京'
                    ),
                    createElement(
                        'el-dropdown-item',
                        { props: { command: '2' }}, 
                        '南京'
                    ) 
                ]
            ) 
        ]
    ) 
}

写完之后,发现每个dropdown item都要重复写,于是优化了一下,实现了下拉菜单的通用写法:

createDropdownColumnTitle(createElement, spanWord, commandList, handleCommand) {
    let dropdownMenu = []
    for (let key in commandList) {
        dropdownMenu.push(createElement(
            'el-dropdown-item', {
                props: {
                    command: key
                } 
            },
            commandList[key]
        ))
    }
    return createElement(
        'el-dropdown', {
            on: {
                command: handleCommand
            }
        }, [
            createElement(
                'span', {
                    'class': 'el-dropdown-link'
                }, [
                    spanWord,
                    createElement(
                        'i', {
                            'class': "el-icon-arrow-down el-icon--right"
                        }
                    ) 
                ]
            ),
            createElement(
                'el-dropdown-menu', {
                    slot: "dropdown"
                },
                dropdownMenu
            )
        ] 
    )
}

于是,对于以下数据:

data(){
    spanCity: "城市", 
    commandCityList: {
        "1": "北京",
        "2": "南京" 
    }
}
methods: {
    handleCityCommand(command){
        console.log(command)
    } 
}

我可以写出这样的render function:

renderCity(createElement, { column, $index }) {
    return this.createDropdownColumnTitle( createElement, this.spanCity, 
        this.commandCityList,this.handleCityCommand)
}

最后,在表格中绑定这个函数

:render-header="renderCity"

就可以得到开头的那个表头了。

JSX

看到这里,你或许觉得,光实现一个下拉菜单就要写这么多代码,有没有简单的方法呢?
官方文档里介绍了一个Babel插件,用于在Vue中使用JSX语法,写起来就简单多了。
https://github.com/vuejs/babel-plugin-transform-vue-jsx
用法也非常简单,首先

npm install\
  babel-plugin-syntax-jsx\
  babel-plugin-transform-vue-jsx\
  babel-helper-vue-jsx-merge-props\
  babel-preset-env\
  --save-dev

.babelrc中加入:

{
  "presets": ["env"],
  "plugins": ["transform-vue-jsx"]
}

然后你就可以开始使用JSX语法了。要注意的是,这里不能再写createElement了,要写成h
h作为createElement的别名是Vue生态系统中的一个通用惯例,实际上也是JSX所要求的,如果在作用域中h失去作用,在应用中会触发报错。
我们就可以把render function写成这样:

renderCity(h){
    return (
        <el-dropdown onCommand={ this.handleCommand }>
            <span class="el-dropdown-link">
                城市<i class="el-icon-arrow-down el-icon--right"></i> 
            </span>
            <el-dropdown-menu slot="dropdown">
                <el-dropdown-item command='1'>北京</el-dropdown-item>
                <el-dropdown-item command='2'>南京</el-dropdown-item> 
            </el-dropdown-menu>
        </el-dropdown>
    )
}

你一定发现了,这边的属性名有所变化了,比如on:{command:handleCommand}变成了onCommand
文档里是这样写的:

// normal attributes or component props.
 id="foo"
// DOM properties are prefixed with `domProps`
 domPropsInnerHTML="bar"
// event listeners are prefixed with `on` or `nativeOn`
 onClick={this.clickHandler}
 nativeOnClick={this.nativeClickHandler}
// other special top-level properties
 class={{ foo: true, bar: false }}
 style={{ color: 'red', fontSize: '14px' }}
 key="key"
 ref="ref"
// assign the `ref` is used on elements/components with v-for
 refInFor
 slot="slot"

对应render function来看

render function Vue JSX
‘class’: { foo: true, bar: false } class={{ foo: true, bar: false }}
style: { color: 'red', fontSize: '14px' } style={{ color: 'red', fontSize: '14px' }}
attrs: { id: 'foo' } id="foo"
props: { myProp: 'bar'} myProp="bar"
domProps: { innerHTML: 'baz'} domPropsInnerHTML='baz'
on: { click: this.clickHandler } onClick={this.clickHandler}
nativeOn: { click: this.nativeClickHandler } nativeOnClick={this.nativeClickHandler}
slot: 'name-of-slot' slot="name-of-slot"
key: 'myKey' key="myKey"
ref: 'myRef' ref="myRef"

我要把如下通用下拉菜单用JSX写出来:

<el-dropdown @command="handleCityCommand">
    <span class="el-dropdown-link">
        {{spanCity}}<i class="el-icon-arrow-down el-icon--right"></i>
    </span>
    <el-dropdown-menu slot="dropdown">
        <el-dropdown-item v-for="(value, key) in commandCityList" :key="key" :command="key">
            {{value}}
        </el-dropdown-item>
    </el-dropdown-menu>
</el-dropdown>

注意v-for在这边要用array.map()来实现,写出来是这样的:

createDropdownColumnTitle(h, spanWord, commandList, handleCommand) {
    return (
        <el-dropdown onCommand={handleCommand}>
            <span class="el-dropdown-link">
                {spanWord} <i class="el-icon-arrow-down el-icon--right"></i>
            </span>
            <el-dropdown-menu slot="dropdown">
                {Object.keys(commandList).map(key=>
                    <el-dropdown-item command={key}>
                        {commandList[key]}
                    </el-dropdown-item>
                )}
            </el-dropdown-menu>
        </el-dropdown>
    ) 
}
renderCity(h, { column, $index }) {
     return this.createDropdownColumnTitle( h, this.spanCity, this.commandCityList, 
        this.handleCityCommand)
}

用了JSX语法之后,渲染函数就回到了更接近于模板的语法上了。

参考资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant