render函数
在介绍渲染函数之前,我们先看看正常情况下在vue中注册组件应该怎么编写。
Vue.component('MyComponent', {
template: `<div><h1>Hello world!</h1></div>`,
props: {},
data() {
return {}
},
...
})
可以看见在注册时提供了template属性,这里看上去类似于我们常书写的Html代码,但实际上这里这样书写只是方便开发人员习惯,最终书写的模板会按照规则被编译成虚拟DOM,然后在组件被使用后才生成虚拟DOM。
而下面介绍的render函数就比较直观了,它会提供一个方法专门来生成虚拟DOM。
下面我来书写上面代码的render函数替换写法
Vue.component('MyComponent', {
render(createElement) {
return createElement(
'div',
[
createElement('h1', 'Hello world!')
]
)
},
props: {},
data() {
return {}
},
...
})
在上面的代码中我们替换了模板语法的写法,改用render函数提供的createElement方法创建虚拟节点。引用一下官方的描述,我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。
搞清楚createElement的作用后我们接下来介绍它的参数。
createElement参数
因为这里没有使用模板语法,所以vue提供给模板语法使用的v-on,v-for,v-if等等方法需要在该函数中进行实现,具体我们可以查看下面的代码。
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML attribute
attrs: {
id: 'foo'
},
// 组件 prop
props: {
myProp: 'bar'
},
// DOM property
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',
// 其它特殊顶层 property
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
v-for,v-if,v-model实现
props: ['value','items'],
render: function (createElement) {
// 保存this,此处this指向当前vue实例对象
var self = this
// 保存ul虚拟节点
// 使用map 和 三元表达式来实现 v-if 和 v-for
var ul = self.items.length ? createElement('ul',
self.items.map(function(item) {
return createElement('li', item.name)
})) : createElement('p', 'No items found.')
return createElement('div',[
// 创建div中的input虚拟节点
createElement('input', {
domProps: {
// 给input的value属性绑定props中传递的值 与模板语法中v-model相似
value: self.value
},
on: {
input: function (event) {
// 使用vue实例对象中的$emit方法向外传递input事件
self.$emit('input', event.target.value)
}
}
}),
// 添加之前创建的ul虚拟节点
ul
])
}
虽然在功能实现上没有问题,但是当组件变大功能变多后就会导致写起来特别复杂难以维护,所以我们引入了JSX的代替写法。
当我们使用vue-cli创建项目时,该脚手架内置了JSX的Bable插件,我们可以无影响的使用,JSX使我们的写法更加接近于模板语法。下面对jsx的使用以及语法规则进行介绍。
jsx的使用以及语法
先看看jsx与render函数配合使用
Vue.component('MyComponent', {
render(h) {
var lis = items.map((item) =>
<li>
{item.name}
</li>
);
return (
<div>
<input value={this.value} onInput={this.handleInput} />
<ul>{lis}</ul>
</div>
)
},
props: ['value','items'],
data() {
return {}
},
methods: {
handleInput() {}
}
...
})
上面是对之前案例的简写,这下看上去就简洁明了了许多,其中render函数提供的createElement方法被替换成了h,我们来看看官方的解释。
将
h作为createElement的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。从 Vue 的 Babel 插件的 3.4.0 版本开始,我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 自动注入const h = this.$createElement,这样你就可以去掉(h)参数了。对于更早版本的插件,如果h在当前作用域中不可用,应用会抛错。
下面我们介绍下jsx的语法规则
在 JSX 中嵌入表达式
在下面的例子中,我们声明了一个名为 name 的变量,然后在 JSX 中使用它,并将它包裹在大括号中:
const name = 'Josh Perez';const element = <h1>Hello, {name}</h1>;
在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,2 + 2,this.firstName 或 this.formatName(user) 都是有效的 JavaScript 表达式。
在下面的示例中,我们将调用 JavaScript 函数 formatName(user) 的结果,并将结果嵌入到 <h1> 元素中。
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
)
JSX 也是一个表达式
在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。
也就是说,你可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX:
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>
}
JSX 中指定属性
你可以通过使用引号,来将属性值指定为字符串字面量:
const element = <a href="https://www.reactjs.org"> link </a>;
也可以使用大括号,来在属性值中插入一个 JavaScript 表达式:
const element = <img src={user.avatarUrl}></img>;
在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。
JSX 表示对象
Babel 会把 JSX 转译成一个和调用 createElement() 函数相同的虚拟节点。
以下两种示例代码完全等效:
render(createElement) {
const element = (
<h1 class="greeting">
Hello, world!
</h1>
);
const element = createElement(
'h1',
{'class': 'greeting'},
'Hello, world!'
);
}
jsx中绑定方法
在JSX中绑定原生事件和原生Html中有一些区分,需要使用kebab-case或驼峰形式,下面两种形式是一样的效果。
const myDiv = <div onClick={fun}></div>
const myDiv = <div on-click={fun}></div>