Code前端首页关于Code前端联系我们

Vue3 JSX和template哪个好?哪个更适合我现在的项目?

terry 1小时前 阅读数 15 #Vue
文章标签 模板与JSX

很多前端开发者刚接触Vue3的时候,都会在template和JSX之间纠结:template是Vue的“亲儿子”写法,写了这么多年都习惯了;但Vue3官方却给了JSX完整的支持,甚至连生态里的组件库比如Element Plus、Ant Design Vue 3都有不少地方用JSX重构或者实现,还有人说JSX更“自由”、更好复用,那这俩到底该怎么选?今天咱们就掰开揉碎了聊,从写法、能力、性能、生态、适配场景这几个维度说清楚。

Vue3 JSX和template的本质是什么?

要选对工具,先得知道它俩“肚子里装的是什么”,不管你写Vue3的template还是JSX,最终浏览器都看不懂,都得经过编译变成同一个东西——渲染函数(render function),渲染函数的作用就是生成虚拟DOM(VNode),然后Vue3的响应式系统和渲染引擎会接手后面的工作:对比新旧VNode,只更新变化的部分,最后挂载到真实DOM上。

那为什么官方要搞两种写法呢?其实核心是给开发者不同的“自由度”选择:template是“约束型自由”,JSX是“无上限自由”——就像你开车,template是走有导航、有车道线的高速路,省心省力,但不能随便变道、不能在应急车道停车;JSX是走没有明确边界的荒野小路,想去哪就去哪,甚至可以自己开一条路,但需要你自己认路、自己选路线,容错率低一点,但遇到特殊路况会更灵活。

从template的编译过程看约束性

Vue3的template编译器(@vue/compiler-sfc里的template部分),会把你写的模板代码,经过词法分析语法分析代码生成这三步,变成渲染函数,词法分析和语法分析咱们不用细抠,但可以看看它做了哪些“有用的约束”:

  • 会帮你做静态标记:把不会变化的节点(比如纯文本、没有绑定动态属性的img标签)标记出来,Vue3的更新过程中会直接跳过这些节点,性能会有提升;
  • 会帮你做静态提升:把纯静态的VNode或者VNode片段提到渲染函数外面,每次重新渲染的时候直接复用,不用再重新生成;
  • 会帮你做事件缓存:如果你在组件里写了没有依赖当前this上下文的事件处理函数(lt;button @click="handleClick">,handleClick不需要访问this.data),编译器会把这个函数缓存起来,每次重新渲染不会重新创建;
  • 会自动绑定this:在模板里写的表达式,不管是插值{{ }}还是v-bind、v-on,都会自动绑定到当前组件的实例上,你不用特意写this.handleClick或者this.message;
  • 会对指令语法做限制但优化:比如v-for必须加key(开发模式警告,生产模式如果你忘了可能会有问题,但编译器会尽量帮你处理),v-model可以同时绑定多个属性(比如组件的v-model:title和v-model:content),这些语法虽然看起来“死板”,但都是经过大量实践验证的最佳实践,能帮你少踩坑。

从JSX的编译过程看灵活性

Vue3的JSX编译器(@vue/babel-plugin-jsx),会把你写的JSX代码,变成和template编译后结构几乎一样但需要手动写一部分优化逻辑的渲染函数,这里要注意,Vue3的JSX不是React的JSX,它有自己的一套语法糖,和React的区别不小,咱们后面会说。

JSX的本质是JavaScript的语法扩展,所以你可以在JSX里写任何JavaScript代码:循环、条件判断、函数调用、变量赋值、甚至闭包,这些都不用像template那样依赖指令,直接用原生的JS语法就能搞定,比如你要遍历一个数组生成列表,template里得写v-for,JSX里直接用Array.prototype.map就行;比如你要做复杂的条件渲染,template里得嵌套v-if、v-else-if、v-else,JSX里直接用三元运算符、或者逻辑与/或,甚至可以写一个专门的函数来返回不同的VNode。

Vue3 JSX和template的写法对比

光说本质可能有点虚,咱们直接拿几个常见的场景来对比写法,看看哪个更顺手。

场景1:简单的展示型组件

展示型组件的特点是:数据都是从父组件传过来的props,没有或者很少有自己的状态,逻辑很简单。

template写法

