React-Hooks 複合コンポーネント#
複合コンポーネントは、2 つ以上のコンポーネントが協力して有用なタスクを達成するために使用されます。通常、1 つは親コンポーネントで、もう 1 つは子コンポーネントです。目標は、より表現力豊かで柔軟な 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 は、コンポーネント間の関係を表現するための良い方法を提供します。
もう 1 つの重要な側面は、「暗黙の状態」の概念です。<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 を提供することができます。
では、これはどのように実現されているのでしょうか?まあ、私のコースを見ていただければ、2 つの方法を紹介します。1 つは子要素で React.cloneElement を使用し、もう 1 つは React コンテキストを使用する方法です(私のコースは、この目的を達成するためにフックを使用する方法を示すためにわずかに更新する必要があります)。このブログ記事では、コンテキストを使用してシンプルな複合コンポーネントセットを作成する方法を紹介します。
新しい概念を教えるとき、私はいつも簡単な例を使うのが好きです。したがって、お気に入りの<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'
// このスイッチはチェックボックス入力を実装しており、この例には関係ありません
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 ツリーの他の部分に提供する責任を持ちます。