什么是 ”无渲染组件“ ?

更新日期: 2023-05-15 阅读: 2.6k 标签: 组件
无头用户界面组件是一种不提供任何接口而提供最大视觉灵活性的组件。“等等,你是在提倡没有用户界面的用户界面模式么?”

是的,这正是我所提倡的。

掷硬币组件

假设你现在需要实现一个掷硬币的功能,当组件渲染时模拟一次掷硬币!一半的时间组件应该渲染 “正面”,一半的时间应该渲染 “反面”。你对你的产品经理说 “这需要多年的研究!” 然后你继续工作。

const CoinFlip = () =>
Math.random() < 0.5 ? <div>Heads</div> : <div>Tails</div>;

事实证明,模仿掷硬币比你想象的要容易得多,所以你可以自豪地分享成果。你得到了回复,“这真的是太棒了!请更新那些显示很酷的硬币的图片好么?” 没问题!

const CoinFlip = () =>
  Math.random() < 0.5 ? (
    <div>
      <img src=”/heads.svg” alt=”Heads” />
    </div>
  ) : (
    <div>
      <img src=”/tails.svg” alt=”Tails” />
    </div>
  );

很快,他们会在营销材料中使用你的 <CoinFlip /> 组件,来向人们演示你的新功能有多么炫酷。“我们想在博客上发表文章,但是我们需要标签 'Heads' 和 'Tails',用于 seo 和其他事情。” 哦,天啊,或许我们需要在商城网站中添加一个标志?

const CoinFlip = (
  // We’ll default to false to avoid breaking the applications
  // current usage.
  { showLabels = false }
) =>
  Math.random() < 0.5 ? (
    <div>
      <img src=”/heads.svg” alt=”Heads” />

      {/* Add these labels for the marketing site. */}
      {showLabels && <span>Heads</span>}
    </div>
  ) : (
    <div>
      <img src=”/tails.svg” alt=”Tails” />

      {/* Add these labels for the marketing site. */}
      {showLabels && <span>Tails</span>}
    </div>
  );

后来,出现了一个需求。“我们想知道你能否只给 APP 里的 <CoinFlip /> 添加一个重掷硬币的按钮?” 事情开始变得糟糕,以致于我不敢再直视 Kent C. Dodds 的眼睛。

 const flip = () => ({
   flipResults: Math.random()
 });

 class CoinFlip extends react.Component {
   static defaultProps = {
     showLabels: false,
     // We don’t repurpose `showLabels`, we aren’t animals, after all.
     showButton: false
   };

   state = flip();

   handleClick = () => {
     this.setState(flip);
   };

   render() {
    return (
      // Use fragments so people take me seriously.
      <>
      {this.state.showButton && (
        <button onClick={this.handleClick}>Reflip</button>
      )}
      {this.state.flipResults < 0.5 ? (
        <div>
          <img src=”/heads.svg” alt=”Heads” />
          {showLabels && <span>Heads</span>}
        </div>
      ) : (
        <div>
          <img src=”/tails.svg” alt=”Tails” />
          {showLabels && <span>Tails</span>}
        </div>
      )}
      </>
    );
  }
 }

很快就有同事找到你。“嗨,你的 <CoinFlip /> 性能太棒了!我们刚接到任务要开发新的 <DiceRoll /> 特性,我们希望可以重用你的代码!” 新骰子的功能:

  • 想要 “重新掷骰子” 的 onClick。
  • 希望在 APP 和商城网站中都显示。
  • 有完全不同的界面。
  • 有不同的随机性。

你现在有两个选项,回复 “对不起,我们不一样。” 或着你一边向 CoinFlip 中添加 DiceRoll 的复杂功能,一边看着组件无法承受过多职责而崩溃。(是否有一个给忧郁的程序员诗人的市场?我喜欢追求这种技术。)

无头组件了解一下

无头用户界面组件将组件的逻辑和行为与其视觉表现分离。当组件的逻辑足够复杂并与它的视觉表现解耦时,这种模式非常有效。实现 <CoinFlip/> 的无头将作为函数子组件或渲染属性,就像这样:

 const flip = () => ({
   flipResults: Math.random()
 });
 class CoinFlip extends React.Component {
   state = flip();
   handleClick = () => {
     this.setState(flip);
   };
   render() {
     return this.props.children({
       rerun: this.handleClick,
       isHeads: this.state.flipResults < 0.5
     });
   }
 }

