欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

無UI?組件Headless框架邏輯原理用法示例詳解

 更新時(shí)間:2022年10月17日 15:40:40   作者:黃子毅  
這篇文章主要為大家介紹了無UI?組件Headless框架邏輯原理用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

概述

Headless 組件即無 UI 組件,框架僅提供邏輯,UI 交給業(yè)務(wù)實(shí)現(xiàn)。這樣帶來的好處是業(yè)務(wù)有極大的 UI 自定義空間,而對框架來說,只考慮邏輯可以讓自己更輕松的覆蓋更多場景,滿足更多開發(fā)者不同的訴求。

我們以 headlessui-tabs 為例看看它的用法,并讀一讀 源碼

headless tabs 最簡單的用法如下:

import { Tab } from "@headlessui/react";
function MyTabs() {
  return (
    <Tab.Group>
      <Tab.List>
        <Tab>Tab 1</Tab>
        <Tab>Tab 2</Tab>
        <Tab>Tab 3</Tab>
      </Tab.List>
      <Tab.Panels>
        <Tab.Panel>Content 1</Tab.Panel>
        <Tab.Panel>Content 2</Tab.Panel>
        <Tab.Panel>Content 3</Tab.Panel>
      </Tab.Panels>
    </Tab.Group>
  );
}

以上代碼沒有做任何邏輯定制,只用 Tab 及其提供的標(biāo)簽把 tabs 的結(jié)構(gòu)描述出來,此時(shí)框架能提供最基礎(chǔ)的 tabs 切換特性,即按照順序,點(diǎn)擊 Tab 時(shí)切換內(nèi)容到對應(yīng)的 Tab.Panel。

此時(shí)沒有任何額外的 UI 樣式,甚至連 Tab 選中態(tài)都沒有,如果需要進(jìn)一步定制,需要用框架提供的 RenderProps 能力拿到狀態(tài)后做業(yè)務(wù)層的定制,比如選中態(tài):

<Tab as={Fragment}>
  {({ selected }) => (
    <button
      className={selected ? "bg-blue-500 text-white" : "bg-white text-black"}
    >
      Tab 1
    </button>
  )}
</Tab>

要實(shí)現(xiàn)選中態(tài)就要自定義 UI,如果使用 RenderProps 拓展,那么 Tab 就不應(yīng)該提供任何 UI,所以 as={Fragment} 就表示該節(jié)點(diǎn)作為一個(gè)邏輯節(jié)點(diǎn)而非 UI 節(jié)點(diǎn)(不產(chǎn)生 dom 節(jié)點(diǎn))。

類似的,框架將 tabs 組件拆分為 Tab 標(biāo)題區(qū)域 Tab 與 Tab 內(nèi)容區(qū)域 Tab.Panel,每個(gè)部分都可以用 RenderProps 定制,而框架早已根據(jù)業(yè)務(wù)邏輯規(guī)定好了每個(gè)部分可以做哪些邏輯拓展,比如 Tab 就提供了 selected 參數(shù)告知當(dāng)前 Tab 是否處于選中態(tài),業(yè)務(wù)就可以根據(jù)它對 UI 進(jìn)行高亮處理,而框架并不包含如何做高亮的處理,因此才體現(xiàn)出該 tabs 組件的拓展性,但響應(yīng)的業(yè)務(wù)開發(fā)成本也較高。

Headless 的拓展性可以拿一個(gè)場景舉例:如果業(yè)務(wù)側(cè)要定制 Tab 標(biāo)題,我們可以將 Tab.List 包裹在一個(gè)更大的標(biāo)題容器內(nèi),在任意位置添加標(biāo)題 jsx,而不會(huì)破壞原本的 tabs 邏輯,然后將這個(gè)組件作為業(yè)務(wù)通用組件即可。

再看更多的配置參數(shù):

控制某個(gè) Tab 是否可編輯:

<Tab disabled>Tab 2</Tab>

Tab 切換是否為手動(dòng)按 EnterSpace 鍵:

<Tab.Group manual>

默認(rèn)激活 Tab:

<Tab.Group defaultIndex={1}>

監(jiān)聽激活 Tab 變化:

<Tab.Group
  onChange={(index) => {
    console.log('Changed selected tab to:', index)
  }}
>

受控模式:

<Tab.Group selectedIndex={selectedIndex} onChange={setSelectedIndex}>

用法就介紹到這里。

精讀

由此可見,Headless 組件在 React 場景更多使用 RenderProps 的方式提供 UI 拓展能力,因?yàn)?RenderProps 既可以自定義 UI 元素,又可以拿到當(dāng)前上下文的狀態(tài),天然適合對 UI 的自定義。

還有一些 Headless 框架如 TanStack table 還提供了 Hooks 模式,如:

const table = useReactTable(options)
return <table {table.getTableProps()}></table>

