SolidJS 上下文提供程序规范

Typ*_*ypo 4 javascript jsx solid-js

我正在遵循教程中的上下文示例,我从示例中了解到的是使用自定义提供程序:

import { createSignal, createContext, useContext } from "solid-js";

const CounterContext = createContext();

export function CounterProvider(props) {
  const [count, setCount] = createSignal(props.count || 0),
    counter = [
      count,
      {
        increment() {
          setCount((c) => c + 1);
        },
        decrement() {
          setCount((c) => c - 1);
        },
      },
    ];

  return (
    <CounterContext.Provider value={counter}>
      {props.children}
    </CounterContext.Provider>
  );
}

export function useCounter() {
  return useContext(CounterContext);
}
Run Code Online (Sandbox Code Playgroud)

我有三个问题:

  1. 除了上面的示例之外,我找不到任何有关如何定义自定义上下文提供程序的规范,是否有任何标准或规范可供遵循?

  2. 此示例中 CounterContext 和 CounterProvider 之间的绑定在哪里?是在这一行吗?<CounterContext.Provider value={counter}>。与createSignal结合然后在计数器中使用?

所以依赖关系是:createSignal->counter->CounterProvider?

  1. 我找不到任何关于具有更复杂对象的 createContext 的 jsx 格式的上下文示例,只能使用打字稿语法。这是一个有效的例子吗?
const SomeContext = createContext({
  someProp: "defaultString",
  someAction: function(){
    console.log('something')
  }
});
Run Code Online (Sandbox Code Playgroud)

snn*_*snn 8

上下文是当前反应范围所有者的对象。它保留提供者设置的键值对。

owner.context = { [id]: value };
Run Code Online (Sandbox Code Playgroud)

当组件运行时,它会创建一个反应范围,从而创建一个新的所有者。我们稍后会详细阐述这一点。

Context API 由三个组件组成:

  1. createContext创建一个独特的id组件Provider
  2. Provider components uses the unique id to set a value on the context object.
  3. useContext returns the previously set value using the unique id passed to it. If current owner does not have the id-value pair, useContext checks owner's parent, then its grand parent, so on so forth until all ancestor owners are exhausted. If none of them has it, returns the default value.

Context is a way to pass values down the component tree without going through the component hierarchy. I used the term passing down, because people tend to explain how context works, but in reality it is the other way around: The useContext hook climbs up the component tree looking for a specific context value using the provided key.

Here is how Solid's context API works:

When you create context via createContext, you will be creating a unique id and a Provider component. That is it. You get an id and a component but you won't be setting anything yet.

Here is the actual code used in Solid:

function createContext(defaultValue) {
  const id = Symbol("context");
  return { id, Provider: createProvider(id), defaultValue };
}
Run Code Online (Sandbox Code Playgroud)

https://www.solidjs.com/docs/latest#createcontext

Provider component will use this id when setting a value, which we will do next:

<Context.Provider value={someValue}></Context.Provider>
Run Code Online (Sandbox Code Playgroud)

Context.Provider components runs createProvider function internally which in turn sets a value on the context object of the current owner:

Owner!.context = { [id]: props.value };
Run Code Online (Sandbox Code Playgroud)

Owner.context is an object because you can provide any number of context.

Owner is the reactive scope that owns the currently running code. Computations (effects, memos, etc.) create owners which are children of their owner, all the way up to the root owner created by createRoot or render. In this sense, components are also effects. When you create a component, you are creating an owner.

Owners are used to keep track of who creates whom, so that they will be disposed when their owner gets discarded. If you create an effect inside another effect, you will be creating an owner inside another owner. Basically it is a graph.

You can learn more about owner at:

https://www.solidjs.com/docs/latest/api#getowner

Now that we created context and set a value, it is time to access it. We use the useContext hook for that purpose.

The useContext hooks needs an id in order to look up a context value. When we pass the value that we get from the createContext function, we will be providing that id.

export function useContext<T>(context: Context<T>): T {
  let ctx;
  return (ctx = lookup(Owner, context.id)) !== undefined ? ctx : context.defaultValue;
}
Run Code Online (Sandbox Code Playgroud)

If you check the code, you will see it is a walker. The hook looks up its immediate owner for a value. If value is there, it will return it, if not, it will get the owner of the current owner, and look it up. So on and so forth, until there are no more owners to look up. In that case, default value is returned.

To reiterate, the useContext function is a walker that walks the graph of owners for a particular id on their context property.

That sums up how Solid's context API works.

The value you provide via Context API can be of any type. It could be static value or could be a signal like the one in your example.

The example looks complicated because an object with methods stored in the context. Lets try a simpler one.

owner.context = { [id]: value };
Run Code Online (Sandbox Code Playgroud)

If you do not provide a value, default value will be used:

function createContext(defaultValue) {
  const id = Symbol("context");
  return { id, Provider: createProvider(id), defaultValue };
}
Run Code Online (Sandbox Code Playgroud)

You can overwrite the context value at different levels of the component tree:

<Context.Provider value={someValue}></Context.Provider>
Run Code Online (Sandbox Code Playgroud)

Now, lets store a signal on the context and use in inside a child component:

Owner!.context = { [id]: props.value };
Run Code Online (Sandbox Code Playgroud)

Lets refactor the previous example. In this one, we will use undefined as the default value but overwrite it later with a getter and setter from a signal using a context provider:

export function useContext<T>(context: Context<T>): T {
  let ctx;
  return (ctx = lookup(Owner, context.id)) !== undefined ? ctx : context.defaultValue;
}
Run Code Online (Sandbox Code Playgroud)

Now it is time to implement the example you post. Yours is wrapped in a component called CounterProvider but I will post it plainly. You can move the logic into a component any time:

import { createContext, useContext } from 'solid-js';
import { render } from 'solid-js/web';

const CounterContex = createContext<number>(0);

const Child = () => {
  const count = useContext(CounterContex);
  return (
    <div>{count}</div>
  );
};

const App = () => {
  return (
    <div>
      <CounterContex.Provider value={10}>
        <Child />
      </CounterContex.Provider>
    </div>
  );
}

render(App, document.querySelector('#app'));
Run Code Online (Sandbox Code Playgroud)