JSX 与 Template

1,472 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天

关于 Temlate 与 JSX 的选择

观察一些 Vue3 组件库的源码,我们发现,Ant Design Vue 使用了 Options API + jsx/tsx ,在render函数中返回jsx/tsx;Vant 使用了 Composition API +jsx/tsx,在setup函数中返回jsx/tsx;Element-Plus 使用了 Composition API + template。那么到底那个好呢,我们应该选择哪个呢?

Options API 与 Composition API

首先,对比一下Options APIComposition APIComposition API在逻辑复用、类型推导、可测性等方面均占优势。而且 Vue3 都从Options API换成了Composition API,所以如何选择就不用再纠结了。

JSX 和 Template

在 Vue 项目中,大部分场景下是推荐使用template的(尤其是在业务场景下)。Vue 3 基于template分析做了很多优化,并且对使用者是透明的,编译器默默地完成了所有的优化操作,性价比非常高。而使用 JSX 的话,则需要手动进行一些优化操作,比如提取静态的 JSX 片段到render函数外部。

而 Vant 为什么选择 JSX 呢?

因为 Vant 考虑到的是,组件库代码比业务代码具有更强的动态性,使用 JSX 可以非常灵活地控制动态的 DOM 片段。当然还可能受原先组件库的旧包袱影响。

Element 选择 JSX/TSX

Element 的规范与 Vue3 官方推荐是一致的:优先选择template,当template写起来贼费劲时上tsx

“Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。”

template 和 jsx/tsx 的优缺点

Template

  • 优点:可读性高。基于DOM结构,很容易就可以看清楚代码要表达的意图(当然,写的一窝粥的除外);通过 vue 的指令和语法,一眼就能确定逻辑在哪里
  • 缺点:不够灵活。过于笨重SFC享受不到props类型提示;受限于SFC,组件在外部使用时 vscode 无法做出props的类型提示

JSX/TSX

  • 优点:灵活,且可以享受到props的类型提
  • 缺点:享受不到模板编译的优化,且可读性差。编译优化有待提高,太过于灵活容易写岔(推荐使用render函数)
// setup 直接返回一个函数
const Foo = {
  setup() {
    const count = ref(0);
    return () => {
      <div>{count.value}</div>;
    };
  },
};

// render 中实现
const Foo = {
  setup() {
    const count = ref(0);
    return { count };
  },
  render(ctx) {
    // 必须调用 ctx
    return <div>{ctx.count}</div>;
  },
};

React 和 Vue 相关讨论

React 组件的渲染功能都依靠JSXJSX是使用XML语法编写 JavaScript 的一种语法糖,有下面这些优势:

  • 你可以使用完整的编程语言 JavaScript 功能来构建你的视图页面
  • 开发工具对JSX的支持相比于现有可用的其他 Vue 模板还是比较先进的 (比如,linting、类型检查、编辑器的自动完成)

Vue 默认推荐的还是模板。任何合乎规范的HTML都是合法的 Vue 模板,这也带来了一些特有的优势:

  • 习惯了HTML的开发者来说,模板比起JSX读写起来更自然。
  • 基于HTML的模板应用更容易迁移框架
  • 学习成本低
  • 可以使用其他模板预处理器
  • 修饰符带来的便利

到底用啥?

其实到底用啥,并没有硬性规定的,我们更应该关注使用场景!例如,你在做一个有复杂判断的生成的组件,在 js 里写switch会比 template 里做一堆if判断更加友好,做起来更舒服,这个时候就非常适合使用jsx/tsx。而一般的业务逻辑,相比复杂的组件没有那么多的动态性,这时候使用 template 性价比就非常高了。

Render 函数

render 和 template 的区别

相同:render 函数 跟 template 一样都是创建 html 模板

不同:

  • Template 适合逻辑简单,render 适合复杂逻辑。
  • 使用者 template 理解起来相对容易,但灵活性不足;自定义 render 函数灵活性高,但对使用者要求较高。
  • render 的性能较高,template 性能较低。
  • 使用 render 函数渲染没有编译过程,相当于使用者直接将代码给程序。所以,使用它对使用者要求高,且易出现错误
  • Render 函数的优先级要比 template 的级别要高,但是要注意的是 Mustache(双花括号)语法就不能再次使用

先看个例子

<body>
    <div id="myDiv"></div>
</body>
<script>
const app = Vue.createApp({
  template: `
            <my-h>
                huohuo
            </my-h>
       `,
});

app.component("my-h", {
  template: `
            <h1>
                <slot />
            </h1>
        `,
});

const vm = app.mount("#myDiv");
</script>

如果我们需要动态改变标签(有多个)

const app = Vue.createApp({
  data() {
    return {
      myLevel: 3,
    };
  },

  template: `
            <my-h :level="myLevel">
                huohuo
            </my-h>
        `,
});

app.component("my-h", {
  props: ["level"],
  template: `
            <h1 v-if="level===1">
                <slot />
            </h1>
            <h2 v-if="level===2">
                <slot />
            </h2>
            <h3 v-if="level===3">
                <slot />
            </h3>
            <h4 v-if="level===4">
                <slot />
            </h4>
            <h5 v-if="level===5">
                <slot />
            </h5>
        `,
});

使用render函数 简化代码,动态生成 DOM

const app = Vue.createApp({
  data() {
    return {
      myLevel: 6,
    };
  },

  template: `
            <my-h :level="myLevel">
                huohuo
            </my-h>
        `,
});

app.component("my-h", {
  props: ["level"],

  render() {
    const { h } = Vue;
    return h(
      "h" + this.level,
      { name: "myh", id: "myh" },
      this.$slots.default()
    );
  },
});

原先的template改成了使用render方法,方法中通过h函数创建虚拟DOM节点(这个h函数和 Vue2.0 中render方法的参数createElement是类似的)。如果我们使用了JSX,那render方法中更可以使用 JSX的语法来编写虚拟DOM的创建。

但是我们发现这里使用了 this 对象,这跟 Composition API 提倡的函数式做法的理念并不一致。我们可以在 setup 方法中返回这个 render 方法。