TypeScript:如何使函数具有反应性

Posted

技术标签:

【中文标题】TypeScript:如何使函数具有反应性【英文标题】:TypeScript: How to make a function behave reactively 【发布时间】:2021-11-08 07:34:47 【问题描述】:

我有一个用 TypeScript 编写的函数来显示一个颜色选择器控件,它将返回一个按钮属性。然后,该按钮属性将被传递给一个外部组件,在那里它们处理实际的呈现逻辑。

我的函数在 ts 文件中如下所示:

const getColorPickerControl = (): ButtonProps => 
    ....
    return createButton(
        id: 'colorPicker',
        label: 'color picker',
        onColorCellSelected: (colorItem: colorParam) => 
             .....
             // here I want to update the icon color below based on the selected color, 
             // so I want this function return a new ButtonProps with the updated icon color
        ,
        iconColor: 'red'   // I can only hard-code this color for now
    );

基本上,onColorCellSelected 是在颜色选择器中选择颜色单元格时的单击事件处理程序,因此我想根据当前选择的颜色动态更改图标颜色。

我知道我可以通过将这个组件转换为一个 tsx 文件来使该组件具有反应性/可观察性,我们可以在该文件中自己处理渲染逻辑并使用状态/存储,但鉴于渲染逻辑是由第三方完成的,我想知道是否有办法让函数在选择新颜色时重新完成其工作?

【问题讨论】:

【参考方案1】:

这应该会让你朝着正确的方向前进——使用映射泛型和 React TSX 定义来处理各种 htmlElement。这是“展开”的 Button——所有道具都成为有条件的,并且都可以访问

export type UnwrapButtonProps<
  T extends keyof ButtonHTMLAttributes<HTMLButtonElement>
> = 
  [P in T]?: ButtonHTMLAttributes<HTMLButtonElement>[P];
;


export const getColorPickerControl = (
  color,
  style,
  onChange,
  ...props
: UnwrapButtonProps<
  'color' | 'onSelect' | 'onSelectCapture' | 'onChange' | 'style'
>) => 

;

此逻辑适用于任何具有子属性(SVG、Anchor、Input 等)的类型

如果你想要的是图标颜色,假设它们是 svg,那么这也是一个有用的辅助函数:

export type SVGAttribs<T extends keyof SVGAttributes<SVGSVGElement>> = 
  [P in T]?: SVGAttributes<SVGSVGElement>[P];
;

这里是一个 SVGAttribs 类型的示例,它用于用户关闭给定视图的“X”图标

import  SVGAttribs  from '@/types/index';

const X = (
  ...props
: SVGAttribs<
  'className' | 'width' | 'height' | 'onMouseOver' | 'aria-hidden'
>) => 
  return (
    <svg
      width='24'
      height='24'
      viewBox='0 0 24 24'
      fill='none'
      xmlns='http://www.w3.org/2000/svg'
      ...props>
      <path
        d='M6 6L18 18M6 18L18 6L6 18Z'
        stroke='rgb(29, 78, 216)'
        strokeWidth='2'
        className='ring-2 ring-blue-700'
        strokeLinecap='round'
        strokeLinejoin='round'
      />
    </svg>
  );
;

export default X;

请注意,通过仅使用 ...props 作为返回值,在 SVGAttribs 的 &lt;"" | ""&gt; 中定义的所有属性现在都可以在项目中的任何位置有条件地用于此组件

这是一个 X 组件的示例,其中包含称为内联的这些道具

                <div className='relative z-10 flex items-center lg:hidden'>
                  /* Mobile menu button */
                  <Disclosure.Button
                    key=4 ** 5 / 2 ** 3 - 1
                    className='rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-600'>
                    <span className='sr-only'>Open menu</span>
                    open ? (
                      <X className='block h-6 w-6' height='24' width='24' onMouseOver=(e) => 
                        // additional logic for onMouseOverEvent, etc.
                        aria-hidden='true' />
                    ) : (
                      <MenuStaggered
                        className='block h-6 w-6'
                        aria-hidden='true'
                      />
                    )
                  </Disclosure.Button>
                </div>

