Hello, I’m Carson.

Do you hate the fact that Hooks cannot be written in conditional statements?

Have you ever used a state in a UseEffect and forgotten to add it to a dependency, causing the timing of a UseEffect callback to fail?

Blame yourself for carelessness? Blame yourself for not looking at your documents?

Promise me you won’t blame yourself.

The root cause is that React does not implement Hooks as responsive updates.

Is it difficult to implement? This article uses 50 lines of code to implement an unlimited set of Hooks, which are the underlying principles of libraries based on reactive updates, such as VUE and Mobx.

The proper way to eat this article is to bookmark it and read it on your computer, then type in the code with me (see the end of this article for a link to the full online Demo).

If the mobile phone party looked Meng force, don’t blame yourself, is your way of eating wrong.

Note: This code is derived from Ryan Carniato’s article
Building a Reactive Library from Scratch


Brother is
SolidJSThe author

Great oaks from little acorns grow

First, implement UseState:

function useState(value) {
  const getter = () => value;
  const setter = (newValue) => value = newValue;
  
  return [getter, setter];
}

The first item in the return array is responsible for the value, and the second is responsible for the assignment. Here’s a small change from React: The first argument to the return value is a function, not the state itself.

Use as follows:

const [count, setCount] = useState(0); console.log(count()); // 0 setCount(1); console.log(count()); / / 1

No Dark Magic

Next, implement UseEffect, which consists of several points:

  • Rely on thestateChange,useEffectThe callback execution
  • There is no need to explicitly specify dependencies (i.eReactIn theuseEffectThe second parameter of the

Here’s an example:

const [count, setCount] = useState(0); useEffect(() => { window.title = count(); }) useEffect(() = bb0 {console.log(' Nothing to me ')})

The first useEffect executes a callback after the count changes (because it internally relies on the count), but the second useEffect does not.

There’s no dark magic on the front end, so how does this work?

The answer is: subscribe to publish.

Continue with the example above to explain the timing of the subscription publishing relationship:

const [count, setCount] = useState(0);

useEffect(() => {
  window.title = count();
})

When useEffect is defined, its callback is executed immediately, internally:

window.title = count();

When count is executed, a subscription publication relationship between effect and state is established.

The next time setCount (setter) is executed, the useEffect subscribed to the count change is notified and its callback is executed.

The relationship between data structures is as follows:

Inside each UseState there is a collection of SUBs to hold the effects that subscribe to the state changes.

Effect is the corresponding data structure for each UseEffect:

const effect = {
  execute,
  deps: new Set()
}

Among them:

  • execute: theuseEffectThe callback function of
  • deps: theuseEffectRely on thestateThe correspondingsubsA collection of

I know you’re a little dizzy. Take a look at the structure above. Slowly, let’s continue.

Implement useEffect

First, you need a stack to hold the currently executing effect. This way the state knows which effect to associate with when it calls the getter.

Here’s an example:

// effect1 useEffect(() => { window.title = count(); (()}) / / effect2 useEffect = > {the console. The log (' didn't I what thing ')})

In order to be associated with effect1, count needs to execute knowing that it is in the context of effect1 (not effect2).

// The stack that is currently executing effectStack is const effectStack = [];

Next, implement UseEffect, including the following function points:

  • Every timeuseEffectReset the dependency before the callback executes (inside the callbackstatethegetterWill restore the dependency.)
  • Ensure the current when the callback is executedeffectineffectStackTo the top of the stack
  • After the callback is executed it will be currenteffectEject from the top of the stack

The code is as follows:

Function useEffect(callback) {const execute = () => {function useEffect(callback) {const execute = () =>; // Push the top of the stack effectStack.push(effect); try { callback(); } finally {// EffectStack.pop (); } const effect = {execute, deps: new Set()} // Execute (); }

Cleanup is used to remove any association between this effect and any states it depends on, including:

  • Subscription relationship: will theeffectAll subscribed tostateChange to remove
  • Dependency: Put theeffectDependent on allstateremove
Function cleanup(effect) {for (const dep of effect.deps) {dep.delete(effect); } // Removes all states dependent on this effect from effect.deps.clear(); }

Once removed, the useEffect callback will re-establish the relationship one by one.

Transform useState

Next, transform UseState to complete the logic of establishing subscription and publishing relationship. The key points are as follows:

  • callgetterGets the current context wheneffectBuild relationships
  • callsetterAll subscribers are notified when thestateChanges in theeffectThe callback execution
Function useState(value) {const subs = new Set(); Const getter = () => {// Get the current context const effect = effectStack[effectStack.length-1]; If (effect) {// Subscribe (effect, subs); } return value; } const setter = (nextValue) => { value = nextValue; // For (const sub of [...subs]) {sub.execute(); } } return [getter, setter]; }

Subscribe implementation also includes the establishment of two relationships:

Function subscribe(effect, subs) {subs. Add (effect); // create a dependency on effect.deps.add(subs); }

Let’s try it out:

const [name1, setName1] = useState('KaSong'); UseEffect (() => console.log(' Who's there! ', name1()))) // Print: Who's there! KaSong setName1('KaKaSong'); // Print: Who is there? KaKaSong

Implement useMemo

Next, implement UseMemo based on the existing 2 hooks:

function useMemo(callback) {
  const [s, set] = useState();
  useEffect(() => set(callback()));
  return s;
}

Automatic dependency tracking

This set of 50 lines of Hooks also has a powerful hidden feature: automatic dependency tracing.

Let’s extend the example above:

const [name1, setName1] = useState('KaSong'); const [name2, setName2] = useState('XiaoMing'); const [showAll, triggerShowAll] = useState(true); const whoIsHere = useMemo(() => { if (! showAll()) { return name1(); } return '${name1()}'; }) useEffect(() => console.log(' Who's there! ', whoIsHere()))

Now we have three states: name1, name2, and showAll.

Whoishere as memo depends on the above three states.

Finally, the useEffect callback is triggered when the whoIsHere changes.

When the above code runs, based on the initial three states, a whoIsHere is calculated and the useEffect callback is triggered to print:

// Print: Who is there? KaSong and XiaoMing

Next call:

setName1('KaKaSong'); // Print: Who is there? KaKaSong and XiaoMing triggerShowAll (false); // Print: Who is there? KaKaSong

Here’s where things get interesting when called:

setName2('XiaoHong');

There’s no log printing.

This is because when triggerShowAll(false) causes showAll state to be false, whoIsHere enters the following logic:

if (! showAll()) { return name1(); }

Since name2 is not executed, name2 has no subscription publishing relationship with whoIsHere!

Only after triggerShowAll(true) does whoIsHere enter the following logic:

Return ` ${name1 ()} and ${name2 ()} `;

That’s when Whoishere returns to name1 and name2.

Automatic dependency tracking, isn’t that cool

conclusion

At this point, based on subscription publishing, we have implemented unrestricted Hooks that automatically rely on tracing.

Has this concept only been used in the last few years?

KnockoutJS has implemented responsive updates in this fine-grained way since early 2010.

I wonder if Steve Sanderson (the author of KnockoutJS) could have foreseen that, 10 years later, fine-grained updates would be widely used in libraries and frameworks.

Here’s the link to the full online Demo