Repository: Tencent/tdesign-mobile-vue
Pull request #1313 (2024-04-17 06:06:26)

refactor(button): sfc to tsx
<!--
首先，感谢你的贡献！😄
请阅读并遵循 [TDesign 贡献指南](https://github.com/Tencent/tdesign/blob/main/docs/contributing.md)，填写以下 pull request 的信息。
PR 在维护者审核通过后会合并，谢谢！
-->

### 🤔 这个 PR 的性质是？

- [ ] 日常 bug 修复
- [ ] 新特性提交
- [ ] 文档改进
- [ ] 演示代码改进
- [ ] 组件样式/交互改进
- [ ] CI/CD 改进
- [x] 重构
- [ ] 代码风格优化
- [ ] 测试用例
- [ ] 分支合并
- [ ] 其他

### 🔗 相关 Issue

<!--
1. 描述相关需求的来源，如相关的 issue 讨论链接。
-->

### 💡 需求背景和解决方案

<!--
1. 要解决的具体问题。
2. 列出最终的 API 实现和用法。
3. 涉及UI/交互变动需要有截图或 GIF。
-->

### 📝 更新日志

<!--
从用户角度描述具体变化，以及可能的 breaking change 和其他风险。
-->

- feat(button): `sfc` 改 `tsx`

- [ ] 本条 PR 不需要纳入 Changelog

### ☑️ 请求合并前的自查清单

⚠️ 请自检并全部**勾选全部选项**。⚠️

- [ ] 文档已补充或无须补充
- [ ] 代码演示已提供或无须提供
- [ ] TypeScript 定义已补充或无须补充
- [ ] Changelog 已提供或无须提供


github-actions[bot]: [<img height="96" alt="失败" src="https://user-images.githubusercontent.com/5378891/75333447-1e63a280-58c1-11ea-975d-235367fd1522.png">](https://github.com/Tencent/tdesign-mobile-vue/actions/runs/8717152228)
<!-- AUTO_PREVIEW_HOOK -->
github-actions[bot]: **Size Change:** -1.1 kB (0%) 

**Total Size:** 438 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 234 kB | -789 B (0%) |
| `./dist/tdesign.min.js` | 137 kB | -309 B (0%) |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `./dist/reset.css` | 672 B |
| `./dist/tdesign.css` | 38.4 kB |
| `./dist/tdesign.min.css` | 28 kB |

</details>


github-actions[bot]: **Size Change:** -1.1 kB (0%) 

**Total Size:** 438 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `./dist/tdesign.js` | 234 kB | -789 B (0%) |
| `./dist/tdesign.min.js` | 137 kB | -309 B (0%) |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `./dist/reset.css` | 672 B |
| `./dist/tdesign.css` | 38.4 kB |
| `./dist/tdesign.min.css` | 28 kB |

</details>



Patch:
diff --git a/src/button/button.en-US.md b/src/button/button.en-US.md
index 00adaf2d..5f5998ec 100644
--- a/src/button/button.en-US.md
+++ b/src/button/button.en-US.md
@@ -1,6 +1,7 @@
 :: BASE_DOC ::
 
 ## API
+
 ### Button Props
 
 name | type | default | description | required
@@ -12,12 +13,12 @@ ghost | Boolean | false | make background-color to be transparent | N
 icon | Slot / Function | - | use it to set left icon in button。Typescript：`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
 loading | Boolean | false | set button to be loading state | N
 loadingProps | Object | - | Typescript：`LoadingProps`，[Loading API Documents](./loading?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/button/type.ts) | N
-shape | String | rectangle | button shape。options：rectangle/square/round/circle | N
-size | String | medium | a button has three size。options：extra-small/small/medium/large。Typescript：`SizeEnum`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
+shape | String | rectangle | button shape。options: rectangle/square/round/circle | N
+size | String | medium | a button has four size。options: extra-small/small/medium/large | N
 suffix | Slot / Function | - | Typescript：`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
-theme | String | default | button theme。options：default/primary/danger/light | N
-type | String | button | type of button element in html。options：submit/reset/button | N
-variant | String | base | button variant。options：base/outline/text | N
+theme | String | default | button theme。options: default/primary/danger/light | N
+type | String | button | type of button element in html。options: submit/reset/button | N
+variant | String | base | button variant。options: base/outline/text | N
 onClick | Function |  | Typescript：`(e: MouseEvent) => void`<br/>trigger on click | N
 
 ### Button Events
@@ -26,8 +27,8 @@ name | params | description
 -- | -- | --
 click | `(e: MouseEvent)` | trigger on click
 
+### CSS 变量
 
-### CSS Variables
 The component provides the following CSS variables, which can be used to customize styles.
 Name | Default Value | Description 
 -- | -- | --
@@ -129,4 +130,4 @@ Name | Default Value | Description
 --td-button-small-font-size | @font-size-base | - 
 --td-button-small-height | 32px | - 
 --td-button-small-icon-font-size | 18px | - 
---td-button-small-padding-horizontal | 12px | - 
+--td-button-small-padding-horizontal | 12px | -
\ No newline at end of file
diff --git a/src/button/button.md b/src/button/button.md
index 28081cd9..b2cc02ab 100644
--- a/src/button/button.md
+++ b/src/button/button.md
@@ -1,9 +1,10 @@
 :: BASE_DOC ::
 
 ## API
+
 ### Button Props
 
-名称 | 类型 | 默认值 | 说明 | 必传
+名称 | 类型 | 默认值 | 描述 | 必传
 -- | -- | -- | -- | --
 block | Boolean | false | 是否为块级元素 | N
 content | String / Slot / Function | - | 按钮内容。TS 类型：`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
@@ -13,7 +14,7 @@ icon | Slot / Function | - | 按钮内部图标，可完全自定义。TS 类型
 loading | Boolean | false | 是否显示为加载状态 | N
 loadingProps | Object | - | 透传加载组件全部属性。TS 类型：`LoadingProps`，[Loading API Documents](./loading?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/button/type.ts) | N
 shape | String | rectangle | 按钮形状，有 4 种：长方形、正方形、圆角长方形、圆形。可选项：rectangle/square/round/circle | N
-size | String | medium | 组件尺寸。可选项：extra-small/small/medium/large。TS 类型：`SizeEnum`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
+size | String | medium | 组件尺寸。可选项：extra-small/small/medium/large | N
 suffix | Slot / Function | - | 右侧内容，可用于定义右侧图标。TS 类型：`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
 theme | String | default | 组件风格，依次为品牌色、危险色。可选项：default/primary/danger/light | N
 type | String | button | 按钮类型。可选项：submit/reset/button | N
@@ -26,8 +27,8 @@ onClick | Function |  | TS 类型：`(e: MouseEvent) => void`<br/>点击时触
 -- | -- | --
 click | `(e: MouseEvent)` | 点击时触发
 
-
 ### CSS 变量
+
 组件提供了下列 CSS 变量，可用于自定义样式。
 名称 | 默认值 | 描述 
 -- | -- | --
@@ -129,4 +130,4 @@ click | `(e: MouseEvent)` | 点击时触发
 --td-button-small-font-size | @font-size-base | - 
 --td-button-small-height | 32px | - 
 --td-button-small-icon-font-size | 18px | - 
---td-button-small-padding-horizontal | 12px | - 
+--td-button-small-padding-horizontal | 12px | -
\ No newline at end of file
diff --git a/src/button/button.tsx b/src/button/button.tsx
new file mode 100644
index 00000000..b101331f
--- /dev/null
+++ b/src/button/button.tsx
@@ -0,0 +1,80 @@
+import { computed, toRefs, defineComponent, getCurrentInstance } from 'vue';
+
+import { space } from 'postcss/lib/list';
+import TLoading from '../loading';
+import { Hover } from '../shared';
+import ButtonProps from './props';
+import config from '../config';
+import { useFormDisabled } from '../form/hooks';
+import { TdButtonProps } from './type';
+import { usePrefixClass } from '../hooks/useClass';
+import { useContent, useTNodeJSX } from '../hooks/tnode';
+
+const { prefix } = config;
+const name = `${prefix}-button`;
+
+export default defineComponent({
+  name,
+  directives: { Hover },
+  props: ButtonProps,
+  setup(props) {
+    const buttonClass = usePrefixClass('button');
+    const renderTNodeJSX = useTNodeJSX();
+    const renderTNodeContent = useContent();
+    const isDisabled = useFormDisabled();
+
+    const buttonClasses = computed(() => [
+      `${buttonClass.value}`,
+      `${buttonClass.value}--size-${props.size}`,
+      `${buttonClass.value}--${props.variant}`,
+      {
+        [`${buttonClass.value}--${props.theme}`]: props.theme,
+        [`${buttonClass.value}--${props.shape}`]: props.shape,
+        [`${buttonClass.value}--ghost`]: props.ghost,
+        [`${buttonClass.value}--block`]: props.block,
+        [`${buttonClass.value}--disabled`]: isDisabled.value,
+        [`${buttonClass.value}--loading`]: props.loading,
+      },
+    ]);
+
+    const handleClick = (e: MouseEvent) => {
+      if (!props.loading && !isDisabled.value) {
+        props.onClick?.(e);
+      } else {
+        e.stopPropagation();
+      }
+    };
+
+    return () => {
+      const suffix = renderTNodeJSX('suffix');
+
+      const readerIcon = () => {
+        if (props.loading) {
+          return <TLoading inherit-color {...(props.loadingProps as TdButtonProps['loadingProps'])} />;
+        }
+
+        return renderTNodeJSX('icon');
+      };
+
+      const readerContent = () => {
+        const content = renderTNodeContent('default', 'content');
+        return <span class={`${buttonClass.value}__content`}>{content}</span>;
+      };
+      return (
+        <button
+          class={buttonClasses.value}
+          role="button"
+          type={props.type}
+          disabled={props.disabled}
+          aria-disabled={props.disabled}
+          onClick={handleClick}
+          v-hover={{ className: `${buttonClass.value}--hover` }}
+        >
+          {readerIcon()}
+          {readerContent()}
+          {suffix}
+        </button>
+      );
+    };
+  },
+});
diff --git a/src/button/button.vue b/src/button/button.vue
deleted file mode 100644
index 8e2a7178..00000000
--- a/src/button/button.vue
+++ /dev/null
@@ -1,77 +0,0 @@
-<template>
-  <button
-    v-hover="{ className: `${name}--hover` }"
-    :class="buttonClass"
-    :disabled="disabled"
-    :type="type"
-    role="button"
-    :aria-disabled="disabled"
-    @click="onClick"
-  >
-    <t-loading v-if="loading" inherit-color v-bind="loadingProps" />
-    <t-node v-else :content="iconContent" />
-    <span :class="`${name}__content`">
-      <t-node :content="buttonContent" />
-    </span>
-    <t-node :content="suffixContent" />
-  </button>
-</template>
-
-<script lang="ts">
-import { computed, toRefs, defineComponent, getCurrentInstance } from 'vue';
-
-import Loading from '../loading';
-import { renderContent, renderTNode, TNode, Hover } from '../shared';
-import ButtonProps from './props';
-import config from '../config';
-import { useFormDisabled } from '../form/hooks';
-
-const { prefix } = config;
-const name = `${prefix}-button`;
-
-export default defineComponent({
-  name,
-  components: { TNode, TLoading: Loading },
-  directives: { Hover },
-  props: ButtonProps,
-  emits: ['click'],
-  setup(props) {
-    const disabled = useFormDisabled();
-    const internalInstance = getCurrentInstance();
-    const buttonClass = computed(() => [
-      `${name}`,
-      `${name}--size-${props.size}`,
-      `${name}--${props.variant}`,
-      {
-        [`${name}--${props.theme}`]: props.theme,
-        [`${name}--${props.shape}`]: props.shape,
-        [`${name}--ghost`]: props.ghost,
-        [`${name}--block`]: props.block,
-        [`${name}--disabled`]: disabled.value,
-        [`${name}--loading`]: props.loading,
-      },
-    ]);
-    const buttonContent = computed(() => renderContent(internalInstance, 'default', 'content'));
-    const iconContent = computed(() => renderTNode(internalInstance, 'icon'));
-    const suffixContent = computed(() => renderTNode(internalInstance, 'suffix'));
-
-    const onClick = (e: Event) => {
-      if (!props.loading && !disabled.value) {
-        props.onClick?.(e);
-      } else {
-        e.stopPropagation();
-      }
-    };
-    return {
-      name,
-      ...toRefs(props),
-      disabled,
-      buttonContent,
-      iconContent,
-      suffixContent,
-      buttonClass,
-      onClick,
-    };
-  },
-});
-</script>
diff --git a/src/button/index.ts b/src/button/index.ts
index 296f52d2..ca13bbbc 100644
--- a/src/button/index.ts
+++ b/src/button/index.ts
@@ -1,4 +1,4 @@
-import Button from './button.vue';
+import Button from './button';
 import { withInstall, WithInstallType } from '../shared';
 
 import './style';
diff --git a/src/button/type.ts b/src/button/type.ts
index 6e9edfae..2c5e7339 100644
--- a/src/button/type.ts
+++ b/src/button/type.ts
@@ -5,7 +5,7 @@
  * */
 
 import { LoadingProps } from '../loading';
-import { TNode, SizeEnum } from '../common';
+import { TNode } from '../common';
 
 export interface TdButtonProps {
   /**
@@ -49,7 +49,7 @@ export interface TdButtonProps {
    * 组件尺寸
    * @default medium
    */
-  size?: SizeEnum;
+  size?: 'extra-small' | 'small' | 'medium' | 'large';
   /**
    * 右侧内容，可用于定义右侧图标
    */
diff --git a/src/fab/fab.en-US.md b/src/fab/fab.en-US.md
index 1c65d96d..51f1a484 100644
--- a/src/fab/fab.en-US.md
+++ b/src/fab/fab.en-US.md
@@ -6,7 +6,7 @@
 
 name | type | default | description | required
 -- | -- | -- | -- | --
-buttonProps | Object | - | \- | N
+buttonProps | Object | - | Typescript：`ButtonProps`，[Button API Documents](./button?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/fab/type.ts) | N
 icon | Slot / Function | - | Typescript：`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
 style | String | right: 16px; bottom: 32px; | \- | N
 text | String | - | \- | N
@@ -18,9 +18,9 @@ name | params | description
 -- | -- | --
 click | `(context: {e: MouseEvent})` | \-
 
+### CSS 变量
 
-### CSS Variables
 The component provides the following CSS variables, which can be used to customize styles.
 Name | Default Value | Description 
 -- | -- | --
---td-fab-shadow | @shadow-2 | - 
+--td-fab-shadow | @shadow-2 | -
\ No newline at end of file
diff --git a/src/fab/fab.md b/src/fab/fab.md
index 1f7b0dfe..f0d6bd07 100644
--- a/src/fab/fab.md
+++ b/src/fab/fab.md
@@ -1,11 +1,12 @@
 :: BASE_DOC ::
 
 ## API
+
 ### Fab Props
 
-名称 | 类型 | 默认值 | 说明 | 必传
+名称 | 类型 | 默认值 | 描述 | 必传
 -- | -- | -- | -- | --
-buttonProps | Object | - | 透传至 Button 组件 | N
+buttonProps | Object | - | 透传至 Button 组件。TS 类型：`ButtonProps`，[Button API Documents](./button?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-mobile-vue/tree/develop/src/fab/type.ts) | N
 icon | Slot / Function | - | 图标。TS 类型：`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-vue/blob/develop/src/common.ts) | N
 style | String | right: 16px; bottom: 32px; | 悬浮按钮的样式，常用于调整位置 | N
 text | String | - | 文本内容 | N
@@ -17,9 +18,9 @@ onClick | Function |  | TS 类型：`(context: {e: MouseEvent}) => void`<br/>悬
 -- | -- | --
 click | `(context: {e: MouseEvent})` | 悬浮按钮点击事件
 
-
 ### CSS 变量
+
 组件提供了下列 CSS 变量，可用于自定义样式。
 名称 | 默认值 | 描述 
 -- | -- | --
---td-fab-shadow | @shadow-2 | - 
+--td-fab-shadow | @shadow-2 | -
\ No newline at end of file
diff --git a/src/fab/fab.tsx b/src/fab/fab.tsx
new file mode 100644
index 00000000..c79adc13
--- /dev/null
+++ b/src/fab/fab.tsx
@@ -0,0 +1,41 @@
+import { defineComponent } from 'vue';
+import config from '../config';
+import FabProps from './props';
+import { useTNodeJSX } from '../hooks/tnode';
+import { usePrefixClass } from '../hooks/useClass';
+import TButton from '../button';
+import { TdFabProps } from './type';
+
+const { prefix } = config;
+const name = `${prefix}-fab`;
+export default defineComponent({
+  name,
+  props: FabProps,
+  setup(props) {
+    const renderTNodeJSX = useTNodeJSX();
+
+    const fabClass = usePrefixClass('fab');
+
+    const handleClick = (e: MouseEvent) => {
+      props.onClick?.({ e });
+    };
+
+    return () => {
+      const icon = renderTNodeJSX('icon');
+      return (
+        <div class={fabClass.value} style={props.style} onClick={handleClick}>
+          <TButton
+            size="large"
+            theme="primary"
+            shape={props.text ? 'round' : 'circle'}
+            class={`${fabClass.value}__button`}
+            {...(props.buttonProps as TdFabProps['buttonProps'])}
+            icon={icon}
+          >
+            {props.text}
+          </TButton>
+        </div>
+      );
+    };
+  },
+});
diff --git a/src/fab/fab.vue b/src/fab/fab.vue
deleted file mode 100644
index 66df0f34..00000000
--- a/src/fab/fab.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-<template>
-  <div :class="classes" :style="style" @click="onClick">
-    <t-button
-      size="large"
-      theme="primary"
-      :shape="props.text ? 'round' : 'circle'"
-      :class="[`${name}__button`]"
-      v-bind="buttonProps"
-      :icon="() => iconTNode"
-    >
-      {{ text }}
-    </t-button>
-  </div>
-</template>
-
-<script lang="ts">
-import { computed, defineProps, defineEmits, getCurrentInstance } from 'vue';
-
-import { renderTNode } from '../shared';
-import fabProps from './props';
-import config from '../config';
-import TButton from '../button';
-
-const { prefix } = config;
-const name = `${prefix}-fab`;
-
-export default {
-  name,
-};
-</script>
-
-<script lang="ts" setup>
-const props = defineProps(fabProps);
-const emit = defineEmits(['click']);
-
-const internalInstance = getCurrentInstance();
-const iconTNode = computed(() => renderTNode(internalInstance, 'icon'));
-
-const classes = computed(() => ({
-  [`${name}`]: true,
-}));
-
-const onClick = (e: MouseEvent) => props.onClick?.({ e });
-</script>
diff --git a/src/fab/index.ts b/src/fab/index.ts
index 36a94c1c..a7ab429d 100644
--- a/src/fab/index.ts
+++ b/src/fab/index.ts
@@ -1,4 +1,4 @@
-import Fab from './fab.vue';
+import Fab from './fab';
 import { withInstall, WithInstallType } from '../shared';
 
 import './style';
diff --git a/src/fab/type.ts b/src/fab/type.ts
index 17971c53..9fc5565d 100644
--- a/src/fab/type.ts
+++ b/src/fab/type.ts
@@ -3,6 +3,7 @@
 /**
  * 该文件为脚本自动生成文件，请勿随意修改。如需修改请联系 PMC
  * */
+
 import { ButtonProps } from '../button';
 import { TNode } from '../common';
 


Test patch:
diff --git a/src/badge/__test__/index.test.jsx b/src/badge/__test__/index.test.jsx
index b92c2538..ccddbc44 100644
--- a/src/badge/__test__/index.test.jsx
+++ b/src/badge/__test__/index.test.jsx
@@ -1,7 +1,7 @@
 import { mount } from '@vue/test-utils';
 import { describe, it, expect } from 'vitest';
 import Badge from '../badge.vue';
-import Button from '../../button/button.vue';
+import Button from '../../button/button';
 import config from '../../config';
 
 const { prefix } = config;
diff --git a/src/button/__test__/index.test.jsx b/src/button/__test__/index.test.jsx
index eff53606..736a54ea 100644
--- a/src/button/__test__/index.test.jsx
+++ b/src/button/__test__/index.test.jsx
@@ -2,7 +2,7 @@ import { h } from 'vue';
 import { mount } from '@vue/test-utils';
 import { describe, it, expect, vi } from 'vitest';
 import { AppIcon as TIconApp } from 'tdesign-icons-vue-next';
-import Button from '../button.vue';
+import Button from '../button';
 import Loading from '../../loading';
 
 const prefix = 't';
diff --git a/src/dialog/__test__/index.test.jsx b/src/dialog/__test__/index.test.jsx
index be5ce079..862aba72 100644
--- a/src/dialog/__test__/index.test.jsx
+++ b/src/dialog/__test__/index.test.jsx
@@ -1,7 +1,7 @@
 import { config, mount } from '@vue/test-utils';
 import { describe, it, expect, vi } from 'vitest';
 import Dialog from '../dialog.vue';
-import Button from '../../button/button.vue';
+import Button from '../../button/button';
 import { CloseIcon } from 'tdesign-icons-vue-next';
 
 import prefixConfig from '../../config';
diff --git a/src/fab/__test__/index.test.jsx b/src/fab/__test__/index.test.jsx
index 4807e9ab..d635384e 100644
--- a/src/fab/__test__/index.test.jsx
+++ b/src/fab/__test__/index.test.jsx
@@ -1,6 +1,6 @@
 import { mount } from '@vue/test-utils';
 import { describe, it, expect, vi } from 'vitest';
-import Fab from '../fab.vue';
+import Fab from '../fab';
 import { AppIcon as TIconApp, MoreIcon } from 'tdesign-icons-vue-next';
 
 describe('fab', () => {