template 和 jsx 用法对比

发布时间 2023-08-21 15:34:53作者: 茶记忆

整体结构

  • jsx 类似vue3中的setup钩子函数?
import { defineComponent, reactive, ref } from 'vue';

export default defineComponent({
  props: {},
  setup: (props, {}) => {
    
    return () => {
      return <></>;
    };
   },
});

或者具名组件

import { defineComponent, reactive, ref } from "vue";

export const DownloadButton = defineComponent({
  props: {},
  setup: (props, { slots }) => {
    return () => {
      return <></>;
    };
  },
});
import { defineComponent, reactive, ref } from "vue";

export const Test = defineComponent((props, { expose }) => {
  return () => {
    return <></>;
  };
});
Test.props = {
  value: String,
  loading: {
    type: Boolean,
    default: false,
  },
};

  • vue (.vue文件和原生html结构类似,vue3.2支持)
<script setup> 
import { reactive, ref } from "vue";
</script>

<template>
  <div></div>
</template>

<style scoped></style>

使用jsx需要进行的配置

首先,devDependencies 中下载 @vitejs/plugin-vue-jsx 插件

"devDependencies": {
    "@vitejs/plugin-vue": "^4.2.3",
    "@vitejs/plugin-vue-jsx": "^3.0.1**",//**下载这个插件
    "vite": "^4.4.6"
  }

其次,vite.config.js 中引入这个插件,放到 plugins

import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx"; //引入这个插件

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), vueJsx()],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});

变量渲染

  • jsx 属性不加冒号,驼峰命名,单花括号进行变量渲染
<SearchTable formColumns={searchOptions.value} onFinish={search} formState={formState} />
  • vue 动态属性前面需要加冒号 双花括号进行变量渲染
<ItemList v-for="(item, index) in state.itemList" :itemData="item" :key="index" >{{item.name}}</ItemList>

循环

  • jsx map循环
import { computed, defineComponent, reactive, ref } from "vue";

export default defineComponent({
  props: {},
  setup: (props, {}) => {
    const arr = [1, 2, 3, 4];
    return () => {
      return (
        <div>
          {arr.map((item) => {
            return <span>{item}</span>;
          })}
        </div>
        或者
         <div>
          {arr.map((item) => (
            <span>{item}</span>
          ))}
        </div>
      );
    };
  },
});
  • vue v-for 指令
<script setup>
import { reactive, ref, computed } from "vue";
const arr = [4, 2, 3, 4];
</script>
<template>
  <div>
    <span v-for="item in arr">{{ item }}</span>
  </div>
</template>

if/else

  • jsx js逻辑运算符 与或
import { computed, defineComponent, reactive, ref } from "vue";

export default defineComponent({
  props: {},
  setup: (props, {}) => {
    const show = false;
    return () => {
      return <div>{show && <span>666</span> || <span>888</span>}</div>;
    };
  },
});
  • vue v-if/v-else 指令
<script setup>
import { reactive, ref, computed } from "vue";
const show = true;
</script>
<template>
  <div>
    <span v-if="show">666</span>
    <span v-else>888</span>
  </div>
</template>

自定义事件写法

  • jsx 采用驼峰onClick
import { defineComponent, reactive, ref } from "vue";

export default defineComponent({
  props: {},
  setup: (props, {}) => {
    const test = ref(1);
    const change = () => {
      test.value = 4;
    };
    return () => {
      return (
        <div>
          <div onClick={change}>about页面</div>
          <div>{test.value}</div>
        </div>
      );
    };
  },
});
  • vue 采用@加事件名
<script setup>
import { reactive, ref } from "vue";
const test = ref(1);
</script>
<template>
  <div
    @click="
      () => {
        test = 4;
      }
    "
  >
    home页面
  </div>
  <div>{{ test }}</div>
</template>
<style scoped></style>

空标签的使用,组件中只有一个父级元素

  • jsx 组件中必须只有一个根元素

export default defineComponent({
  props: {},
  setup: (props, {}) => {
    const test = ref(1);
    const change = () => {
      test.value = 4;
    };
    return () => {
      return (
        <div>
          <div onClick={change}>about页面</div>
          <div>{test.value}</div>
        </div>
        或者采用以下空的闭合标签
        <>
          <div onClick={change}>about页面</div>
          <div>{test.value}</div>
        </>
      );
    };
  },
});
  • vue 支持多个根元素
<template>
  <div>home页面</div>
  <div>{{ test }}</div>
</template>

computed / ref 在模版中的处理

  • jsx 模版中渲染需要.value