这个组件是无头的,因为它没有渲染任何东西,它期望当它在处理逻辑的时,各种 consumers 完成视觉表现。因此 APP 代码看起来应该是这样的:

 <CoinFlip>
   {({ rerun, isHeads }) => (
    <>
      <button onClick={rerun}>Reflip</button>
      {isHeads ? (
        <div>
          <img src=”/heads.svg” alt=”Heads” />
        </div>
      ) : (
        <div>
          <img src=”/tails.svg” alt=”Tails” />
        </div>
      )}
    </>
   )}
 </CoinFlip>

商场站点代码:

 <CoinFlip>
  {({ isHeads }) => (
    <>
      {isHeads ? (
        <div>
          <img src=”/heads.svg” alt=”Heads” />
          <span>Heads</span>
        </div>
      ) : (
        <div>
          <img src=”/tails.svg” alt=”Tails” />
          <span>Tails</span>
        </div>
      )}
    </>
  )}
 </CoinFlip>

这很好不是么!我们把逻辑与视觉表现完全解耦!这给我们视觉上带来了很大的灵活性!我知道你正在思考什么......

你这小笨蛋,这不就是一个渲染属性么?

这个无头组件恰好是作为渲染工具实现的,是的!它也可以作为一个高阶组件来实现。即使是简单的实现,也可以到达我们的要求。它甚至可以作为 View 和 Controller 来实现。或者是 ViewModel 和 View。这里的重点是将翻转硬币的机制和该机制的 “界面” 分离。

那DiceRoll呢?

这种分离的巧妙之处在于,推广我们的无头组件以及支持我们同事的新的 <DiceRoll /> 的特性会很容易。拿着我的 Diet Coke™:

 const run = () => ({
   random: Math.random()
 });

 class Probability extends React.Component {
   state = run();

   handleClick = () => {
     this.setState(run);
   };

   render() {
     return this.props.children({
       rerun: this.handleClick,

       // By taking in a threshold property we can support
       // different odds!
       result: this.state.random < this.props.threshold
     });
   }
 }

利用这个无头组件,我们在没有对 consumer 进行任何更改对情况下,交换的实现:

 const CoinFlip = ({ children }) => (
  <Probability threshold={0.5}>
    {({ rerun, result }) =>
      children({
        isHeads: result,
        rerun
    })}
  </Probability>
 );

现在我们的同事可以分享我们的 <Probability /> 模拟程序机制了!

 const RollDice = ({ children }) => (
   // Six Sided Dice
   <Probability threshold={1 / 6}>
     {({ rerun, result }) => (
       <div>
         {/* She was able to use a different event! */}
         <span onMouseOver={rerun}>Roll the dice!</span>
         {/* Totally different interface! */}
         {result ? (
           <div>Big winner!</div>
         ) : (
           <div>You win some, you lose most.</div>
         )}
       </div>
     )}
  </Probability>
 );

非常干净,不是么?

分离原则 —— Unix 哲学

这表达了一个存在很长时间对普遍基本原则,“Unix 基础哲学第四条”:

分离原则:将策略与机制分离,将接口和引擎分离 —— Eric S. Raymond。

我想借用书中的部分,并且用 “接口” 来替换 “策略” 一词。

接口和机制都倾向于在不同时间范围内变化,但接口的变化比机制要快得多。GUI 工具包那时尚的外观和体验会变,但是操作和组合却不会。

因此,将接口和机制结合在一起有两个不好的影响:它使得接口变的生硬,更难响应用户的需求,这意味着试图更改接口具有很强的不稳定性。

另一方面,通过将这两者分开,我们可以在没有中断机制的情况下试验新的接口。我们还可以更容易地为该机制编写好的测试(接口,因为它们太新了,难以证明这样的投资是合理的)。

我喜欢这里的真知灼见!这也让我们对何时使用无头组件模式有了一些了解。

  • 这个组件会持续多长时间?除了界面外,是否值得刻意保留这个机制?也许在另一个外观和体验不同的项目中可以使用这种机制?
  • 我们的界面改变的频率多快?同一机制会有多个接口么?