<template>
  <div class="user-card">
    <img :src="user.avatar" :alt="user.name" class="user-avatar" />
    <h3 class="user-name">{{ user.name }}</h3>
    <p class="user-bio">{{ user.bio || '暂无简介' }}</p>
    <div class="user-tags">
      <span v-for="tag in user.tags" :key="tag.id" class="user-tag">
        {{ tag.content }}
      </span>
    </div>
  </div>
</template>
<script setup lang="ts">
interface User {
  id: string
  avatar: string
  name: string
  bio?: string
  tags: { id: string; content: string }[]
}
defineProps<{ user: User }>()
</script>
<style scoped>
/* 样式代码省略 */
</style>

JSX写法

// UserCard.tsx
import { defineComponent } from 'vue'
interface User {
  id: string
  avatar: string
  name: string
  bio?: string
  tags: { id: string; content: string }[]
}
export default defineComponent({
  name: 'UserCard',
  props: {
    user: {
      type: Object as () => User,
      required: true
    }
  },
  setup(props) {
    return () => (
      <div class="user-card">
        <img src={props.user.avatar} alt={props.user.name} class="user-avatar" />
        <h3 class="user-name">{props.user.name}</h3>
        <p class="user-bio">{props.user.bio || '暂无简介'}</p>
        <div class="user-tags">
          {props.user.tags.map(tag => (
            <span key={tag.id} class="user-tag">
              {tag.content}
            </span>
          ))}
        </div>
      </div>
    )
  }
})

这个场景下,两种写法的代码量差不多,template的写法更“直观”——因为它和HTML几乎一模一样,新手甚至不用学JavaScript就能看懂大概的结构;而JSX的写法需要写setup函数,需要用箭头函数返回VNode,需要注意props不能省略(因为没有自动绑定this),但TypeScript的支持会更“丝滑”——因为defineProps的类型定义是直接写在props选项里的(或者用setup的参数解构,但不管哪种,都是纯TypeScript语法,不需要像template那样单独定义interface再用泛型)。

场景2:复杂的条件渲染+动态内容

比如我们要做一个“内容卡片”,根据内容的类型(文字、图片、视频、文章链接)显示不同的布局:如果是文字,显示全部内容;如果是图片,显示缩略图,点击弹出大图;如果是视频,显示封面和播放按钮;如果是文章链接,显示标题、描述和来源网站的logo。

template写法

<template>
  <div class="content-card">
    <!-- 文字类型 -->
    <template v-if="content.type === 'text'">
      <p class="text-content">{{ content.data.text }}</p>
    </template>
    <!-- 图片类型 -->
    <template v-else-if="content.type === 'image'">
      <div class="image-wrapper" @click="showImageModal = true">
        <img :src="content.data.thumbnail" :alt="content.data.alt" class="image-thumbnail" />
        <div class="image-count">共{{ content.data.images.length }}张</div>
      </div>
      <!-- 图片弹窗,这里用了Vue3的Teleport组件,假设弹窗组件是全局注册的 -->
      <Teleport to="body">
        <ImageModal
          v-if="showImageModal"
          :images="content.data.images"
          :initial-index="0"
          @close="showImageModal = false"
        />
      </Teleport>
    </template>
    <!-- 视频类型 -->
    <template v-else-if="content.type === 'video'">
      <div class="video-wrapper" @click="playVideo">
        <img :src="content.data.cover" :alt="content.data.title" class="video-cover" />
        <div class="play-button">
          <svg viewBox="0 0 24 24" fill="currentColor">
            <path d="M8 5v14l11-7z"></path>
          </svg>
        </div>
        <div class="video-duration">{{ formatDuration(content.data.duration) }}</div>
      </div>
    </template>
    <!-- 文章链接类型 -->
    <template v-else-if="content.type === 'link'">
      <a :href="content.data.url" target="_blank" rel="noopener noreferrer" class="link-wrapper">
        <div class="link-info">
          <h4 class="link-title">{{ content.data.title }}</h4>
          <p class="link-desc">{{ content.data.desc }}</p>
          <div class="link-source">
            <img :src="content.data.sourceLogo" :alt="content.data.sourceName" class="source-logo" />
            <span class="source-name">{{ content.data.sourceName }}</span>
          </div>
        </div>
        <img v-if="content.data.thumbnail" :src="content.data.thumbnail" :alt="content.data.title" class="link-thumbnail" />
      </a>
    </template>
    <!-- 未知类型 -->
    <template v-else>
      <p class="unknown-content">不支持的内容类型</p>
    </template>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import ImageModal from './ImageModal.vue'
