亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

概述

Headless 組件即無 UI 組件,框架僅提供邏輯,UI 交給業(yè)務(wù)實現(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)描述出來,此時框架能提供最基礎(chǔ)的 tabs 切換特性,即按照順序,點擊 Tab 時切換內(nèi)容到對應(yīng)的 Tab.Panel。

此時沒有任何額外的 UI 樣式,甚至連 Tab 選中態(tài)都沒有,如果需要進一步定制,需要用框架提供的 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>

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

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

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

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

控制某個 Tab 是否可編輯:

<Tab disabled>Tab 2</Tab>

Tab 切換是否為手動按 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 拓展能力,因為 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 場景下會引發(fā)不必要的全局 ReRender,相比之下,RenderProps 只會將重渲染限定在回調(diào)函數(shù)內(nèi)部,在性能上 RenderProps 更優(yōu)。

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

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

首先在 Tab.Group 利用 ContextProvider 包裹一層上下文容器,并封裝一個 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.PanelTab.List 都從 useData 獲取數(shù)據(jù),而這些數(shù)據(jù)都可以從當(dāng)前最近的 Tab.Group 上下文獲取,所以多個 tabs 之間數(shù)據(jù)可以相互隔離。

另一個重點就是 RenderProps 的實現(xiàn)。其實早在 75.精讀《Epitath 源碼 - renderProps 新用法》 我們就講過 RenderProps 的實現(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} 時,就直接把 resolvedChildren 作為子元素,否則自己就作為 dom 載體 createElement(Component, ..., resolvedChildren) 來渲染。

而體現(xiàn) RenderProps 的點就在于 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,那么給用戶提供一個組合變量 selected 就可能比分別提供這兩個變量更方便。

總結(jié)

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

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

作為使用者,了解這些組件分別支持哪些模塊,各模塊提供了哪些狀態(tài),并根據(jù)這些狀態(tài)實現(xiàn)對應(yīng)的 UI 組件,響應(yīng)這些狀態(tài)的變化。由于最復(fù)雜的狀態(tài)邏輯已經(jīng)被框架內(nèi)置,所以對于 UI 狀態(tài)多樣的業(yè)務(wù)甚至可以每個組件重寫一遍 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個常用的對象

    JavaScript中5個常用的對象

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

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

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

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

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

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

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

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

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

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

    這篇文章主要為大家介紹了js?交互在Flutter?中使用?webview_flutter示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    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

最新評論