一起学习吧

111 阅读5分钟

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 + 2this.firstNamethis.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>