用React渲染一个函数的详细指南

62 阅读4分钟

这个 "功能 "已从React 16中删除,所以请不要依赖它。尽管如此,这还是有点意思的,所以继续阅读吧

这周我在PayPal的一个内部模块上工作,不得不用React做一些有点古怪的事情。 我想你会有兴趣听听我学到了什么。

不,这并不是关于渲染道具。如果你希望这样......请稍后回拨😉

I see what you did there

语境

所以react-i18n (不是npm的那个......是我们在PayPal内部做的那个)有这样一个API,我称之为 "sorta-curried"。我在上次的新闻通讯中写了一些关于它的内容。

所以这里有一个例子:

import getContent, {init} from 'react-i18n'
init({
  content: {
    pages: {
      home: {
        nav: {
          about: 'About',
          contactUs: 'Contact us',
        },
      },
    },
  },
})

// here's the sorta-curried part...
// These all result in exactly the same thing: "About"
getContent('pages.home.nav.about')
getContent('pages')('home')('nav')('about')
getContent('pages.home')('nav.about')
getContent('pages')('home.nav')('about')
// etc...

如果你不喜欢这个API,你并不孤单。但是,API是有原因的,这不是我们在这篇文章中要讨论的......现在回想起来,这是个糟糕的API,我不应该这样做😅。

使用React

所以在React的背景下思考这个问题:

const getHomeContent = getContent('pages.home')
const ui = (
  <a href="/about">
    {getHomeContent('nav.about')}
  </a>
)
// that'll get you:
<a href="/about">About</a>

到目前为止还不错。但是,如果你搞砸了,出现了错别字怎么办

在本周之前,发生的情况是这样的:

const ui = (
  <a href="/about">
    {getContent('pages.typo.nav.about')}
  </a>
)
// that'll get you:
<a href="/about">{pages.typo.nav.about}</a>
// note, that's a string of "{" and "}"...
// not jsx interpolation...

的问题

所以,这很好。但是,这里是事情变得棘手的地方。因为我们返回的是一串{full.path.to.content} ,如果内容丢失或有错别字,你就不能对你返回的内容调用一个函数。如果你尝试,你将在一个字符串上调用一个函数,这将给你一个错误,使应用程序崩溃。错误边界可以帮助解决这个问题,尽管有时我们会在React上下文之外调用getContent ,所以这并不是在所有情况下都有帮助。无论如何,这将破坏应用程序:

const getHomeContent = getContent('pages.typo')
const ui = <a href="/about">{getHomeContent('nav.about')}</a>
// 💥 error 💥

同样,发生这种情况是因为getContent('pages.typo') 将返回字符串{pages.typo} (表示在该路径上没有内容,开发人员需要解决这个问题以获得内容)。问题是,你不能调用一个字符串,但这就是正在发生的事情,因为getHomeContent 是一个字符串,而不是一个函数。

一个解决方案和一个新问题

因此,我本周所做的改变使它在给定的路径上没有内容时,不再是一个字符串,而是返回一个 "sorta-curried "函数(就像你没有打错字时那样)。这样,如果你想的话,你可以一整天都在调用它。没问题。

所以现在这不会出错,但如果没有内容,我们就会失去渲染路径:

const getHomeContent = getContent('pages.typo')
const ui = (
  <a href="/about">
    {getHomeContent('nav.about')}
  </a>
)
// that'll get you:
<a href="/about"></a>

我们要确保显示缺失的内容,这样对开发者来说就更明显了(是的,我们也会记录到控制台),如果世界上发生了🔥🌎🔥🌏🔥🌍🔥,而内容由于某种原因未能加载,那么一个按钮说{pages.transfer.sendMoney} 总比什么都不说要好。

所以,挑战就在这里了。让我们重写一下上面的内容,使之更加清晰。

const getHomeContent = getContent('pages.typo')
const aboutContent = getHomeContent('nav.about')
const ui = <a href="/about">{aboutContent}</a>

aboutContent 在这个例子中是一个函数,因为对 的调用有一个错字,所以我们实际上永远找不到与完整路径相匹配的内容。因此,挑战是我们如何确保在这样的情况下能够呈现完整的路径?getContent

开发解决方案

起初,我以为我可以在内容获取器函数上做猴子补丁toString 。但这并不奏效。我还是从React得到了这个警告。

警告。函数作为React的一个子节点是无效的。如果你返回一个Component而不是从render中返回,这可能会发生。或者你是想调用这个函数而不是返回它。

因此,我在记录该错误的那一行设置了一个断点,并在堆栈中逐步查找问题所在。

> printWarning
> warning
> warnOnFunctionType
> reconcileChildFibers <-- ding ding! 🔔

这个reconcileChildFibers 函数是我发现react会检查你试图渲染的子对象,以确保它们是可渲染的。

纵观这段代码,它首先检查它是否是一个对象,然后检查它是否是一个字符串或数字,然后是一个数组,然后是一个迭代器。如果它不是这些东西,那么它就会抛出(对于非ReactElement对象)或警告(对于一个函数,像我们的例子)。

所以,在我的例子中,由于前面提到的限制,我想要渲染的东西必须是一个函数。所以我不能让它作为一个对象、字符串、数字或数组工作。但我意识到,没有什么能阻止我使我的函数可迭代(如果你不熟悉,这里是我的ES6工作坊录音中的迭代器部分)。

所以......我让我的函数可迭代😉。

easy button

const ITERATOR_SYMBOL =
  (typeof Symbol === 'function' && Symbol.iterator) || '@@iterator'

// ...

function iterator() {
  let timesCalled = 0
  // useful logging happens here...
  return {
    next() {
      // this is called twice. Once to get the value, and the second time
      // will report that it's done.
      return {done: timesCalled++ > 0, value: pathAsString}
    },
  }
}

// ...

contentGetterFn[ITERATOR_SYMBOL] = iterator

// ...

我为此做了一个方便的函数,并创建了一个CodeSandbox演示,供你试用!请欣赏

You're welcome

这也是一个很酷的事情,我可以用一堆上下文来记录一个错误,以帮助开发者弄清楚发生了什么。这是可能的,因为如果调用iterator ,我可以假设React正在尝试渲染contentGetterFn

所以,这就是我让函数可迭代的用例😉。

我希望这对你有帮助,也很有趣!祝您好运!