Closed14

useのインタフェース見る

Yug (やぐ)Yug (やぐ)
export function use<T>(usable: Usable<T>): T;

引数はプロミス以外にコンテキストもokなのは公式で言っている通り
どういうときにコンテキストをuseで使うのかはわからん
そういえば、そもそもコンテキストの実態を知らないな

export type Usable<T> = PromiseLike<T> | Context<T>;
interface PromiseLike<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
}
interface Context<T> extends Provider<T> {
  Provider: Provider<T>;
  Consumer: Consumer<T>;
  /**
   * Used in debugging messages. You might want to set it
   * explicitly if you want to display a different name for
   * debugging purposes.
   *
   * @see {@link https://legacy.reactjs.org/docs/react-component.html#displayname Legacy React Docs}
   */
  displayName?: string | undefined;
}

んーやっぱりcontextの方は非同期とかpromiseとかの話じゃないよな?どういうときにuse()使うのかイメージできない

useは非同期のやつを抑え込むというかいろいろうまくやってくれるものという雑印象しかないので

Yug (やぐ)Yug (やぐ)

あー普通にuseはuseContextとしても使えるってだけのことかも

useの返り値がTということは多分そうだわ
Tは最終的にpropsのvalueにたどり着くので

interface ProviderProps<T> {
    value: T;
    children?: ReactNode | undefined;
}
Yug (やぐ)Yug (やぐ)

PromiseLike / Promise はReact関係なく普通のJSの話で、このスクラップで理解したので飛ばす
https://zenn.dev/yg_kita/scraps/c6f37c1fad955c

要はPromiseLikeは「Thenableな最小限の共通インタフェース / 抽象化レイヤー」
まぁほぼPromiseと一緒と考えて良い

export type Usable<T> = PromiseLike<T> | Context<T>;

ということでContextだけ見ていくか
(なので本スクラップはuseを見ると言うよりContextを見ると言う形になりそうだ)

/**
 * Context lets components pass information deep down without explicitly
 * passing props.
 *
 * Created from {@link createContext}
 *
 * @see {@link https://react.dev/learn/passing-data-deeply-with-context React Docs}
 * @see {@link https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/context/ React TypeScript Cheatsheet}
 *
 * @example
 *
 * ```tsx
 * import { createContext } from 'react';
 *
 * const ThemeContext = createContext('light');
 * ```
 */
interface Context<T> extends Provider<T> {
    Provider: Provider<T>;
    Consumer: Consumer<T>;
    /**
     * Used in debugging messages. You might want to set it
     * explicitly if you want to display a different name for
     * debugging purposes.
     *
     * @see {@link https://legacy.reactjs.org/docs/react-component.html#displayname Legacy React Docs}
     */
    displayName?: string | undefined;
}

へーdisplayNameというデバッグ用のoptional propertyがあるのは初めて知った

Yug (やぐ)Yug (やぐ)
  • Consumerとは何だ
    • あー多分これだわ、古いやり方っぽい
  • Providerは多分あれかな、コンポーネントをラップするコンポーネントみたいなやつ
    • <SomeContext.Provider value={value} />ってやつ
    • そのProviderをContextが継承している

とりあえずProviderから見るか

/**
 * Wraps your components to specify the value of this context for all components inside.
 *
 * @see {@link https://react.dev/reference/react/createContext#provider React Docs}
 *
 * @example
 *
 * ```tsx
 * import { createContext } from 'react';
 *
 * const ThemeContext = createContext('light');
 *
 * function App() {
 *   return (
 *     <ThemeContext.Provider value="dark">
 *       <Toolbar />
 *     </ThemeContext.Provider>
 *   );
 * }
 * ```
 */
type Provider<T> = ProviderExoticComponent<ProviderProps<T>>;

あーやっぱそうだわあのProviderだ

ProviderExoticComponentがProviderPropsをラップしてる

簡単そうな後者のpropsから見ていく

Yug (やぐ)Yug (やぐ)
/**
 * Describes the props accepted by a Context {@link Provider}.
 *
 * @template T The type of the value the context provides.
 */
interface ProviderProps<T> {
    value: T;
    children?: ReactNode | undefined;
}
  • 当然valueは持ってる
  • あとReactNodeもある
    • 多分Providerがラップするコンポーネントを指す
Yug (やぐ)Yug (やぐ)

次、そのProviderPropsをラップしてるProviderExoticComponent