type ContentType = 'text' | 'image' | 'video' | 'link'
interface TextData {
  text: string
}
interface ImageData {
  thumbnail: string
  alt: string
  images: string[]
}
interface VideoData {
  cover: string string
  duration: number
}
interface LinkData {
  url: string string
  desc: string
  sourceLogo: string
  sourceName: string
  thumbnail?: string
}
interface Content {
  type: ContentType
  data: TextData | ImageData | VideoData | LinkData
}
defineProps<{ content: Content }>()
const showImageModal = ref(false)
const playVideo = () => {
  // 这里是播放视频的逻辑,比如调用视频播放器API
  console.log('播放视频')
}
const formatDuration = (seconds: number) => {
  const m = Math.floor(seconds / 60)
  const s = seconds % 60
  return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
}
</script>
<style scoped>
/* 样式代码省略 */
</style>

这个场景下,template的写法已经开始有点“乱”了——嵌套了很多template标签,代码的缩进层次很深,读的时候需要不断往上翻找对应的v-if/v-else-if;而且很多逻辑需要写在script setup里,比如formatDuration、playVideo,和模板代码是分离的,修改的时候需要在两个文件块之间切换(虽然现在的IDE可以拆分窗口,但还是不如在一起方便)。

JSX写法

// ContentCard.tsx
import { defineComponent, ref, Teleport } from 'vue'
import ImageModal from './ImageModal.vue'
type ContentType = 'text' | 'image' | 'video' | 'link'
interface TextData {
  text: string
}
interface ImageData {
  thumbnail: string
  alt: string
  images: string[]
}
interface VideoData {
  cover: string string
  duration: number
}
interface LinkData {
  url: string string
  desc: string
  sourceLogo: string
  sourceName: string
  thumbnail?: string
}
interface Content {
  type: ContentType
  data: TextData | ImageData | VideoData | LinkData
}
export default defineComponent({
  name: 'ContentCard',
  props: {
    content: {
      type: Object as () => Content,
      required: true
    }
  },
  components: { ImageModal },
  setup(props) {
    const showImageModal = ref(false)
    const playVideo = () => {
      console.log('播放视频')
    }
    const formatDuration = (seconds: number) => {
      const m = Math.floor(seconds / 60)
      const s = seconds % 60
      return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
    }
    // 专门写一个渲染内容的函数,逻辑和UI放在一起
    const renderContent = () => {
      const { type, data } = props.content
      switch (type) {
        case 'text':
          // 这里可以直接用类型断言,因为TypeScript会根据type缩小data的类型
          const textData = data as TextData
          return <p class="text-content">{textData.text}</p>
        case 'image':
          const imageData = data as ImageData
          return (
            <>
              <div class="image-wrapper" onClick={() => showImageModal.value = true}>
                <img src={imageData.thumbnail} alt={imageData.alt} class="image-thumbnail" />
                <div class="image-count">共{imageData.images.length}张</div>
              </div>
              <Teleport to="body">
                {showImageModal.value && (
                  <ImageModal
                    images={imageData.images}
                    initialIndex={0}
                    onClose={() => showImageModal.value = false}
                  />
                )}
              </Teleport>
            </>
          )
        case 'video':
          const videoData = data as VideoData
          return (
            <div class="video-wrapper" onClick={playVideo}>
              <img src={videoData.cover} alt={videoData.title} class="video-cover" />
              <div class="play-button">
                <svg viewBox="0 0 24 24" fill="currentColor">
                  <path d="M8 5v14l11-7z"></path>
                </svg>
              </div>
              <div class="video-duration">{formatDuration(videoData.duration)}</div>
            </div>
          )
        case 'link':
          const linkData = data as LinkData
          return (
            <a href={linkData.url} target="_blank" rel="noopener noreferrer" class="link-wrapper">
              <div class="link-info">
                <h4 class="link-title">{linkData.title}</h4>
                <p class="link-desc">{linkData.desc}</p>
                <div class="link-source">
                  <img src={linkData.sourceLogo} alt={linkData.sourceName} class="source-logo" />
                  <span class="source-name">{linkData.sourceName}</span>
                </div>
              </div>
              {linkData.thumbnail && (
                <img src={linkData.thumbnail} alt={linkData.title} class="link-thumbnail" />
              )}
            </a>
          )
        default:
          return <p class="unknown-content">不支持的内容类型</p>
      }
    }
    return () => (
      <div class="content-card">
        {renderContent()}
      </div>
    )
  }
})