export default defineComponent({
  props: {},
  setup: (props, {}) => {
    const test = ref(666);
    const abc = computed(() => {
      return "999";
    });

    return () => {
      return (
        <>
          {test.value} {abc.value}
        </>
      );
    };
  },
});
  • vue 模版中渲染不需要.value
<script setup>
import { reactive, ref, computed } from "vue";
const num = ref(1);
const test = computed(() => {
  return "999";
});
</script>
<template>
  <div>num:{{ num }}</div>
  <div>
    {{ test }}
  </div>
</template>

computed 写法

  • jsx 可以在两个return中间直接写类似于计算属性返回的表达式,这样模版里面使用不需要.value。
import { computed, defineComponent, reactive, ref } from "vue";

export default defineComponent({
  props: {},
  setup: (props, {}) => {
    const abc = ref(999);

    const test = computed(() => {
      return abc.value + 111;
    });

    return () => {
      const test1 = abc.value + 111;
      return (
        <div>
          <div>{test.value}</div>
          <div>{test1}</div>
          <div
            onClick={() => {
              abc.value = 666;
            }}
          >
            点击
          </div>
        </div>
      );
    };
  },
});
  • vue
<script setup>
import { reactive, ref, computed } from "vue";
const test = computed(() => {
  return "999";
});
</script>
<template>
  <div>
    {{ test }}
  </div>
</template>

props传参

  • jsx 通过props.来获取
import { computed, defineComponent, reactive, ref } from "vue";

export const Test = defineComponent({
  props: {
    name: String,
  },
  setup: (props, { emit }) => {
    console.log("props.name", props.name);
    return () => {
      return <div>{props.name}</div>;
    };
  },
});

export default defineComponent({
  props: {},
  setup: (props, { emit }) => {
    return () => {
      return <Test name="tom" />;
    };
  },
});

  • vue 通过 defineProps 编译器宏显示,不需要导入; template中可以直接使用,或者props. 来使用 。setup中必须使用props. 来使用。
<script setup>
import { reactive, ref, useAttrs } from "vue";
const props = defineProps({
  title: {
    type: String,
    default: "",
  },
});
console.log('props.title',props.title)
</script>
<template>
  <div>
    {{ props.title }}<br />
    {{ title }}
  </div>
</template>
<style scoped></style>

emit

  • jsx
import { computed, defineComponent, reactive, ref } from "vue";

export default defineComponent({
  props: {},
  setup: (props, { emit }) => {
    return () => {
      return (
        <div
          onClick={() => {
            emit("close");
          }}
        >
          关闭
        </div>
      );
    };
  },
});
  • vue 通过 defineEmits 编译器宏显示,不需要导入
<script setup>
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}
</script>

expose用法

  • jsx
import { computed, defineComponent, reactive, ref } from "vue";

export default defineComponent({
  props: {},
  setup: (props, { expose }) => {
    expose({
      a: 666,
    });
    return () => {
      return null;
    };
  },
});
  • vue 通过 defineExpose 编译器宏显示,不需要导入
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

attrs用法

  • jsx setup中解构处理使用
import { computed, defineComponent, reactive, ref } from "vue";

export const Test = defineComponent({
  props: {},
  setup: (props, { attrs }) => {
    return () => {
      return (
        <div>
          {attrs.name}
          {attrs.age}
        </div>
      );
    };
  },
});

export default defineComponent({
  props: {},
  setup: (props, {}) => {
    const show = false;
    return () => {
      return <Test name="test" age="18" />;
    };
  },
});
  • vue 使用useAttrs获取attrs
<script setup>
import { reactive, ref, useAttrs } from "vue";
const arrts = useAttrs();
</script>
<template>
  <div>
    {{ arrts.name }}
    {{ arrts.age }}
  </div>
</template>
<style scoped></style>

插槽slot

  • jsx 利用v-slots插入插槽,利用slots.default等接受插槽内容
import { computed, defineComponent, reactive, ref } from "vue";

export const Test = defineComponent({
  props: {
    name: String,
  },
  setup: (props, { slots }) => {
    return () => {
      return (
        <div>
          {slots.default()}
          <div>{slots.tom()}</div>
        </div>
      );
    };
  },
});

export default defineComponent({
  props: {},
  setup: (props, { emit }) => {
    return () => {
      return (
        <Test
          v-slots={{
            default: () => {
              return 888;
            },
            tom: () => {
              return <h1>999</h1>;
            },
          }}
        />
      );
    };
  },
});
  • vue

匿名插槽