至于有条件地改变颜色,您需要通过映射用户将切换以更改为给定颜色的元素来做到这一点。像这样的

          page?.afcSocial?.afcTemplate?.map((heroImg, i) => (
            <div
              key=i++
              className=cn(
                'bg-white max-w-none bg-current',
                i === 0
                  ? 'bg-[#D2D2D2]'
                  : i === 1
                  ? 'bg-[#0AA8C4]'
                  : i === 2
                  ? 'bg-[#BFDAED]'
                  : ''
              )>
              <div className='max-w-2xl mx-auto sm:px-6 lg:max-w-8xl'>
                <div className='relative overflow-hidden h-[160px] lg:h-[210px] '>
                  <div className='absolute inset-0 max-w-[490px] mx-auto group'>
                    <Image
                      sizes=`$heroImg?.cta?.image?.sizes`
                      quality=85
                      priority=true
                      loader=ImageLoader
                      placeholder='blur'
                      objectFit='cover'
                      layout='responsive'
                      blurDataURL=blurDataURLShimmer(
                        w: 203.38,
                        h: 479.97
                      )
                      className=' h-full object-center max-w-[490px] group-hover:bg-opacity-75 duration-150 animated fadeIn'
                      src=`$heroImg?.cta?.image?.sourceUrl`
                      alt=`$heroImg?.cta?.image?.altText`
                      width=`$479.97`
                      height=`$203.38`
                    />
                  </div>

                  <div
                    className=cn(
                      'relative max-w-[130px] lg:max-w-[190px] mx-auto flex flex-col left-[7rem] lg:left-[8rem]  lg:px-0',
                      i === 0 ? 'text-[#00588F]' : 'text-white'
                    )>
                    <div
                      className=cn(
                        ' tracking-tight text-white lg:text-2xl absolute mt-5 md:mt-0 origin-right right-[0%]',
                        i === 0 ? 'text-[#00588F]' : 'text-white'
                      )>
                      <div className='inline-flex flex-shrink font-medium align-middle text-[18px] w-[160px] sm:w-[190px] md:mt-2 '>
                        <p>flow[i].icon</p>&nbsp;
                        <p className='mt-2 my-auto align-middle'>
                          ' '
                          <SocialChevron
                            className=cn(
                              i === 0 ? `fill-[#00588F]` : 'white',
                              ''
                            )
                          />
                        </p>
                        &nbsp;&nbsp;&nbsp;
                        <p
                          className=cn(
                            'sm:text-[20px] text-[18.6px] mt-1 mr-0.25 leading-[24px] ',
                            i === 1 ? 'max-w-[83px] sm:max-w-none' : ''
                          )>
                          heroImg?.cta?.caption
                        </p>
                      </div>
                      <p
                        className=cn(
                          'lg:mt-4 mt-2.5 md:text-[16px] md:max-w-[275px] text-[12.25px] leading-[16px] md:leading-[20.26px] text-center font-normal',
                          i === 0
                            ? 'w-[160px] md:max-w-[275px] text-[#00588F]'
                            : i === 1
                            ? 'w-[200px] md:max-w-[380px] text-[#e5e5e5]'
                            : i === 2
                            ? 'w-[200px] md:max-w-[340px] text-[#e5e5e5]'
                            : ''
                        )>
                        heroImg?.cta?.description
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          ))

注意:cn 的值是 classnames,从 classnames 包导入

【讨论】:

以上是关于TypeScript:如何使函数具有反应性的主要内容,如果未能解决你的问题,请参考以下文章

R Shiny:如何使多个元素在添加/删除按钮上下文中具有反应性?

VueJs 使计算属性具有反应性

如何在安装后使物体产生反应?

typescript 反应性forms1.component.ts

使用 TypeScript 反应道具类型 - 如何拥有函数类型?

如何在 Vue js 中使 localStorage 中的数据具有反应性