Hooks 模式的好處是沒有 RenderProps 那么多層回調(diào),代碼層級看起來舒服很多,而且 Hooks 模式在其他框架也逐漸被支持,使組件庫跨框架適配的成本比較低。但 Hooks 模式在 React 場景下會(huì)引發(fā)不必要的全局 ReRender,相比之下,RenderProps 只會(huì)將重渲染限定在回調(diào)函數(shù)內(nèi)部,在性能上 RenderProps 更優(yōu)。

分析的差不多,我們看看 headlessui-tabs 的 源碼

首先組件要封裝的好,一定要把內(nèi)部組件通信問題給解決了,即為什么包裹了 Tab.Group 后,TabTab.Panel 就可以產(chǎn)生聯(lián)動(dòng)?它們一定要訪問共同的上下文數(shù)據(jù)。答案就是 Context:

首先在 Tab.Group 利用 ContextProvider 包裹一層上下文容器,并封裝一個(gè) Hook 從該容器提取數(shù)據(jù):

// 導(dǎo)出的別名就叫 Tab.Group
const Tabs = () => {
  return (
    <TabsDataContext.Provider value={tabsData}>
      {render({
        ourProps,
        theirProps,
        slot,
        defaultTag: DEFAULT_TABS_TAG,
        name: "Tabs",
      })}
    </TabsDataContext.Provider>
  );
};
// 提取數(shù)據(jù)方法
function useData(component: string) {
  let context = useContext(TabsDataContext);
  if (context === null) {
    let err = new Error(
      `<${component} /> is missing a parent <Tab.Group /> component.`
    );
    if (Error.captureStackTrace) Error.captureStackTrace(err, useData);
    throw err;
  }
  return context;
}

所有子組件如 Tab、Tab.Panel、Tab.List 都從 useData 獲取數(shù)據(jù),而這些數(shù)據(jù)都可以從當(dāng)前最近的 Tab.Group 上下文獲取,所以多個(gè) tabs 之間數(shù)據(jù)可以相互隔離。

另一個(gè)重點(diǎn)就是 RenderProps 的實(shí)現(xiàn)。其實(shí)早在 75.精讀《Epitath 源碼 - renderProps 新用法》 我們就講過 RenderProps 的實(shí)現(xiàn)方式,今天我們來看一下 headlessui 的封裝吧。

核心代碼精簡后如下:

function _render<TTag extends ElementType, TSlot>(
  props: Props<TTag, TSlot> & { ref?: unknown },
  slot: TSlot = {} as TSlot,
  tag: ElementType,
  name: string
) {
  let {
    as: Component = tag,
    children,
    refName = 'ref',
    ...rest
  } = omit(props, ['unmount', 'static'])
  let resolvedChildren = (typeof children === 'function' ? children(slot) : children) as
    | ReactElement
    | ReactElement[]
  if (Component === Fragment) {
    return cloneElement(
      resolvedChildren,
      Object.assign(
        {},
        // Filter out undefined values so that they don't override the existing values
        mergeProps(resolvedChildren.props, compact(omit(rest, ['ref']))),
        dataAttributes,
        refRelatedProps,
        mergeRefs((resolvedChildren as any).ref, refRelatedProps.ref)
      )
    )
  }
  return createElement(
    Component,
    Object.assign(
      {},
      omit(rest, ['ref']),
      Component !== Fragment && refRelatedProps,
      Component !== Fragment && dataAttributes
    ),
    resolvedChildren
  )
}

首先為了支持 Fragment 模式,所以當(dāng)制定 as={Fragment} 時(shí),就直接把 resolvedChildren 作為子元素,否則自己就作為 dom 載體 createElement(Component, ..., resolvedChildren) 來渲染。

而體現(xiàn) RenderProps 的點(diǎn)就在于 resolvedChildren 處理的這段:

let resolvedChildren =
  typeof children === "function" ? children(slot) : children;

如果 children 是函數(shù)類型,就把它當(dāng)做函數(shù)執(zhí)行并傳入上下文(此處為 slot),返回值是 JSX 元素,這就是 RenderProps 的本質(zhì)。

再看上面 Tab.Group 的用法:

render({
  ourProps,
  theirProps,
  slot,
  defaultTag: DEFAULT_TABS_TAG,
  name: "Tabs",
});

其中 slot 就是當(dāng)前 RenderProps 能拿到的上下文,比如在 Tab.Group 中就提供 selectedIndex,在 Tab 就提供 selected 等等,在不同的 RenderProps 位置提供便捷的上下文,對用戶使用比較友好是比較關(guān)鍵的。

比如 Tab 內(nèi)已知該 TabindexselectedIndex,那么給用戶提供一個(gè)組合變量 selected 就可能比分別提供這兩個(gè)變量更方便。