<script setup>
import { reactive, ref, computed } from "vue";
import Test from "./Test.vue";
</script>
<template>
  <div>
    <Test title="test1">我是插槽1</Test>
    或者
    <Test title="test1"> 
        <template #default>
            我是插槽1
        </template> 
     </Test>
  </div>
</template>


Test.vue实现如下

<script setup>
import { reactive, ref, useAttrs } from "vue";
</script>
<template>
  <div>
    <slot></slot>
    或者
    <slot name="default"></slot>
  </div>
</template>
<style scoped></style>

具名插槽

<script setup>
import { reactive, ref, computed } from "vue";
import Test from "./Test.vue";
</script>
<template>
  <div>
    <Test title="test1">
      <template #tom> 名字是tom插槽 </template>
    </Test>
  </div>
</template>

Test.vue实现如下

<script setup>
import { reactive, ref, useAttrs } from "vue";
</script>
<template>
  <div>
    <slot name="tom"></slot>
  </div>
</template>
<style scoped></style>

css写法

  • jsx -样式需要使用驼峰来命名,比如fontSize

style写法

import { computed, defineComponent, reactive, ref } from "vue";

export default defineComponent({
  props: {},
  setup: (props, { emit }) => {
    return () => {
      return (
        <div
          style={{
            color: "red",
          }}
        >
          666
        </div>
      );
    };
  },
});

@emotion/css 写法, 需要 npm i @emotion/css -D

import { computed, defineComponent, reactive, ref } from "vue";
import { css } from "@emotion/css";

export default defineComponent({
  props: {},
  setup: (props, { emit }) => {
    return () => {
      return (
        <div
          class={css({
            color: "pink",
          })}
        >
          666
        </div>
      );
    };
  },
});

class或者className的写法 ,需要@emotion/css库支持
注意: Vue 的 JSX 转换方式与 React 中 JSX 的转换方式不同,可以使用 HTML attributes 比如 class 和 for 作为 props - 不需要使用 className 或 htmlFor

import { computed, defineComponent, reactive, ref } from "vue";
import { css } from "@emotion/css";

export default defineComponent({
  props: {},
  setup: (props, { emit }) => {
    return () => {
      return (
        <div
          class={css({
            color: "pink",
            ".test": {
              fontSize: 26,
            },
            ".test1": {
              textDecoration: "underline",
            },
          })}
        >
          666
          <span className="test test1">888</span>
          或者
          <span class="test test1">888</span>
          或者
          <span className={'test1 test'}>888</span>
          或者
          <span class={'test1 test'}>888</span>
        </div>
      );
    };
  },
});
  • vue

style写法

<script setup>
import { reactive, ref, computed } from "vue";
</script>
<template>
  <div style="color: red;">666</div>
</template>

class 写法

<script setup>
import { reactive, ref, computed } from "vue";
</script>
<template>
  <div class="addColor">666</div>
</template>
<style>
.addColor {
  color: blue;
}
</style>

customRender中返回组件处理区别

  • jsx 可以直接写dom或者引入组件
customRender: ({ record, value }) => {
    return (
      <span
        class={css({
          cursor: "pointer",
          ":hover": {
            color: "var(--el-color-primary)",
          },
        })}
        onClick={() => {
          router.push({
            name: "Archives-query-enterprise-detail",
            query: {
              title: record.fentName,
              id: record.fentBusinessCode,
              firstYear: record.freportYear,
              current: 1,
              routerName: "重点排放单位详情",
            },
          });
        }}>
        {value}
      </span>
    );
  },
// 或者类似直接引入组件
import {Stack} from "@/rockontrol/component";
 {
      title: "操作",
      customRender: ({ value }) => {
        return (
          <Stack
            style={{
              color: "var(--el-color-primary)",
              cursor: "pointer",
              justifyContent:'center'
            }}
            onClick={() => {
              router.push({name:"month-report-detail"});
            }}
          >
            查看
          </Stack>
        );
 },
},
  • vue 需要利用h函数来进行处理
import { h } from 'vue'
import KeyEntJump from "./KeyEntJump.vue";
 {
      title: "重点排放单位",
      dataIndex: "fentName",
      fixed: "left",
      width: 300,
      customRender: ({ record }) => {
        return h(KeyEntJump, {
          title: record.fentName,
          id: record.fentBusinessCode,
          firstYear: record.freportYear.slice(0, -1),
        });
      }
},

单个jsx文件支持导出多组件

  • jsx --- 一个jsx文件里面书写多个组件,组件可具名导出(两个组件内容关联紧密有共同导入内容,可以使用这种模式? 增加可读性,减少导入? ),也可供别的组件引用。
