banner
AgedCoffee

AgedCoffee

React-Hooks 複合元件

React-Hooks 复合组件#

原文地址

复合组件是你有兩個或更多的組件一起工作來完成一項有用的任務。通常,其中一個組件是父級,另一個是子級。目標是提供更豐富表達性和靈活性的 API。

把它想象成<select><option>

<select>
  <option value="value1">key1</option>
  <option value="value2">key2</option>
  <option value="value3">key3</option>
</select>

如果你試圖只用其中一個而不使用另一個,它就不會工作(或者沒有意義)。此外,這實際上是一個非常出色的 API。讓我們看看如果我們沒有組合組件 API 來使用時會是什麼樣子(請記住,這是 HTML 而不是 JSX)

<select options="key1:value1;key2:value2;key3:value3"></select>

我相信你可以想出其他表達這個意思的方式,但是太惡心了。那麼用這種 API 如何表達 disabled 屬性呢?有點瘋狂。

所以,複合組件 API 為您提供了一種表達組件之間關係的好方法。

另一個重要方面是 “隱式狀態” 的概念。<select>元素會隱式存儲有關所選選項的狀態,並將該狀態與它的子元素分享,以便它們根據該狀態來呈現自身。但這種共享是隱式的,因為我們的 HTML 代碼中甚至無法訪問此狀態(而且也不需要)。

好的,讓我們看一下一個合法的 React 組件,它暴露了複合組件來進一步理解這些原則。這裡是 Reach UI 中<Menu />組件的例子,它暴露了複合組件 API:

function App() {
  return (
    <Menu>
      <MenuButton>
        Actions <span aria-hidden>▾</span>
      </MenuButton>
      <MenuList>
        <MenuItem onSelect={() => alert('Download')}>Download</MenuItem>
        <MenuItem onSelect={() => alert('Copy')}>Create a Copy</MenuItem>
        <MenuItem onSelect={() => alert('Delete')}>Delete</MenuItem>
      </MenuList>
    </Menu>
  )
}

在這個例子中,<Menu>建立了一些共享的隱式狀態。 <MenuButton><MenuList><MenuItem>組件都可以訪問或操作該狀態,而且都是隱式實現的。這樣就可以提供你想要的表達 API。

那麼這是如何做到的呢?好吧,如果你觀看我的課程,我會向你展示兩種方法來實現。一個使用 React.cloneElement 在子元素上,另一個使用 React context。 (我的課程需要略微更新以顯示如何使用 hooks 來實現此目的)。在本博客文章中,我將向您展示如何使用 context 創建一套簡單的複合部件。

在教授一個新的概念時,我喜歡先使用簡單的例子。所以我們將使用我最喜歡的<Toggle>組件例子來進行講解。

這是我們如何使用<Toggle>複合組件的方式:

function App() {
  return (
    <Toggle onToggle={(on) => console.log(on)}>
      <ToggleOn>The button is on</ToggleOn>
      <ToggleOff>The button is off</ToggleOff>
      <ToggleButton />
    </Toggle>
  )
}

好的,你們都在等待的時刻到了,實際上使用上下文和掛鉤實現複合組件的全部代碼。

import * as React from 'react'
// this switch implements a checkbox input and is not relevant for this example
import { Switch } from '../switch'

const ToggleContext = React.createContext()

function useEffectAfterMount(cb, dependencies) {
  const justMounted = React.useRef(true)
  React.useEffect(() => {
    if (!justMounted.current) {
      return cb()
    }
    justMounted.current = false
  }, dependencies)
}

function Toggle(props) {
  const [on, setOn] = React.useState(false)
  const toggle = React.useCallback(() => setOn((oldOn) => !oldOn), [])
  useEffectAfterMount(() => {
    props.onToggle(on)
  }, [on])
  const value = React.useMemo(() => ({ on, toggle }), [on])
  return <ToggleContext.Provider value={value}>{props.children}</ToggleContext.Provider>
}

function useToggleContext() {
  const context = React.useContext(ToggleContext)
  if (!context) {
    throw new Error(`Toggle compound components cannot be rendered outside the Toggle component`)
  }
  return context
}

function ToggleOn({ children }) {
  const { on } = useToggleContext()
  return on ? children : null
}

function ToggleOff({ children }) {
  const { on } = useToggleContext()
  return on ? null : children
}

function ToggleButton(props) {
  const { on, toggle } = useToggleContext()
  return <Switch on={on} onClick={toggle} {...props} />
}

線上展示

所以這個工作原理是我們使用 React 創建一個上下文,在其中存儲狀態和更新狀態的機制。然後<Toggle>組件負責將該上下文值提供給 React 樹的其餘部分。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。