總結(jié)

我們總結(jié)一下 Headless 的設(shè)計(jì)與使用思路。

作為框架作者,首先要分析這個(gè)組件的業(yè)務(wù)功能,并抽象出應(yīng)該拆分為哪些 UI 模塊,并利用 RenderProps 將這些 UI 模塊以 UI 無關(guān)方式提供,并精心設(shè)計(jì)每個(gè) UI 模塊提供的狀態(tài)。

作為使用者,了解這些組件分別支持哪些模塊,各模塊提供了哪些狀態(tài),并根據(jù)這些狀態(tài)實(shí)現(xiàn)對應(yīng)的 UI 組件,響應(yīng)這些狀態(tài)的變化。由于最復(fù)雜的狀態(tài)邏輯已經(jīng)被框架內(nèi)置,所以對于 UI 狀態(tài)多樣的業(yè)務(wù)甚至可以每個(gè)組件重寫一遍 UI 樣式,對于樣式穩(wěn)定的場景,業(yè)務(wù)也可以按照 Headless + UI 作為整體封裝出包含 UI 的組件,提供給各業(yè)務(wù)場景調(diào)用。

討論地址是:精讀《Headless 組件用法與原理》· Issue #444 · dt-fe/weekly

以上就是無UI 組件Headless框架邏輯原理用法示例詳解的詳細(xì)內(nèi)容,更多關(guān)于無UI 組件Headless框架邏輯的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JavaScript 原型與原型鏈詳情

    JavaScript 原型與原型鏈詳情

    這篇文章主要介紹了JavaScript 原型與原型鏈,JavaScript常被描述為一種基于原型的語言,對象以其原型為模板、從原型繼承屬性和放法。原型對象也可能擁有原型,并從中繼承屬性和方法,一層一層以此類推。這種關(guān)系常被稱為原型鏈,帶著簡單的了解看看下文內(nèi)容具體介紹吧
    2021-10-10
  • JavaScript中5個(gè)常用的對象

    JavaScript中5個(gè)常用的對象

    JavaScript是一門腳本語言,不同于Python的是,它是一門瀏覽器腳本語言,而Python則是服務(wù)器腳本語言,我們不光要會(huì)Python,還要會(huì)JavaScript,因?yàn)樗鼘ψ鼍W(wǎng)頁方面是有很大作用的。本篇內(nèi)容小編就來詳細(xì)解說JavaScript常用的對象,需要的朋友可以參考一下
    2021-10-10
  • 微信小程序獲取用戶openId的實(shí)現(xiàn)方法

    微信小程序獲取用戶openId的實(shí)現(xiàn)方法

    這篇文章主要介紹了微信小程序獲取用戶openId的實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • JS前端設(shè)計(jì)模式之發(fā)布訂閱模式詳解

    JS前端設(shè)計(jì)模式之發(fā)布訂閱模式詳解

    這篇文章主要為大家介紹了JS前端設(shè)計(jì)模式之發(fā)布訂閱模式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 微信小程序?qū)崿F(xiàn)錨點(diǎn)定位樓層跳躍的實(shí)例

    微信小程序?qū)崿F(xiàn)錨點(diǎn)定位樓層跳躍的實(shí)例

    這篇文章主要介紹了微信小程序?qū)崿F(xiàn)錨點(diǎn)定位樓層跳躍的實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • TypeScript 內(nèi)置高級類型編程示例

    TypeScript 內(nèi)置高級類型編程示例

    這篇文章主要為大家介紹了TypeScript 內(nèi)置高級類型編程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • js?交互在Flutter?中使用?webview_flutter

    js?交互在Flutter?中使用?webview_flutter

    這篇文章主要為大家介紹了js?交互在Flutter?中使用?webview_flutter示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • 微信小程序 wx.uploadFile無法上傳解決辦法

    微信小程序 wx.uploadFile無法上傳解決辦法

    這篇文章主要介紹了微信小程序 wx.uploadFile無法上傳解決辦法的相關(guān)資料,需要的朋友可以參考下
    2016-12-12
  • JavaScript與JQuery框架基礎(chǔ)入門教程

    JavaScript與JQuery框架基礎(chǔ)入門教程

    這篇文章主要介紹了jQuery和JavaScript入門基礎(chǔ)知識學(xué)習(xí)指南,jQuery是當(dāng)下最主流人氣最高的JavaScript庫,需要的朋友可以參考下
    2021-07-07
  • JSON字符串轉(zhuǎn)換JSONObject和JSONArray的方法

    JSON字符串轉(zhuǎn)換JSONObject和JSONArray的方法

    這篇文章主要介紹了JSON字符串轉(zhuǎn)換JSONObject和JSONArray的方法的相關(guān)資料,需要的朋友可以參考下
    2016-06-06

最新評論