这个场景下,JSX的优势就体现出来了:

  • 逻辑和UI完全放在一起:我们写了一个renderContent函数,里面用switch语句处理不同的内容类型,读代码的时候不用在script和template之间切换;
  • TypeScript的类型缩小更自然:因为switch语句是JavaScript原生的,TypeScript可以根据type的值自动缩小data的类型,不需要像template那样(虽然template的TypeScript支持在Vue3.3之后已经好了很多,但还是不如JSX灵活);
  • 没有多余的标签:比如template写法里的template标签,JSX里可以用Fragment(<>...</>)代替,不会生成额外的真实DOM节点;
  • 事件绑定更符合JavaScript习惯:JSX里用onClick、onClose,而不是@click、@close,如果你之前写过React,会觉得非常顺手。

场景3:高阶组件(HOC)和渲染函数组件

高阶组件和渲染函数组件是Vue3里用来复用逻辑和UI的两种高级方式,template在这两种场景下几乎“无能为力”,而JSX则是“如鱼得水”。

渲染函数组件

渲染函数组件是指没有template,直接用setup函数返回VNode的组件(也就是我们刚才写的JSX组件的简化版,如果你不想用defineComponent,也可以直接写一个箭头函数),比如我们要做一个“可复用的加载状态容器”,不管里面包的是什么内容,当loading为true的时候显示加载动画,loading为false的时候显示内容。

用JSX写的渲染函数组件
// WithLoading.tsx
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'WithLoading',
  props: {
    loading: {
      type: Boolean,
      default: false
    },
    loadingText: {
      type: String,
      default: '加载中...'
    }
  },
  setup(props, { slots }) {
    return () => (
      <div class="with-loading">
        {props.loading ? (
          <div class="loading-wrapper">
            <div class="loading-spinner"></div>
            <span class="loading-text">{props.loadingText}</span>
          </div>
        ) : (
          slots.default?.()
        )}
      </div>
    )
  }
})
使用这个组件

不管是用template还是JSX的组件,都可以直接用这个WithLoading组件:

<!-- UserList.vue -->
<template>
  <WithLoading :loading="loading" loading-text="正在获取用户列表...">
    <ul class="user-list">
      <li v-for="user in users" :key="user.id" class="user-item">
        {{ user.name }}
      </li>
    </ul>
  </WithLoading>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import WithLoading from './WithLoading.tsx'
interface User {
  id: string
  name: string
}
const loading = ref(true)
const users = ref<User[]>([])
onMounted(async () => {
  // 模拟获取用户列表的API请求
  await new Promise(resolve => setTimeout(resolve, 2000))
  users.value = [
    { id: '1', name: '张三' },
    { id: '2', name: '李四' },
    { id: '3', name: '王五' }
  ]
  loading.value = false
})
</script>

高阶组件(HOC)

高阶组件是指接受一个组件作为参数,返回一个新组件的函数,它可以用来给原组件添加额外的props、状态、逻辑或者UI,比如我们要给刚才的UserList组件添加“懒加载”功能,当用户滚动到页面底部的时候自动加载更多用户。

如果用template的话,你需要把懒加载的逻辑(监听滚动事件、计算滚动位置、判断是否加载更多)复制到每个需要懒加载的组件里,或者用Vue3的Composition API封装成一个hook,然后在每个组件里引入使用,但如果你有很多组件需要添加完全一样的懒加载UI(比如底部的加载动画、没有更多数据的提示),用hook的话还是需要在每个组件里写重复的UI代码,而用HOC+JSX的话,就可以把逻辑和UI都封装在HOC里,原组件几乎不需要修改。