/**
 * An {@link ExoticComponent} with a `propTypes` property applied to it.
 *
 * @template P The props the component accepts.
 */
interface ProviderExoticComponent<P> extends ExoticComponent<P> {
}

プロパティ無しでたらい回し

Yug (やぐ)Yug (やぐ)

継承元であるExticCoponentを見に行く

/**
 * An object masquerading as a component. These are created by functions
 * like {@link forwardRef}, {@link memo}, and {@link createContext}.
 *
 * In order to make TypeScript work, we pretend that they are normal
 * components.
 *
 * But they are, in fact, not callable - instead, they are objects which
 * are treated specially by the renderer.
 *
 * @template P The props the component accepts.
 */
interface ExoticComponent<P = {}> {
    (props: P): ReactNode;
    readonly $$typeof: symbol;
}

これすごい面白い

/**
 * An object masquerading as a component. These are created by functions
 * like {@link forwardRef}, {@link memo}, and {@link createContext}.
 *
 * In order to make TypeScript work, we pretend that they are normal
 * components.
 *
 * But they are, in fact, not callable - instead, they are objects which
 * are treated specially by the renderer.
  • コンポーネントに擬態したオブジェクト
    • だから「Exotic」なコンポーネントと呼んでるのか!
    • createContextやmemoなどによって作られる
  • だが呼び出すことはできないので、レンダラーによって特別に扱われるようになってる
Yug (やぐ)Yug (やぐ)

実装だけ見る

interface ExoticComponent<P = {}> {
    (props: P): ReactNode;
    readonly $$typeof: symbol;
}
  • propsを受け取ってReactNodeを返す関数を持ってるとして実装されてる
    • この関数がExoticComponentの実態そのものと言えそう
  • あとReactNodeへの参照らしきもの(読み取り専用)
Yug (やぐ)Yug (やぐ)

異常を踏まえてProviderをまとめる

type Provider<T> = ProviderExoticComponent<ProviderProps<T>>;
  • ProviderProps<T>が実質渡されるprops
  • それをProviderExoticComponent(=ExoticComponent)がラップする
    • それにより最終的に「propsを受け取りReactNodeを返せる関数」が出来上がる
Yug (やぐ)Yug (やぐ)

Contextに戻る

interface Context<T> extends Provider<T> {
    Provider: Provider<T>;
    Consumer: Consumer<T>;
    /**
     * Used in debugging messages. You might want to set it
     * explicitly if you want to display a different name for
     * debugging purposes.
     *
     * @see {@link https://legacy.reactjs.org/docs/react-component.html#displayname Legacy React Docs}
     */
    displayName?: string | undefined;
}
  • Providerでもあるが内部にProviderを持ってもいる
    • これはReact19のこの新機能の話を表していると思われる
      • 要は「Providerって書かなくても良いよね」ということ
  • 同じようにConsumerも使えるようにはなってる

なるほどねー

Provider(Exoticなコンポーネント)はコンポーネントに擬態(masquerading)してるとは言え、ちゃんとレンダラーに拾われるようにはなってるし、ちゃんとReactNodeを返すのは確かだから、コンポーネントみたいに振舞えるのか

だから普通に<SomeContext.Provider>みたいにコンポーネントと同じように扱える

(レンダラーがどうProviderを拾ってるのか気になるがそれはまた今度)

Yug (やぐ)Yug (やぐ)

一応最後にConsumerも見とくか
https://ja.react.dev/reference/react/createContext#consumer

/**
 * The old way to read context, before {@link useContext} existed.
 *
 * @see {@link https://react.dev/reference/react/createContext#consumer React Docs}
 *
 * @example
 *
 * ```tsx
 * import { UserContext } from './user-context';
 *
 * function Avatar() {
 *   return (
 *     <UserContext.Consumer>
 *       {user => <img src={user.profileImage} alt={user.name} />}
 *     </UserContext.Consumer>
 *   );
 * }
 * ```
 */
type Consumer<T> = ExoticComponent<ConsumerProps<T>>;

あーラップ部分はまったく同じでpropsだけ違うのか

Yug (やぐ)Yug (やぐ)

ConsumerPropsを見る

/**
 * Describes the props accepted by a Context {@link Consumer}.
 *
 * @template T The type of the value the context provides.
 */
interface ConsumerProps<T> {
    children: (value: T) => ReactNode;
}

シンプルな構造。childrenだけ持ってる
childrenは、プロップスの値を受け取ってReactNodeを返す関数

このスクラップは4ヶ月前にクローズされました