在Vue中使用JSX的正确姿势

ant-design-vue开源了一段时间后,收到了一些反馈,尤其是Form组件上线后,很多用户对JSX的使用感到迷惑和不习惯,为此专门介绍下Vue JSX的使用姿势及注意事项。

Form组件的自动收集校验功能需要在JSX下使用,当然如果不需要自动收集校验,你依然可以使用template

 

Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,就需要使用 render 函数,它比 template 更接近编译器。但是复杂的render函数书写异常痛苦,好在官方提供了一个 Babel 插件,可以将更接近于模板语法的JSX转译成JavaScript。

 

使用过React的同学对JSX肯定不陌生,但是Vue的JSX写法和React的还是有一些区别。

React中父子之间传递的所有数据都是属性,即所有数据均挂载在`props`下(style, className, children, value, onChange等等)。

 

Vue则不然,仅仅属性就有三种:组件属性props,普通html属性attrs,Dom属性domProps

接下来我们通过一个示例来详细解释他们的区别:本文代码可在codesandbox查看运行

const ButtonCounter = {
  name: "button-counter",
  props: ["count"],
  methods: {
    onClick() {
      this.$emit("change", this.count + 1);
    }
  },
  render() {
    return (
      <button onClick={this.onClick}>You clicked me {this.count} times.</button>
    );
  }
};

export default {
  name: "button-counter-container",
  data() {
    return {
      count: 0
    };
  },
  methods: {
    onChange(val) {
      this.count = val;
    }
  },
  render() {
    const { count, onChange } = this;
    return (
      <div>
        <ButtonCounter
          style={{ marginTop: "10px" }}
          count={count}
          type="button"
          onChange={onChange}
        />
        <ButtonCounter
          style={{ marginTop: "10px" }}
          count={count}
          type="button"
          domPropsInnerHTML={`hello ${this.count}.`}
          onChange={onChange}
        />
      </div>
    );
  }
};

 

组件属性props指组件声明的属性,即上述示例中声明的props: ['count']

普通html属性attrs指组件未声明的属性,即上述示例中的type="button",该属性默认会直接挂载到组件根节点的上,如果不需要挂载到根节点,可声明 inheritAttrs: false

Dom属性domProps指的Dom属性,如上述示例中的innerHTML,它会覆盖组件内部的children, 这类属性我们一般很少使用到。

同样事件属性也分了两种:on nativeOn

那么问题来了,组件是如何区分各类属性的呢?

答:正则则则...... ?,babel-plugin-transform-vue-jsx插件会通过正则匹配的方式在编译阶段将书写在组件上属性进行“分类”。

onXXX的均被认为是事件,nativeOnXXX是原生事件,domPropsXXX是Dom属性,

class,staticClass,style,key,ref,refInFor,slot,scopedSlots这些被认为是顶级属性,至于我们属性声明的props,以及html属性attrs,不需要加前缀,插件会将其统一分类到attrs属性下,然后在运行阶段根据是否在props声明来决定属性归属(即属于props还是attrs)。

 

在编译阶段通过正则来区分,毕竟不是很严谨,那么根据以上分类规则会有哪些问题呢?

第一、属性分类是编译阶段进行的分类,那么对于动态属性如何划分分类?

在React中所有属性都是顶级属性,直接使用{...props}就可以了,但是在Vue中,你需要明确该属性所属的分类,如一个动态属性value和事件change,你可以使用如下方式(延展属性)传递:

 

const dynamicProps = {
  props: {},
  on: {},
}
if(haValue) dynamicProps.props.value = value
if(hasChange) dynamicProps.on.change = onChange
<Dynamic {...dynamicProps} />

当然你可以混合着使用:

<Dynamic {...dynamicProps} style="color: red"/>

先别高兴太早,如果你没有深入使用过Vue JSX,不建议你使用混合方式,因为Vue会对其进行属性合并,至于合并的规则官方也并没有详细的文档,文档中有一处示例,我在这再举一个例子:

const dynamicProps2 = { on: { change: onChange2 } };
<Dynamic
  {...{ on: { change: onChange1 } }}
  {...dynamicProps2}
  onChange={onChange3}
/>

上例中的onChange1、onChange2、onChange3都会触发,而你想要的可能仅仅是onChange3。其它属性的合并规则我就不一一列举了,总之,我不建议你使用混合方式,除非你及你的团队其他小伙伴对其规则了解的足够透彻。

注:理想情况你不应该需要动态属性,在业务开发中也比较少的使用动态属性,但如果你尝试开发一些通用性比较强的组件,就很难逃过动态属性的使用。

 

第二、如果声明的属性就是onXXX怎么处理?

首先我不建议你这么做,但如果真的需要,你必须明确该属性的分类,如下所示:

<div>
    <Dynamic value="获取到value,然而并不能获取到onXXX" onXXX="?" />
    <Dynamic {...{ props: { onXXX: "获取到onXXX,但不建议使用" } }} />
</div>

 

第三、函数式组件的props如何处理?

关于函数式组件的相关概念可查看官方文档,文档中有如下一段内容:

注意:在 2.3.0 之前的版本中,如果一个函数式组件想要接受 props,则  props 选项是必须的。在 2.3.0 或以上的版本中,你可以省略  props 选项,所有组件上的属性都会被自动解析为 props。

 

propsattrs之间存在着很微妙的关系,在普通组件中,只要明确声明的属性会被划分到props分类中,剩下的均在attrs中。而对于函数式组件,只要省略了props选项,传参时不管是否明确分类,最终context.props获取到的都是全部属性,如果你需要获取明确的分类情况,可以在context.data下查看。

总之,在函数式组件中,推荐省略props选项。

 

第四、指令是否还可用?

很不幸的告诉你,大多数指令并不能在JSX中使用,对于原生指令,只有v-show是支持的。

大部分指令在JSX中可以使用表达式来替代,如:条件运算符(?:)替代v-ifarray.map替代v-for

对于自定义的指令,可以使用如下方式使用:

const directives = [
  { name: 'my-dir', value: 123, modifiers: { abc: true } }
]

return <div {...{ directives }}/>

 

更多Vue JSX的知识,可以查看官方文档。

 

总结:

说了那么多,其实只要记住一点,尽量使用明确分类的方式传递属性,而不是要babel插件帮你分类及合并属性。

Logo

前往低代码交流专区

更多推荐