用JSX写的高阶组件
// withInfiniteScroll.tsx
import { defineComponent, ref, onMounted, onUnmounted, h } from 'vue'
// 定义HOC的参数类型
interface WithInfiniteScrollOptions {
  threshold?: number // 滚动到底部的阈值(像素),默认100px
  loadMore: () => Promise<void> // 加载更多的函数
  hasMore: boolean // 是否还有更多数据
}
// 定义HOC
export function withInfiniteScroll<T extends new (...args: any[]) => any>(
  Component: T,
  options: WithInfiniteScrollOptions
) {
  const { threshold = 100, loadMore, hasMore } = options
  return defineComponent({
    name: `WithInfiniteScroll${(Component as any).name || 'Component'}`,
    setup(props, { attrs, slots }) {
      const loading = ref(false)
      const containerRef = ref<HTMLElement | null>(null)
      // 监听滚动事件
      const handleScroll = async () => {
        if (loading.value || !hasMore) return
        const container = containerRef.value
        if (!container) return
        const { scrollTop, scrollHeight, clientHeight } = container
        if (scrollTop + clientHeight >= scrollHeight - threshold) {
          loading.value = true
          try {
            await loadMore()
          } finally {
            loading.value = false
          }
        }
      }
      onMounted(() => {
        const container = containerRef.value
        if (container) {
          container.addEventListener('scroll', handleScroll, { passive: true })
        }
      })
      onUnmounted(() => {
        const container = containerRef.value
        if (container) {
          container.removeEventListener('scroll', handleScroll)
        }
      })
      return () => (
        <div ref={containerRef} class="infinite-scroll-container" style={{ height: '100%', overflow: 'auto' }}>
          {/* 渲染原组件,把所有的props、attrs、slots都传过去 */}
          <Component {...props} {...attrs} v-slots={slots} />
          {/* 渲染底部的加载状态 */}
          <div class="infinite-scroll-footer">
            {loading.value ? (
              <div class="loading-wrapper">
                <div class="loading-spinner"></div>
                <span class="loading-text">加载更多...</span>
              </div>
            ) : hasMore ? (
              <div class="no-more-tip">上拉加载更多</div>
            ) : (
              <div class="no-more-tip">没有更多数据了</div>
            )}
          </div>
        </div>
      )
    }
  }) as T
}
使用这个高阶组件

我们需要把刚才的UserList.vue改成一个纯展示型的组件(没有自己的loading和获取用户列表的逻辑,因为这些逻辑会被HOC封装):

<!-- PureUserList.vue -->
<template>
  <ul class="user-list">
    <li v-for="user in users" :key="user.id" class="user-item">
      {{ user.name }}
    </li>
  </ul>
</template>
<script setup lang="ts">
interface User {
  id: string
  name: string
}
defineProps<{ users: User[] }>()
</script>

我们在另一个组件里使用withInfiniteScroll HOC:

// UserListContainer.tsx
import { defineComponent, ref } from 'vue'
import { withInfiniteScroll } from './withInfiniteScroll.tsx'
import PureUserList from './PureUserList.vue'
interface User {
  id: string
  name: string
}
// 用HOC包裹PureUserList
const UserListWithInfiniteScroll = withInfiniteScroll(PureUserList, {
  threshold: 50,
  loadMore: async () => {
    // 模拟加载更多的API请求
    await new Promise(resolve => setTimeout(resolve, 1500))
    // 这里假设我们有一个全局的users状态,或者通过props传进来,为了简化示例,我们直接在外面定义
    // 实际项目中,你可以把hasMore、loadMore、users都作为props传给UserListContainer,或者用Pinia/Vuex管理状态
    console.log('加载更多用户')
  },
  hasMore: true // 这里暂时设为true
})
export default defineComponent({
  name: 'UserListContainer',
  setup() {
    const users = ref<User[]>([
      { id: '1', name: '张三' },
      { id: '2', name: '李四' },
      { id: '3', name: '王五' },
      // 初始加载20个用户
      ...Array.from({ length: 17 }, (_, i) => ({ id: `${i + 4}`, name: `用户${i + 4}` }))
    ])
    return () => (
      <div style={{ height: '100vh' }}>
        <UserListWithInfiniteScroll users={users.value} />
      </div>
    )
  }
})

这个场景下,JSX的优势是不可替代的——因为HOC需要动态创建一个新组件,并且在新组件的setup函数里返回VNode,template根本做不到这一点(除非你用Vue.compile API动态编译template,但那样的话性能会很差,而且不利于Tree Shaking)。

Vue3 JSX和template的性能对比

很多开发者关心的另一个问题是:JSX和template的性能哪个更好?

其实在Vue3里,两者的性能差距非常小,几乎可以忽略不计——因为它们最终都会变成渲染函数,而Vue3的响应式系统和渲染引擎是一样的,template编译器会自动做一些优化(静态标记、静态提升、事件缓存),而JSX编译器需要你手动开启这些优化(或者用Vue3.3之后的defineComponent+setup返回箭头函数的方式,编译器会自动做一部分优化)。

如何让JSX的性能和template一样好?