当你将 “机制” 和 “策略” 分离时,就会产生间接的成本。你需要确保分离的价值大于它的间接成本。我认为这在很大程度上是过去许多 MV* 模式出问题的地方,它们从这样一个公理开始,即所有的东西都应该以这种方式分开;而在现实中,机制和策略往往是紧密耦合的,或分离的成本并没有超过分离的好处。

开源无头组件和非平凡引用

要获取一个真正的示例性非平凡无头组件,可以了解一下我朋友 Kent C. Dodds 在 Paypal 上的项目:downshift 的文章。事实上,正是 downshift 给了这篇文章一些灵感。在不提供任何用户界面的情况下,downshift 提供了复杂的自动完成、下拉、选择体验,这些体验都是可以访问的。在这里看看它所有可用的方法。

我希望随着时间的推移,会出现更多类似的项目。我无法计算有多少次我想使用一个特定的开源 UI 组件,但却无法这样做,因为在满足设计要求的方式上,它并不是 “主题化的” 或 “可剥离的”。无头组件完全通过 “自带接口” 的要求来解决这个问题。

在一个设计系统和用户界面库都是无头的世界里,你的界面可以有一种高端定制的感觉,以及优秀开源库的持久性和可访问性。你仅需要将时间花费在你所需要的部分 —— 一个独特的,外观及体验都只属于你 APP 的部分。

我可以继续讨论从国际化到 E2E 测试集成的好处,但我建议你最好自己去体验。

译者:@Starrier,译文:https://github.com/xitu/gold-miner/blob/master/TODO1/headless-user-interface-components.md
作者:@Merrick Christensen,原文:https://medium.com/merrickchristensen/headless-user-interface-components-565b0c0f2e18

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/12482

相关推荐

vue重新渲染组件(重置或者更新)

当数据通过异步操作后,对之前加载的数据进行变更后,发现数据不生效。A组件或者B组件触发数据更新,C组件数据更新了,但是C组件仍显示上一次数据。

Vuetify基于vue2.0,为移动而生的组件框架

Vuetify 支持SSR(服务端渲染),SPA(单页应用程序),PWA(渐进式Web应用程序)和标准HTML页面。 Vuetify是一个渐进式的框架,试图推动前端开发发展到一个新的水平。

React高阶组件中使用React.forwardRef的技巧

之前使用React.forwardRef始终无法应用于React高阶组件中,关键点就是React.forwardRef的API中ref必须指向dom元素而不是React组件。codepen实例请划到底部。

Vue使用Props绑定Object并且传参

通过Props 给子组件传变量,变量是对象时,子组件无法在首次打开时获取到传入对象数据,并且在父组件中改变对象的属性,子组件也是无法监听

Vue中插槽的作用_Vue组件插槽的使用以及调用组件内的方法

通过给组件传递参数, 可以让组件变得更加可扩展, 组件内使用props接收参数,slot的使用就像它的名字一样, 在组件内定义一块空间。在组件外, 我们可以往插槽里填入任何元素。slot-scope的作用就是把组件内的数据带出来

React Hook父组件获取子组件的数据/函数

我们知道在react中,常用props实现子组件数据到父组件的传递,但是父组件调用子组件的功能却不常用。文档上说ref其实不是最佳的选择,但是想着偷懒不学redux,在网上找了很多教程,要不就是hook的讲的太少

使用Vue 自定义文件选择器组件

文件选择元素是web上最难看的 input 类型之一。它们在每个浏览器中实现的方式不同,而且通常非常难看。这里有一个解决办法,就是把它封装成一个组件。

element-ui 的隐藏滚动组件el-scrollbar

为什么要用el-scrollbar,大家都知道,模拟一个滚动不难,而且市面上有很多这样的库。我考虑的,首先项目用的框架是Vue,然后用的组件库是Element,Element官网也有很多滚动

vue中prop属性传值解析

prop的定义:在没有状态管理机制的时候,prop属性是组件之间主要的通信方式,prop属性其实是一个对象,在这个对象里可以定义一些数据,而这些数据可以通过父组件传递给子组件。 prop属性中可以定义属性的类型,也可以定义属性的初始值。

写一个vue组件库_跟着element学习写组件

组件以插件的形式引入使用,当然,也可以直接在页面引入组件文件,两者按需使用。通过源码可知,vue不会重复安装同一个插件。以第一次安装为准,现在,可以在代码中使用组件啦~

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!