tiodot/tiodot.github.io

Vue2使用JSX编写组件探索

tiodot opened this issue · 0 comments

在开发vue项目时,需要动态渲染组件,从vue2开始,提供使用jsx编写组件的能力。想着都是jsx,按照之前使用React的jsx开发组件思路来开发vue组件,果然还是有坑。

环境设置

使用jsx开发组件时,需要使用babel-plugin-transform-vue-jsx这个babel插件进行编译,根据babel-plugin-transform-vue-jsx中所说:

  1. 安装依赖包
npm install  babel-plugin-syntax-jsx  --save-dev  // babel对jsx语法支持
npm install  babel-plugin-transform-vue-jsx   --save-dev // 将jsx装换成vue的函数调用
npm install  babel-helper-vue-jsx-merge-props  --save-dev // 对jsx属性进行merge
npm install  babel-preset-es2015   --save-dev // es6语法支持
  1. 修改编译相关.babelrc配置
{
  "presets": ["es2015"],
  "plugins": ["transform-vue-jsx"]
}

基本使用

形式上和React基本一致,用render函数替代template:

new Vue({
  el: '#app',
  render() {
    return <div>hello</div>
  }
});

如果同时存在template,render函数优先:

new Vue({
  el: '#app',
  template: '<div>word</div>',
  render() {
    return <div>hello</div>
  }
});

其结果都是:
image

属性传递

在React中,所有jsx中元素属性都被被放到props属性中,然而在vue中却有些不一样。
有两个组件,父组件传递数据到子组件

// 父组件
import C from './c'
export default {
  name: 'test',
  render() {
    return <C a="foo" b ="bar"/>
  },
  components: {C}
}

然后需要在子组件中打印出ab的值:

// 子组件
export default {
  name: 'c',
  render() {
    console.log(this);
    return <p>{this.$attrs.a}: {this.$attrs.b}</p>;
  }
}

打印出this:
image
可以发现ab都在$attrs中,这个和我们正常写vue组件好像不太一致啊,写vue模板组件时可以直接使用:

<template>
  <p>{{a}}:{{b}}</p>
</template>
<script>
  export default {
    name: 'c',
    props: {
      a: String,
      b: String
    }
  }
</script>

可以发现这里定义了一个props属性,里面包含需要从父组件获取的属性。于是改造一下jsx,发现加上props也是好使的。

// 添加了props的jsx
export default {
  name: 'c',
  render() {
    console.log(this);
    return <p>{this.a}: {this.b}</p>;
  },
  props: {
    a: String,
    b: String
  }
}

打印的this其实也是有变化的:
image

这还没有完,在React中,一般props都会使用...展开一个对象赋值,在vue中使用这个却有些问题:

// 父组件中使用 ... 展开对象形式
import C from './c'
export default {
  name: 'test',
  render() {
    const props = {a: 'foo', b: 'bar'};
    return <C {...props}/> // === <C a="foo" b="bar" /> ?
  },
  components: {C}
}

正常理解这种应该等价于之前的写法,然而结果却是
image
如果用React中的jsx来看这个问题,就觉得很难理解,然而这毕竟是vue。为了解决这个问题,可以先看一下编译之后的render的代码:

function render(createElement) {
    var props = {a: 'foo', b: 'bar'};
    return createElement(C, props, [],);
}

 <C a="foo" b="bar" />

编译之后的是:

function render(createElement) {
    var props = {a: 'foo', b: 'bar'};
    return createElement(C, {attrs: { a: 'foo', b: 'bar' }}, [],);
}

createElement的参数形式为:

createElement('div', data, [children]);

区别就是使用...是相当于直接扩展到data中,而jsx元素属性在babel编译阶段会被收集到data.attrs属性中,所以:

<C {...{attrs: {a: 'foo', b: 'bar'}}}/>  === <C a="foo" b="bar" /> 

当然对应属性也可以使用props,也就是说:

import C from './c.js'
export default {
  name: 'test',
  render() {
    const props = {props: {a: 'foo', b: 'bar'}}; // ===  {attrs: {a: 'foo', b: 'bar'}}
    return <C {...props}/>
  },
  components: {C}
}

效果也是同样的。

总结

vue的jsx和React的jsx在对对象使用...有些区别,vue需要里面再包一层props或者attrs才能正常被子组件获取,其他的可以参考深入-data-对象

参考

babel-plugin-transform-vue-jsx
渲染函数&JSX