import { computed, defineComponent, reactive, ref } from "vue";

export const MyCom = defineComponent({
  props: {},
  setup: (props, {}) => {
    return () => {
      return <>888</>;
    };
  },
});

export default defineComponent({
  props: {},
  setup: (props, {}) => {
    return () => {
      return (
        <div>
          <MyCom />
        </div>
      );
    };
  },
});

当然,如果jsx文件中需要被路由直接引用,必须有默认的export default组件,以下行为不支持

import { computed, defineComponent, reactive, ref } from "vue";

export const MyCom = defineComponent({
  props: {},
  setup: (props, {}) => {
    return () => {
      return <>888</>;
    };
  },
});

export const MyCom1 = defineComponent({
  props: {},
  setup: (props, {}) => {
    return () => {
      return <>999</>;
    };
  },
});
  • vue
    .vue 只支持一个匿名组件的导出,不能写多个组件。
<script setup>
import { reactive, ref, computed } from "vue";
const test = computed(() => {
  return "999";
});
</script>
<template>
  <div>
    {{ test }}
  </div>
</template>

jsx文件支持导出其他变量

  • jsx 可以导出其他的函数方法等内容,比如以下在一个jsx文件中可方便共同使用AMapProvideKey常量,并且导出useAMap。
const AMapProvideKey = Symbol("amap-key");

export const useAMap = () => inject(AMapProvideKey);

export const AMapProvider = defineComponent({
  props: {
    key: String | undefined,
    opts: Object,
    loadOpts: Object,
  },
  setup: (props, context) => {
    //todo:: 待优化
    window._AMapSecurityConfig = {
      securityJsCode: "b4db2d2dc121f0ffb398025c760295cb",
    };

    const divRef = ref();
    const amap = shallowRef();

    provide(AMapProvideKey, amap);
  • vue .vue 文件只能匿名导出组件,其他内容无法导出,只能依靠.js 来处理
import { provide } from "vue";
const AnalysisStr = "analysis$";

export const analysisProvide = (data) => {
  provide(AnalysisStr, data);
};

export const useAnalysisProvide = () => {
  return inject(AnalysisStr);
};

<script setup>
import CarbonEmission from "./components/CarbonEmission";
import EnergyConsumption from "./components/EnergyConsumption";
import dayjs from "dayjs";
import { analysisProvide } from "@/views/analysis/ctx";
const state = reactive({
  type: "1",
  year: dayjs().year() + "",
  componentName: null,
});

analysisProvide(state);

性能对比

官网描述--由于template其确定的语法,更容易对模板做静态分析。这使得 Vue 的模板编译器能够应用许多编译时优化来提升虚拟 DOM 的性能表现。

小结

.vue 优点:
1、熟悉的语法:模板语法基于 HTML,对于那些熟悉 HTML 的开发人员来说更加自然和易懂。
2、更简单的模板:模板语法不需要像 JSX 语法一样用 JavaScript 写模板,因此可以更加简单和易于维护。
3、更好的性能:由于 Vue3 的改进和优化,模板语法的性能比 Vue2 更好,尤其是在渲染大量静态内容时。

.vue 缺点:
1、不够灵活:模板语法只能使用指令和插值表达式来绑定数据和行为,相对来说不太灵活。
2、不够可复用:由于模板语法的结构比较固定,因此相对来说不够可复用。

JSX 的优点:
1、更加灵活:由于 JSX 是使用 JavaScript 编写的,因此可以更加灵活地操作数据和组件。
2、更加可复用:由于 JSX 可以在 JavaScript 中嵌套 HTML 标签和组件,因此可以更加轻松地实现组件的复用。
3、更加直观:相对于模板语法来说,JSX 更加直观和易于理解,特别是对于那些熟悉 JavaScript 的开发人员。

JSX 的缺点:
1、学习成本高:相对于模板语法来说,JSX 的学习成本更高,需要掌握更多的 JavaScript 语法和特性。
2、更容易出错:由于 JSX 是使用 JavaScript 编写的,因此更容易出现拼写错误、逻辑错误等问题。

总的来说,模板语法和 JSX 语法在 Vue3 中都有各自的优缺点和适用情况。模板语法简单易学,性能好,适合开发小型项目;而 JSX 语法灵活可复用,适合开发大型项目。开发人员应该根据实际情况灵活选择使用哪种方式,也可以在两种方式之间切换,以达到最佳的开发效果和用户体验。