Skip to content

TDesign 中 CompositionAPI 与 TNode 的结合

PY edited this page Jun 19, 2022 · 2 revisions

TNode

首先了解一下 TDesignTNode 的概念, 什么是TNode。在 TDesign 中, 与 TNode 相关的是 renderTNodeJSX,在 compositionAPI 改造过程中增加了 useTNodeJSX

renderTNodeJSX

该函数在 optionsAPI 中使用,与组件相关的上下文会存在于 this 对象,函数参数需要传入 this, TNode name 和拓展参数。

渲染逻辑优先级 function props > (string | number | boolean) props > slots

源代码

const renderTNodeJSX = (instance: ComponentPublicInstance, name: string, options?: OptionsType) => {
  // assemble params && defaultNode
  const params = getParams(options);
  const defaultNode = getDefaultNode(options);

  // 处理 props 类型的Node
  let propsNode;
  if (Object.keys(instance).includes(name)) {
    propsNode = instance[name];
  }

  // 同名插槽和属性同时存在,则提醒用户只需要选择一种方式即可
  if (instance.$slots[name] && propsNode && propsNode !== true) {
    console.warn(`Both $scopedSlots.${name} and $props.${name} exist, $props.${name} is preferred`);
  }

  // propsNode 为 false 不渲染
  if (propsNode === false) return;
  if (propsNode === true && defaultNode) {
    return instance.$slots[name]?.(params) ? instance.$slots[name]?.(params) : defaultNode;
  }

  // 同名 props 和 slot 优先处理 props
  if (isFunction(propsNode)) return propsNode(h, params);
  const isPropsEmpty = [undefined, params, ''].includes(propsNode);
  // Props 为空,但插槽存在
  if (isPropsEmpty && (instance.$slots[camelCase(name)] || instance.$slots[kebabCase(name)])) {
    return handleSlots(instance, params, name);
  }
  return propsNode;
};

使用方式

import { renderTNodeJSX } from '../utils/render-tnode';

defineComponent({
  methods: {
   renderChild() {
     renderTNodeJSX(this, 'default')
   }
  },
  render() {
    <div>
       {this.renderChild()}
       {renderTNodeJSX(this, 'TNodeName', options)}
    </div>
  }
})

useTNodeJSX

本质上这是一个 utils, 不是传统意义上的 hook

以下为初版的 useTNodeJSX 实现, 只能用在 setup 当中,如在 render 函数中使用的话,可使用 renderTNodeJSX

源代码

const useTNodeJSX = (name: string, options?: OptionsType) => {
  // 组装 params 和 defaultNode
  const params = getParams(options);
  const defaultNode = getDefaultNode(options);

  const instance = getCurrentInstance();

  // 处理 props 类型的 TNode
  let propsNode;
  if (Object.keys(instance).includes(name)) {
    propsNode = instance[name];
  }

  // propsNode 为 false 不渲染
  if (propsNode === false) return;

  // 同名function和slot优先处理插槽
  if (instance.slots[name]) {
    return instance.slots[name](params);
  }
  if (isFunction(propsNode)) return propsNode(h, params);

  // propsNode为true则渲染defaultNode
  if (propsNode === true && defaultNode) {
    return defaultNode;
  }
  // 处理字符串类型的 propsNode
  return propsNode;
};

改造后利用闭包,在 render 函数中也可以在执行 TNode 渲染函数的时候能够访问到组件实例。并可以复用 renderTNodeJSX 母方法.

import { renderTNodeJSX } form '../utils/render-tnode';
const useTNodeJSX = () => {
  const instance = getCurrentInstance();
  return function (name: string, options?: OptionsType) {
    // assemble params && defaultNode
    const params = getParams(options);
    const defaultNode = getDefaultNode(options);

    // 处理 props 类型的Node
    let propsNode;
    if (Object.keys(instance.props).includes(name)) {
      propsNode = instance.props[name];
    }
    const slotNode = instance.slots[name];

    // 同名插槽和属性同时存在,则提醒用户只需要选择一种方式即可
    if (slotNode && propsNode && propsNode !== true) {
      log.warn('', `Both slots.${name} and props.${name} exist, props.${name} is preferred`);
    }
    // propsNode 为 false 不渲染
    if (propsNode === false) return;
    if (propsNode === true) {
      return instance.slots[name]?.(params) || defaultNode;
    }

    // 同名 props 和 slot 优先处理 props
    if (isFunction(propsNode)) return propsNode(h, params);
    const isPropsEmpty = [undefined, params, ''].includes(propsNode);
    if (isPropsEmpty && (instance.slots[camelCase(name)] || instance.slots[kebabCase(name)])) {
      return handleSlots(instance, name, params);
    }
    return propsNode;
  };
};

使用方式

import { useTNodeJSX } from '../hooks/tnode';

defineComponent({
  setup() {
    const renderTNode = useTNodeJSX();
    const renderChild = () => {
      return renderTNode('default')
    }
    return {
      renderTNode,
      renderChild
    }
  },
  render() {
    <div>
      {this.renderTNode('TNodeName', options)}
      {this.renderChild()}
    </div>
  }
})