如果你想让JSX的性能达到template的水平,可以做以下几件事:

  1. 使用Vue3.3+版本:Vue3.3之后,@vue/babel-plugin-jsx会自动对setup返回的箭头函数里的静态内容做标记和提升;
  2. 使用@vue/babel-plugin-jsx的优化选项:在babel.config.js或者.babelrc里配置transformOn: true(自动把v-on的数组语法转换成优化后的代码)、cacheHandlers: true(自动缓存事件处理函数)、optimize: true(开启所有优化选项);
  3. 手动做静态提升:如果你有一些非常大的纯静态VNode片段,可以把它提到setup函数外面,这样每次重新渲染的时候直接复用;
  4. 避免在渲染函数里创建不必要的对象和数组:比如不要在return的VNode里写style={{ color: 'red', fontSize: '14px' }},而是把这个对象提到setup函数外面;不要在return的VNode里写[1, 2, 3].map(...),而是如果数组不变的话,提到外面。

Vue3 JSX和template的生态对比

生态也是选择写法的一个重要因素——如果生态里的组件库、工具库、教程文档都偏向某一种写法,那你用这种写法会更省心。

组件库

目前主流的Vue3组件库,比如Element Plus、Ant Design Vue 3、Naive UI,都同时支持template和JSX——你可以用template调用它们的组件,也可以用JSX调用;而且这些组件库的内部实现,都有不少地方用JSX重构或者实现(比如Element Plus的表格组件、Ant Design Vue 3的表单组件),因为JSX更适合处理复杂的UI逻辑。

工具库

Vue3的官方工具库,比如Vue Router、Pinia、VueUse,都完全支持两种写法——不管你用template还是JSX,都可以正常使用它们的API。

教程文档

目前Vue3的官方教程文档,还是以template为主——官方文档里的大部分示例都是用template写的,只有在“渲染函数”和“JSX”这两个章节里才会介绍JSX,随着越来越多的开发者开始使用JSX,第三方的JSX教程文档也越来越多,比如Vue Mastery、B站上的很多Vue3教程,都会专门花一节课讲JSX。

到底该选Vue3 JSX还是template?

说了这么多,终于到了最关键的问题:到底该选哪个?

其实没有“绝对的正确答案”,只有“适合你的项目和团队的答案”——你可以根据以下几个因素来选择:

团队的技术栈和习惯

如果你的团队之前都是写Vue2或者Vue3 template的,而且没有React的经验,那继续用template是更好的选择——因为学习成本低,上手快,团队协作也更顺畅;如果你的团队之前有React的经验,或者有部分成员有React的经验,那可以尝试用JSX——因为React的开发者转到Vue3 JSX几乎没有学习成本,而且可以把React的一些最佳实践(比如HOC、渲染属性、自定义Hook)带到Vue3里。

项目的类型和复杂度

  • 如果是简单的展示型项目(比如企业官网、博客、电商的商品详情页):用template——因为这类项目的逻辑很简单,template的写法更直观,开发效率更高;
  • 如果是复杂的中后台项目(比如CRM、ERP、OA):可以混合使用——展示型的页面用template,复杂的表单、表格、图表组件用JSX;
  • 如果是组件库或者工具库尽量用JSX——因为JSX更灵活,更容易复用,而且TypeScript的支持更好;
  • 如果是需要大量高阶组件或者渲染函数组件的项目必须用JSX——因为template在这两种场景下几乎“无能为力”。

你个人的喜好

也是最重要的一点:你个人的喜好——如果你喜欢“约束型自由”,喜欢写和HTML几乎一模一样的代码,喜欢少踩坑,那选template;如果你喜欢“无上限自由”,喜欢把逻辑和UI放在一起,喜欢用原生的JavaScript语法,那选JSX。

写在最后

不管你选template还是JSX,都是Vue3的“合法公民”——官方不会强制你用某一种写法,而是给你足够的选择权,你也不用一开始就完全切换到某一种写法,可以先在一个小的组件或者页面里尝试一下,看看哪种写法更适合你,然后再慢慢推广到整个项目。

给大家一个小建议:如果你是Vue3的新手,先把template学好——因为template是Vue的核心,学好template之后,你再学JSX会非常容易,因为JSX的很多概念(比如VNode、响应式、插槽)和template是一样的;如果你已经是Vue3的老手,而且有一定的React经验,那可以尝试用JSX——它会给你带来不一样的开发体验。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门