-
Notifications
You must be signed in to change notification settings - Fork 495
使用 CompositionAPI 开发 TDesign 组件
PY edited this page Jun 19, 2022
·
7 revisions
可以到 https://github.com/Tencent/tdesign-vue-next/discussions/84 进行讨论,这边做总结。
每个贡献者的代码风格都是不同的,在认同 CompositionAPI
理念的情况下。我们希望能够有较为标准的代码组织结构。让整体的代码保持一个大概的代码块风格,组件的维护会更加清晰,避免代码的堆积。
以 upload
组件为例,代码块应该是分层设计的。第一步先进行合理的组件拆分,拆分的原则:
- 按表现类型拆分出子组件:
upload
组件存在多种表现类型,因此会衍生出4个子组件:Dragger
、ImageCard
、FlowList
、SingleFile
- 按不同的逻辑处理, 内聚出不同的
hook
与表现层代码挂钩:useComponentsStatus
,useImgPreview
,useRemove
,useDragger
,useActions
。
- 组件参数状态,双向绑定语法糖,受控,非受控
- 一个组件内置状态的上下文,集中管理组件内置的状态。散落在各个代码块的内部变量难以维护。这样会很清晰的知道组件的内置状态存在哪些。这一部分的代码可以通过
provider
向子组件注入,也可以使用context
向子组件传递。
- 类似这种简短计算的代码必要性不是很强
const isSingle = computed(() => props.theme === 'single')
import { defineComponent } from 'vue';
import { TdUploadProps } from './type'; // 标准的type文件
import props from './props'; // 标准的props文件
// 子组件
import Dragger from './dragger';
import ImageCard from './image';
import FlowList from './flow-list';
import SingleFile from './single-file';
// hooks
import { useConfig, usePrefixClass, useCommonClassName } from '../hooks/useConfig'; // 全局的config配置, classPrefix, commonClass
import useVModel from '../hooks/useVModel'; // 语法糖与受控处理
import { useLogicHook } from '../hook'; // 纯逻辑代码的hook,按逻辑区分,方便后续的维护
export default defineComponent({
name: "TUpload",
props,
setup(props: TdUploadProps) {
const { classPrefix: prefix, global } = useConfig('upload');
const COMPONENT_NAME = usePrefixClass('upload');
const { STATUS } = useCommonClassName();
const { files, modelValue } = toRefs(props);
// `files` 的更新统一使用 `setUploadValue`
const [uploadValue, setUploadValue] = useVModel(
files,
modelValue,
props.defaultFiles || [],
props.onChange,
);
// 组件上下文,集中管理组件内置的状态
const uploadCtx: UploadCtxType = reactive({
uploadValue,
setUploadValue,
loadingFile: null, // 加载中的文件
toUploadFiles: [], // 等待上传的文件队列
errorMsg: '',
});
// 逻辑层 `hook` 导出表现层需要的变量, 相关的effect函数。
const { logicVar, logicHandler } = useLogicHook(props, uploadCtx)
// 表现层 `render` 函数, 按模块拆分,避免主 `render` 函数内容过多。
const renderContent = () => {
<div class={[COMPONENT_NAME.value, {
STATUS.disabled: props.disabled
}]} onClick={logicHandler}>
{logicVar}
</div>
}
// 可以直接在setup返回render函数,不需要再单独写 `render` 函数。同时 `setup render` 函数里面也有很完整的类型支持。需要对外暴露的方法可以使用 `ctx.expose`
return (
<div>{renderContent()}</div>
)
},
})
为 TNode
的 API
中,需要使用 useTNodeJSX
得到渲染函数进行渲染,函数内会处理好 props
function props
与插槽的关系。
import { renderTNodeJSX } from '../utils/render-tnode';
import { useTNodeJSX } from '../hooks/tnode';
defineComponent({
setup() {
const renderTNode = useTNodeJSX();
const renderChild = () => {
return renderTNode('default')
}
return {
renderTNode,
renderChild
}
},
render() {
<div>
// 两种写法
{this.renderTNode('TNodeName', options)}
{renderTNodeJSX(this, 'TNodeName', options)}
{this.renderChild()}
</div>
}
})
统一使用 useConfig
, useConfig
会导出 global
, classPrefix
, t
。
在很多情况下你可能只需要导出一个带prefix的类名,你可以使用 usePrefixClass
。
const COMPONENT_NAME = usePrefixClass('componentName');
// 也可以只得到一个classPrefix
const classPrefix = usePrefixClass();
commonclass
集合了一些公用的 class
。分为 SIZE
和 STATUS
。
const { SIZE, STATUS } = useCommonClassName()
ConfigReceiverMixins
会被逐渐移除掉。
逐渐放弃使用高阶函数 mapProps
。实现 v-model
使用 useVModel
, 实现 v-model:xx
使用 useDefaultValue
// 用于实现 v-model
const { value, modelValue } = toRefs(props)
const [innerValue, setInnerValue] = useDefaultValue(
value,
modelValue,
props.defaultValue,
props.onChange,
);
// 用于实现 v-model:visible
const { visible } = toRefs(props)
const [innerVisible, setInnerVisible] = useDefaultValue(
visible,
props.defaultVisible,
props.onVisibleChange,
'visible',
);
在 TDesign vue
中事件都会存在 onXXX
的 props
函数,可以通过 props.onXXX
的方法进行处理。
// props
{
onChange?: (...args) => {};
}
// tsx
setup(props) {
props.onChange?.(args)
}
- 合理使用
Provide
与inject
,按需provide
,避免children
调用$parent
这类代码. - 合理的
InjectionKey
import { InjectionKey } from 'vue';
const CheckboxGroupInjectionKey: InjectionKey<{
name: string;
componentProps: string,
xxxProvideProps: string,
}> = Symbol('componentName');