首页 > 解决方案 > 无论如何设置一个变量等于每次访问变量时调用的函数

问题描述

我正在尝试从 React 重新创建 'useState' 钩子作为一个愚蠢有趣的个人练习,但是在从我的“全局状态”访问变量时遇到了麻烦。我知道这在 react 之外没有实际用途,但我只是认为无论如何都可以尝试。

目前我在下面有以下实现,但是因为解构变量仅在第一次设置并且在使用相应的设置器时没有更新,所以它总是会返回相同的变量。我完全理解为什么会发生这种情况,但我不确定是否有办法让它工作,或者这是否是一个失败的原因。解构的 setter 确实更新了全局状态,但变量当然如前所述不会再次访问全局状态,因为它只是在初始时间设置。

let PROP_ID = 0;
let GLOBAL_STATE = {};

const useState = prop => {
  const id = PROP_ID++;
  GLOBAL_STATE[id] = prop;

  return [
    (() => {
      return GLOBAL_STATE[id];
    })(),
    function(nv) {
      GLOBAL_STATE[id] = nv;
    }
  ];
};

const [userName, setUserName] = useState("Chris");
const [favCol, setFavCol] = useState("red");

console.log(GLOBAL_STATE);
console.log(userName);

setUserName("Bob");

console.dir(GLOBAL_STATE);
console.log(userName);

我只想知道是否有办法将解构的引用变量设置为某种函数,当引用该变量时,总是会调用该函数以从全局状态获取新变量。

标签: javascript

解决方案


我认为你在这里遗漏了一块拼图。

React 钩子取决于它们在给定功能组件中的调用位置。如果没有封装函数,您将删除钩子提供的状态的有用性,因为它们在您的示例中只被调用一次,因此解构语法中的引用永远不会像您观察到的那样更新。

让我们让它们在函数的上下文中工作。

const { component, useState } = (function () {
  const functions = new WeakMap()
  const stack = []
  let hooks
  let index

  function component (fn) {
    return function (...args) {
      try {
        stack.push({ hooks, index })
        hooks = functions.get(fn)
        index = 0

        if (!hooks) {
          functions.set(fn, hooks = [])
        }

        return fn.apply(this, args)
      } finally {
        ({ hooks, index } = stack.pop())
      }
    }
  }

  function useState (initialValue) {
    const hook = index++

    if (hook === hooks.length) {
      hooks.push(initialValue)
    }

    return [
      hooks[hook],
      function setState (action) {
        if (typeof action === 'function') {
          hooks[hook] = action(hooks[hook])
        } else {
          hooks[hook] = action
        }
      }
    ]
  }

  return { component, useState }
})()

const fibonacci = component(function () {
  const [a, setA] = useState(1)
  const [b, setB] = useState(1)

  setA(b)
  setB(a + b)

  return a
})

const sequence = component(function () {
  const [text, setText] = useState('')

  setText(
    text.length === 0
    ? fibonacci().toString()
    : [text, fibonacci()].join()
  )

  return text
})

for (let i = 0; i < 20; i++) {
  console.log(sequence())
}

这里的stack变量允许我们嵌套有状态的函数调用,并且该hooks变量通过当前执行componentstack.

component()这种实现可能看起来过于复杂,但重点stack是部分模仿 React 框架如何处理功能组件。这仍然比 React 的工作方式简单得多,因为我们将同一函数的所有调用视为函数组件的同一实例。

另一方面,在 React 中,一个特定的函数可以用于多个不同的实例,可以根据许多因素(例如虚拟 DOM 在层次结构中的位置、thekeyrefprops 等)相互区分,所以它是比这复杂得多。


在我看来,您只是想让您的示例正常工作。为此,您需要做的就是将变量更改为 getter 函数:

const useState = state => [
  () => state,
  value => { state = value }
];

const [getUserName, setUserName] = useState('Chris');
const [getFavCol, setFavCol] = useState('red');

console.log(getUserName());
setUserName('Bob');
console.log(getUserName());

比您拥有的要简单得多,并且不需要任何全局变量即可工作。

如果手动 getter 看起来太不方便,那么您就无法解构,但您可以实现一种几乎同样易于使用的方法:

const useState = state => ({
  get state () { return state },
  set (value) { state = value }
});

const userName = useState('Chris');
const favCol = useState('red');

console.log(userName.state);
userName.set('Bob');
console.log(userName.state);


推荐阅读