React: 如何使用this.context实现shouldComponentUpdate?

创建于 2014-11-13  ·  126评论  ·  资料来源: facebook/react

我知道this.context并没有正式出现,但是很多图书馆都依赖它,而且看起来好像#2509已经成形。

我试图了解shouldComponentUpdate context在考虑nextContext ),我可以扩展PureRenderMixin来检查它:

  shouldComponentUpdate: function(nextProps, nextState, nextContext) {
    return !shallowEqual(this.props, nextProps) ||
           !shallowEqual(this.state, nextState) ||
           !shallowEqual(this.context, nextContext); // this will throw without context, read on
  }

通过不省略contextTypes而没有选择加入this.context的组件将不会得到第三个参数,这是可以理解的。

但是,当我们在<Top />上下文所有者和<Bottom />上下文使用者之间具有<Middle />组件时,这会带来问题。 如果<Middle />实现了限制性shouldComponentUpdate ,则<Bottom />根本无法对<Top />的上下文更新作出反应:

小提琴

var Bottom = React.createClass({
  contextTypes: {
    number: React.PropTypes.number.isRequired
  },

  render: function () {
    return <h1>{this.context.number}</h1>
  }
});

var Middle = React.createClass({
  shouldComponentUpdate: function (nextProps, nextState, nextContext) {
    return false;
  },

  render: function () {
    return <Bottom />;
  }
});

var Top = React.createClass({
  childContextTypes: {
    number: React.PropTypes.number.isRequired
  },

  getInitialState: function () {
    return { number: 0 };
  },

  getChildContext: function () {
    return { number: this.state.number };
  },

  componentDidMount: function () {
    setInterval(function () {
      this.setState({
        number: this.state.number + 1
      });
    }.bind(this), 1000);
  },

  render: function() {
    return <Middle />;    
  }
});

React.render(<Top />, document.body);

如果我试图给Middle一个通用的上下文感知shouldComponentUpdate就像我在上面写的那样,也会发生同样的问题,因为除非选择,否则Middle没有this.context在。

通过将contextTypesMiddle ,但这似乎不是一个好的解决方案。 您需要使用智能的shouldComponentUpdate在每个级别上显式添加必要的contextTypes ,所以它很容易滑倒。

#2112是否可以解决? 同时有其他解决方案吗? 推荐的方法是什么?

Component API Bug

最有用的评论

我想知道是否可以在单独的树遍历中进行上下文传播,而不会被中间的虚假shouldComponentUpdate阻止?

基本上,当父级的上下文更改时,无论其距离有多远,它都应将接收此上下文的所有后代标记为脏。 对于选择加入此上下文的后代,祖先的上下文更改应具有与状态更改相同的效果-无论父母怎么说,他们都应获得新的context

所有126条评论

这是一个非常好的问题。 感谢您提出来!

在这种情况下,您将手动修剪/优化树的重新计算。 在我看来,您需要请求访问与您的计算相关的任何上下文变量。 我们还可以考虑做其他一些花哨的事情,例如在必要时传递完整上下文(甚至完整上下文)的哈希值。

@sebmarkbage-有何想法?

是的,问题在于中间组件不知道某个遥远的孩子可能需要什么样的环境。 他们无法知道要声明哪个contextTypes甚至_能够_正确实现shouldComponentUpdate

我想知道是否可以在单独的树遍历中进行上下文传播,而不会被中间的虚假shouldComponentUpdate阻止?

基本上,当父级的上下文更改时,无论其距离有多远,它都应将接收此上下文的所有后代标记为脏。 对于选择加入此上下文的后代,祖先的上下文更改应具有与状态更改相同的效果-无论父母怎么说,他们都应获得新的context

@gaearon我认为在这种情况下,您需要重新渲染所有内容,因此shouldComponentUpdate不会具有修剪子树的效果。 否则上下文将与元素树处于不一致状态。

@andreypopp

我认为中间组件上的shouldComponentUpdate对其子代是否接收新上下文不起作用,就像它对它们是否接收新状态没有影响一样:

http://jsbin.com/geseduneso/2/edit?js ,输出

(如果是这种情况,两个数字都将递增)

不一致之处在哪里?

换句话说,我认为context工作原理应该与上下文所有者拥有类似“存储”之类的东西一样,将其getChildContext()的结果写入componentWillUpdate ,并且所有在contextTypes从该上下文声明了键的后代上下文消费者,都应该像接收到该“商店”并更新自己的状态一样接收该上下文。

我并不是说实际的实现,但我想证明的是,该模型并不比中间带有shouldComponentUpdate任何Flux应用程序更加不一致。 上下文应像“横向存储”一样,但范围仅限于子树。

编辑:这将不起作用,因为父母可能会改变

我在IRC上与Glenjamin进行了交谈,他说服我改变环境本身可能不是一个好主意。 如果某个根更新隐式导致不同的子更新,则您将无法推理为什么要更新某些内容。

但是,然后,我看到的唯一合理的解决方案是完全禁止上下文更改。 也就是说,使getChildContext()getInitialState() getChildContext()类似,在装入组件之前只会调用一次。

这将使上下文变得简单得多。 我们可以从shouldComponentUpdate删除第三个参数,因为上下文永远不会更新。

如果您需要上下文所有者的更新(例如,像react-router希望自上而下地传递activeRoutes以在ActiveState mixin cc @mjackson中使用),则没有什么阻止您传递{ addChangeListener, removeChangeListener, getActiveRoutes } context 。 后代现在可以订阅更改并将其放入state

这是一个合理的解决方案吗?

我认为要回答的关键问题是:

在哪种情况下,通过context传递_data_优于通过props传递数据或触发事件以使组件调用其自己的setState

我一直在使用上下文愉快地传递对象引用,因为我只写那些对象,而不读。 例如。 this.context.triggerAction("something")

上下文的用例是适用于可能在较大子树中使用的参数,但您不想通过更通用的容器节点传递这些参数。 一个示例可能是颜色主题,其中大量节点可能会监听以查看背景颜色是白色还是黑色。 您不希望将它们作为参数传递给任何地方。

使getChildContext()的行为更像getInitialState()实际上并不能解决问题,因为您始终可以将提供给定上下文值的父节点替换为提供不同上下文值的另一个父节点。 对于受影响的子树,这与更改上下文变量的值是无法区分的。

我认为我们可以找到一种避免用户连接变更侦听器的解决方案。

我认为@andreypopp可能是正确的。 或者至少,我们需要为shouldComponentUpdate提供一种方法来知道上下文中的任何内容是否已更改,以便可以确定如果上下文变量发生更改,则始终返回true。

今天晚些时候我将与@sebmarkbage聊天,看看他的想法。

使getChildContext()的行为更像getInitialState()实际上并不能解决问题,因为您始终可以将提供给定上下文值的父节点替换为提供不同上下文值的另一个父节点。 对于受影响的子树,这与更改上下文变量的值是无法区分的。

哎哟。 您完全正确,我还没有考虑过。

@jsfb再说一遍,我不太理解您的评论。 如果替换父节点,则整个子子树都将重新安装,不是吗?

当前,是的,它将重新安装,但这部分是实现细节。 您可以想象(并且我们一直在讨论其实现和后果)在不丢失节点状态的情况下重设子树。

我和@sebmarkbage聊天,得出以下结论:

  • shouldComponentUpdate是一个复杂的逃生舱口,其实现要求您对组件下正在发生的事情有深入的了解。 因此,假定您知道正在使用哪些上下文变量并不是没有道理的,因为您已经需要知道正在使用哪些属性以及如何使用它们。
  • 这种变化可能不会使情况变得比现在更糟,它是对使用旧的“所有者”关系的一种改进。 因此,这个问题仍然值得讨论,但可能是无障碍的。
  • 目前,上下文是一项复杂的功能/实验,在正式支持/记录文档之前,我们可能需要做更多的思考。 随着我们学到更多,我们可能会继续迭代这个话题。

@sebmarkbage ,让我知道是否错过了任何事情!

很好的讨论! 感谢您的所有反馈!

感谢您抽出宝贵时间来讨论这个问题!

假定您知道正在使用哪些上下文变量并不是没有道理的,因为您已经需要知道正在使用哪些属性以及如何使用它们。

如果顶级Feed组件通过使用PureRenderMixin来实现shouldComponentUpdate PureRenderMixin来避免进行额外的更新,这并不意味着Feed知道其中的某个地方是一个Cell ,恰好包含一个取决于路由器上下文的Link

当框架(例如最流行的React路由框架)使用上下文时,情况甚至变得更糟,您甚至可能不知道它。 冥冥之中,有没有改变积极的链路状态,因为有人优化顶层组件和字面上_No idea_他们不得不宣布相应的应用程序contextTypes以_even的get_ nextContext在他们的shouldComponentUpdate

组件无法知道它们的所有后代。 基本上,如果我将上下文相关的组件移动到启用了PureRenderMixin组件内的任何位置,它将破坏但非常微妙。 因此,如果使用上下文,唯一避免这种无关组件微妙破坏的方法是_never_实现shouldComponentUpdate ,这与React所说的

在React之上的一些框架,例如@swannodette的Om,使PureRenderMixin -ish shouldComponentUpdate _default_像这样被切断是很奇怪的。 这意味着在每个组件中,上下文中断shouldComponentUpdate

我同意这不会妨碍您的工作,但是如果完全使用上下文,我不能同意当前的情况令人满意。 现在,要在上下文中实现shouldComponentUpdate不仅是艰巨的任务,而且完全不可能,除非我们假设每个组件始终知道其children可能是什么。

流行的图书馆已经严重依赖上下文。 我了解它不是受支持的功能,但我认为它必须至少是_possible_才能与shouldComponentUpdate ,或者应该将其禁用,并且应该强制库不使用它,因为它被巧妙地打破了。

从介绍上下文开始就已经知道这一点。 我们需要能够将未记录且不受支持的功能作为实验功能,以便能够对它们进行迭代以查找特殊情况。 例如,如果我们没有上下文,就不会知道它们需要更改为基于容器而不是基于所有者。 微妙的损坏是使用未记录功能的合同的一部分。

我认为如果父树中任何地方都有新的上下文,我们需要做的是绕过shouldComponentUpdate 。 可能带有shouldUpdateChildContext或用于确定整个子树是否需要协调的内容。

@sebmarkbage

我想这可能行得通,因为上下文几乎不需要经常更改。
在这种情况下, shouldComponentUpdate不需要第三个参数,对吗?

是的,这总是没用的。

我认为,如果父树中的任何地方都有新的上下文,我们需要做的是绕过shouldComponentUpdate。

这很有道理。 父级中的上下文更改需要覆盖子树中的虚假shouldComponentUpdate s。

但这引出了一个问题:我们如何知道上下文何时发生变化? 对于状态和道具,我们有setStaterender / setProps 。 似乎我们需要在某处保留当前上下文的副本,并在每次调用render时将其与getChildContext的结果进行比较。

我将上下文用作管理商店引用的一种方式。 我喜欢这样,因为我可以明确定义所需的存储,因此
1)我可以在需要测试组件的商店中模拟出样本数据,而不必实例化我的大型框架,并且
2)将带有已知数据的简单只读存储传递给服务器端呈现,这简化了服务器端的情况,因为未使用大量应用程序代码。

所以,看一个例子,

在我的实现中,只能通过getter访问存储数据。 我的元素不缓存存储数据。 我想要一个事实的单一版本。 作为一个简单的非异步示例,我的render()通常具有类似

var peopleStore = this.context.peopleStore;
var person = peopleStore.get(this.props.personId);
return <div>person.fullName</div>;

组件将侦听器附加到商店。 我还没有完全确定粒度。 所有商店都有一个onChange事件。 但是,我还没有决定其他两件事,
1)监听器,用于单个属性的更改
2)如果商店应包含商店

在此示例中,如果“ people”是“ fb users”,则它是一个大型的复杂异步存储。 我应该为PersonStore重用商店结构吗? 一个用于一般集合(getFriends,getPerson等),但单个人的Person存储类型的许多唯一实例。

因此,在我的示例中,我的组件需要People存储作为上下文参数。 然后,它使用personId属性识别并订阅特定的Person商店。

现在,介绍一些动态更改。 假设当前登录的用户注销,其他人登录。忽略了您可能会在真实应用程序中重定向/刷新页面的事实,此元素将如何更新? 我们还假设此元素仍在页面上,而不仅仅是被销毁。

我希望应用程序逻辑首先删除/销毁现有的People商店。 为此,我的组件需要停止监听更新。 为此,我建议使用ReactClass API。 例如,

onContextChange(上一个,新)

然后,该元素可以比较两个peopleStore实例。 让我们忽略公共数据,并假设新的PeopleStore为空。 该元素将取消订阅先前的Store并触发render()。 然后,渲染将显示一些“用户未知”类型的消息。

当用户以其他用户身份登录时,将创建一个新的Store,该元素重新附加,并且render()使用新数据来完成其工作。

在后台,“ this.render()”无法同步。 为了使我的任何设计/示例都毫无意义,render()调用需要由框架收集并分批处理。

与道具不同,监听商店不在管理渲染的核心React角色之外。 这就是为什么我不认为应该在shouldComponentUpdate()中进行上下文更改。 尽管我的示例包括更改上下文值(一个新的存储对象),但我认为存储在其高级性质上不会是一成不变的。 我认为典型的流量应用程序设计将与订户模型,异步回调等一起使用。基本存储对象通常在应用程序的生命周期内有效。

人们对此颇有兴趣。 (请参阅上面的参考。)

是的@gaearon

我想在上下文中传递本地化数据,并让用户能够在运行时更改其语言,让所有组件使用更新的翻译,货币,日期格式重新呈现自己。据我所知,这似乎现在很难做。

也可以看看
https://github.com/yahoo/react-intl/issues/58
https://github.com/facebook/react/issues/3038

@slorber我还采用了自定义PureRenderMixin ,显然它会在中间被更严格的shouldComponentUpdate s破坏,如

@gpbl检查:
https://github.com/facebook/react/issues/3038#issuecomment -76449195

在我看来,万一我们想在上下文更改的情况下从顶部重新渲染整个应用程序,我们可以在同一事件循环滴答中卸载并重新装载该节点,并且不会(恒定)产生闪烁效果。 这是处理用户语言更改的理想解决方法。

@slorber调用unmountComponentAtNode会带来糟糕的用户体验,因为这会导致所有本地状态的丢失。

我认为是时候宣布上下文为官方功能了。 对于大多数人来说,不使用上下文不是一个选择,因为路由器和ReactIntl​​使用它。

@cody等人,明确一点:上下文仍然会发生变化。 我们仍在尝试使用它,并建议您在决定最终API之前避免使用它。 使用后果自负。 是否有用:可能。 准备好了吗?

我认为,如果父树中的任何地方都有新的上下文,我们需要做的是绕过shouldComponentUpdate。 可能带有shouldUpdateChildContext或确定整个子树是否需要协调的内容。

是的, shouldUpdateChildContext使它变得漂亮且对称。

有机会变成0.14吗?

如果您设计一个不错的API并实现它。 :)

它应该包含一些方法来确定只有正在侦听实际上已更改的上下文键的孩子才真正被更新。 而不是一切。

我们的全职核心团队不可能有时间去做。 :(

2015年4月9日,下午1:32,Dan Abramov [email protected]写道:

有机会变成0.14吗?

-
直接回复此电子邮件或在GitHub上查看。

当然,我来看看!

抱歉,我仍然找不到时间开始研究。 在即将发布的React DnD 1.0发布之后,我将忙于准备conf,因此我不太可能很快就可以进行此工作。 :-(

@gaearon有什么计划要尽快看看吗? 我对此感兴趣,因为这似乎是使上下文完全可用的最后部分。 (可能是我想念的其他东西吗?)

我想知道的一件事是,以下陈述是否正确:

组件的子上下文应始终减少其状态道具和上下文

这确保了我们可以在已知生命周期内可靠地传输数据。 这类似于“组件的渲染始终是减少状态和道具”的说法。

这就是我目前的设想方式-很好看我是否完全偏离了路线!:

shouldUpdateChildContext确定是否应使用新上下文更新子树。 生命周期的这一部分是由组件状态道具或上下文的更改触发的。

shouldComponentUpdate仍然具有3个参数,因此上述子树中的组件可以决定是否实际更新任何内容(注意:在此处返回false不会进一步影响上下文在子树中的流动)

我想知道的另一件事是,是否应该使用另一种新的生命周期方法componentWillReceiveContext ? -在与componentWillReceiveProps类似的时间调用,但在上下文数据流中。 可以在“相同时间”调用这两种方法。

@Chrisui

请跳上它! 我现在正忙于其他事情。

这确保了我们可以在已知生命周期内可靠地传输数据。

还请记住,有一个observe钩子可能会可能不会在0.14中实现。 这意味着无论调用什么,都可能导出或不导出data / observed /。 但这可能与您目前的任务无关。

我的叉子上有一个非常初步的草稿,您可以看到它以examples/context/index.html

我已经建立了一个简单的“上下文父级” /“上下文子级”关系。 “上下文父级”是可能更改其子级上下文(即,实现childContextTypes )的任何组件,而“上下文子级”是依赖于上下文或可能更改其子级上下文(即,实现contextTypes任何组件)的组件。 childContextTypes )。 当前安装组件时会建立此关系。

当组件更新生命周期发挥作用时,我们有两种情况:

  1. 如果组件说的是_not_更新(即shouldComponentUpdate() === false ),则我们检查是否应使用shouldUpdateChildContext更新子上下文。 如果这是true (默认值),则如果该组件具有任何直接的“上下文子代”,我们会将子代组件转换为更新生命周期。 这一直持续到我们到达上下文树的末尾或遇到shouldUpdateChildContext() === false
  2. 如果某个组件说要更新,那么没有什么不同。 在这种情况下,上下文数据没有旁通通道,我们让常规流程继续进行,直到组件再次遇到案例#1。

毫无疑问,我已经错过了一些重要的东西,因此,如果有人可以快速浏览一下,那就太好了。 欢迎对上下文的体系结构发表评论,并实际编写适合React代码库的代码! :)

对于快速伪示例,请考虑以下内容:

<Root num={1}>  // Gives context {num: this.props.num}
  <Mid>         // Ignores any context and shouldComponentUpdate() === false
    <Child>     // Requires context {num: propTypes.number}

对于初始渲染(安装),一切都与我们期望的一样。 (示例详细信息:在RootChild之间创建了父/子关系)

我们更新Rootnum的道具价值2

<Root num={2} />

Mid还实现了shouldComponentUpdate()方法,该方法返回false因为它是render()方法,与context.num无关,其他都没有改变-那为什么要更新呢?

在旧情况下,这是Child组件永远看不到新上下文的地方,因为我们已经放弃了在树下进行更新。

通过这些更改,我们现在检查Mid上的shouldUpdateChildContext()方法,该方法可以(我们在示例中进行了默认操作)执行简单的相等性检查,以查看是否有任何上下文值具有改变了。 注意: shouldUpdateChildContext()的签名当前与shouldComponentUpdate()

如果shouldUpdateChildContext()true (默认情况下),那么如果之前建立了任何关系,我们将更新每个最近的“上下文子代”(在示例情况下仅为Child ) 。 这会将组件放入其更新生命周期中(与我之前看到的讨论相反,此操作将照常_still_调用shouldComponentUpdate()并使用_new_上下文作为第三个参数,因为这为组件提供了对何时需要的细粒度控制更新)。

希望这足以解释该过程!

@Chrisui Cool,很

  • 验证子项更新,即使父项shouldComponentUpdate()返回false。
  • 验证子项更新,即使父项shouldUpdateChildContext()返回false。
  • 如果组件shouldUpdateChildContext()返回false,则验证子代不会更新。

此外,如果这些功能受支持,我们可能要验证:

  • 验证如果祖父母更改其提供的上下文时不会调用shouldUpdateChildContext() ,但是直接父级覆盖该上下文并且不会更改。
  • 如果组件未从已更改的上下文变量中读取,请验证没有调用shouldUpdateChildContext()
  • 验证如果prop更改且上下文变量更改,我们不会更新/渲染两次。

抄送: @sebmarkbage,以获取有关API设计/想法的反馈。

@Chrisui到目前为止,这听起来棒极了,谢谢您完成这项工作。

@jimfb肯定会开始接受一些单元测试-只是想玩一些东西并确认我们所有人在那之前应该如何工作!

关于第二组情况,这些是我为了简化设计而避免的领域。 例如,有人建议我们注意查看哪些特定的上下文密钥已更改,仅对其进行更新。 我觉得这可能会给当前的父/子关系增加太多的复杂性,并且与其他数据通过React的流动方式不那么对称。 例如(我可能很天真并且忽略了这一点),我们没有在内部比较变化的道具,而是让用户明确定义两个单独的数据源(即当前道具和下一个道具)是否应该引起更新或仅仅是忽略了。

如果人们强烈认为应该执行此详细信息,那么最好在此详细讨论一下。

至于这个的简单实现,如果我们确实想继续进行下去,我想您会在合并树时与contextTypes进行比较, childContextTypes个组件,以找到有效的更新目标。 或者,也许我们可以以某种方式在挂载上做一些魔术,并仅在子项和具有匹配上下文子项键的最近的父项之间创建父/子关系。 我觉得后者将使以后很难管理更新顺序,即使不是没有可能。

我将快速进行黑客攻击,并尝试将其模拟出来!

实际上,就我对功能的特定上下文关键字比较部分的后一个建议而言,如果仅遵循已经使用的按安装顺序批处理逻辑排序的更新顺序,那么维护更新顺序可能不会那么棘手!

@Chrisui @sebmarkbage因此,我的担心(除非我忘记/遗漏了一些明显的东西)是,每次上下文提供者重新渲染时,它将导致所有(可能数百个)依赖于提供的上下文变量的子组件即使上下文变量未更改,也要重新渲染。 我的直觉是,仅当我们已经有了某种指示新值可用的指示(例如新值不等于旧值的三倍或带有某些标志/通知)时,才应该触发上下文重新传播。

实际上,此实现开始看起来像订阅系统。 由于context实际上是作用域范围内的全局变量,所以正确的解决方案可能是要求组件侧向订阅上下文变量,从而使我们能够将与所有其他全局订阅相同的机制用于上下文数据。 见https://github.com/facebook/react/issues/3398https://github.com/facebook/react/issues/3858https://github.com/facebook/react/pull/3920为相关信息。

戳@sebmarkbage。 我认为我的最新想法是使用我们的横向数据加载解决方案来解决此问题,从而避免API表面积。 我很好奇您的想法。

@jimfb关于重新渲染成百上千个组件,您是正确的,但是如果您触发一些状态更改并必须实现shouldComponentUpdate来防止大规模更新发生,我不确定它与现在有什么不同。

我同意,尽管上下文看起来只是特定的辅助数据流。 如果这些提议可以产生上下文的连锁反应,我认为没有理由像现在这样真正地发展上下文。 让我们看看别人的想法!

出于兴趣,我在草稿中添加了功能,以仅在组件具有匹配的上下文/子上下文密钥(防止重复的树遍历)时在组件之间创建父/子关系,而仅在上下文没有实际更改的情况下阻止更新一如既往的PureRenderMixin。

@Chrisui我认为区别在于子组件中的单个shouldComponentUpdate可以切断树的巨大分支,而在上下文中,查找/修复将要重新渲染的所有位置要困难得多。 即使您纾困,依赖于上下文变量的孩子仍然会重新渲染。

是的,我很好奇其他人对使用横向数据加载解决方案的看法。

我认为上下文通常与横向数据加载正交

理想情况下,我想要某种方式将横向数据存储的句柄传递给非全局的组件-这主要是我使用上下文的方式。

像这样将上下文用于DI可以降低在应用程序过程中上下文发生变化的可能性。

2015年5月26日,22:10,Jim [email protected]写道:

@Chrisui我认为区别在于子组件中的单个shouldComponentUpdate可以切断树的巨大分支,而在上下文中,查找/修复将要重新渲染的所有位置要困难得多。 即使您纾困,依赖于上下文变量的孩子仍然会重新渲染。

是的,我很好奇其他人对使用横向数据加载解决方案的看法。

-
直接回复此电子邮件或在GitHub上查看。

似乎所有有关使用上下文的当前建议(除“不要”之外)都将适用,其中频繁更改的任何内容都会招致相当高的重新渲染成本。 以非常有针对性的建议方式增加成本并不一定会改变人们对使用成本的看法。 另外,如果您想切断一棵树的分支,它仍然非常简单:

class Mid extends Component {
  shouldComponentUpdate() { return false; }
  shouldUpdateChildContext() { return false; }
  ...
}

我对上下文的相对简单感到满意,因为上下文+ shouldUpdateChildContext。 希望将基本机制更改为与其他全局订阅一致,不会太大地降低其易用性。

@jimfb
正如@eplawless指出的,shouldUpdateChildContext可用于阻止要更新的树的巨大分支。

我认为上下文api(具有建议的更改)非常简单,我们现在可以实现,并更改内部结构以使用稍后可能支持的任何辅助数据加载。

另一个建议:我们还可以将shouldUpdateChildContext()缺省设置为false,以默认情况下防止此新行为并使它选择加入。

为了进行迭代并进行一些测试(以相同的方式,上下文功能一直存在-因此未记录),我们是否应该将此API提案的价格设为0.14? (很高兴听到门卫的消息!)

我为你打开了一个公关: https :

与PR相比,与PR相比,PR更易于审核,并且优先于问题,因此您很可能会收到有关PR的快速反馈。

您知道我认为shouldUpdateChildContext本来是错误的。 我们真的需要吗? 对不起,rash。

shouldComponentUpdate似乎是一种策略,使孩子有责任确定某些事情是否已更改。 例如,它可能不会使用您提供的所有数据来确定其输出。 作为父母,你不知道。 也许就足够了。

我也认为我们可能希望将parent-> child连接限制为一组固定属性,例如props。 也就是说,您将无法从一个来源提供多个不同的上下文,其中任何给定的子代仅使用上下文的一部分。 可能有1:1映射。 另外,我总是不愿让上下文气泡直接穿过使用它的孩子,而合法的用例通常是两个组件之间的“虫洞”,而不仅仅是广播给所有人(这是一种反模式)。

我会考虑更多,并在PR上详细说明...

@sebmarkbage我可以在shouldUpdateChildContext上任意选择。 我的偏好始终是尽量减少API面积。 我们总是可以将其保留在初始API之外(上下文会跳过并在读取上下文变量的任何组件上发起重新渲染)。 如果人们需要其他逃生舱口,我们稍后会添加。

我认为,如@mjackson所指出的,更大的问题是我们不知道上下文变量何时更改。 我们是否真的想在每次i18n ContextProvider碰巧重新渲染时重新渲染每个fbt / i18n文本节点? 我们还没有提供一种方式来表达“不要担心,什么都没有改变,不必费心重新呈现页面上的每个文本元素”。

我对你的最后一段有些困惑; 你能详细说明吗? 我假设您不是在暗示以下内容不是上下文的反模式:一个i18n组件“广播”到用户喜欢的语言/时区/格式/等所有i18n感知子组件。

关于PR的评论,我想应该在这里转载:

@sebmarkbage @Chrisui @jimfb

回复: shouldUpdateChildContext ,我认为这是一个有用的补充。 默认情况下, shouldComponentUpdate返回true,这使孩子有责任确定是否发生了更改。 实施shouldComponentUpdate意味着在父母更了解的情况下,您要承担责任。 shouldUpdateChildContext ,它只是为了从其子级中消除责任而进行优化。

回复:没有广播到整个子树,我认为有两种有效的模式。 正如您在#2517中建议的那样,虫洞非常有道理。 我们使用该模式对各个子系统(例如焦点和语音)中的项目进行分组。 但是,此外,我们希望使用上下文将i18n信息传递到应用程序的整个部分(也许是整个应用程序),并强制重新渲染使用该信息的任何内容。 与虫孔模式相比,这种广播模式可能相对较少,但我相信它仍然有效。

现在,我们偶尔会使用混入来自同一来源的焦点和语音组。 我认为有一些有效的用例可以不强制进行1:1映射,尽管我了解对限制的渴望。

我实际上以为“虫洞”模式是反模式,而广播是预期的模式。 仅当变量的使用者很多时,上下文才有意义(即,如果显式传递prop将导致将它实际上添加到每个组件中,因此是很多样板),否则,最好将它作为支柱。 但是@sebmarkbage似乎

@sebmarkbage @eplawless您认为有效的虫洞模式示例是什么? 父母提供的是什么,孩子如何使用它,为什么不能仅仅作为孩子的支柱等等?

<Table>
  <Cell />
  <Cell />
  <FancyCell />
</Table>
class FancyCell {
  render() {
    return <SomeWhatFancyCell>Some content</SomeWhatFancyCell>;
  }
}

class SomeWhatFancyCell {
  render() {
    return <Cell>{this.props.children}</Cell>;
  }
}

borderWidth并将其以borderLeft/Top/Right/Bottom的形式传递给表的所有单元格。

一种简洁的方法是将borderWidth传递给<Table /> ,将其拆分并传递上​​下文。

它为可能呈现给单元格的事物提供了一种方法,使其可以通过隐藏通道与其概念上的父级(如表格)进行通信。 这已经是DOM元素相互之间的作用,并且可能是在React中进行布局所必需的。

不要过分关注“上下文”的概念。 当前的概念并不理想。 也许理想是像两个不同的概念或其他渠道?

可能的替代API:

class Table {
  render() {
    var w = this.props.borderWidth;
    var borderStyle = { left: w, right: w, top: w, bottom: w };
    return <context key={someSymbol} value={borderStyle}>{this.props.children}</context>
  }
}
class Cell {
  static contextKey = someSymbol;
  render() {
    var borderStyle = this.context;
    ...
  }
}

只是在这里吐痰。

继续吐口水...

可以使用其他语法...

<Table borderStyleChannelKey="myKey">
  <Cell borderStyle={myKey} />
  <Cell borderStyle={myKey} />
  <FancyCell borderStyle={myKey} />
</Table>

现在,您已使通信渠道变得明确,避免了名称冲突的所有可能性,等等。(https://github.com/reactjs/react-future/pull/28)

符号也没有名称冲突,但是您已经建立了通信通道,但要付出一定的代价。 您已通过多个间接级别传递了它。 每当您需要更改或添加其他通道(例如背景色)时,即使记录的合同(表中的单元格)仍然相同,也需要更新所有内容。

顺便说一句,React的本质已经是一个隐藏的沟通渠道:状态。 它没有明确地通过每个孩子。

将其称为“ styleinfo”或“ celldata”,而不是“ borderStyle”,然后使其成为对象。 然后,添加字段/信息不需要在TableCell之间更改API合同。

我的变体和您的变体之间的唯一区别是(在我的变体中)孩子实际“读取”值的唯一方法是将其作为父项的prop显式传递。 从功能上讲,您的最新建议与我在https://github.com/reactjs/react-future/pull/28中提出的建议完全相同

明确地说,我真的很喜欢您的最新建议...所以我不会抱怨...但是它确实解决了一个微妙的问题。 那是您三月份告诉我的,我最终得出结论,您是正确的(例如广播问题)。 如果我们可以不解决广播问题(这就是我坚信上下文确实可以提供一些价值的方式),那么无论如何,让我们做这样的事情吧!

除非符号名称始终由所有者决定,否则任意符号的确会发生名称冲突,在这种情况下,您还可以使其对所有者可见,因为所有者仍需要将密钥名称传达给子代(否则,如何孩子知道去哪里看?)。 通过强迫所有者参与,您自然会鼓励组件使用符号而不是硬编码的键。

我的意思是必须通过某种渠道(例如通用模块)来协调全局符号(大写S)。 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol

关于我新提出的API的好处是,该密钥不是与控制流和程序分析相混淆的“对象属性”密钥。 在我的建议中,是一流的动态价值,就像赋予国家权力的钥匙一样。

哦,集成电路, @ sebmarkbage再次推出了精美的新javascript :)。 我早该知道; 他今天在TC39会议上。

好吧,你是对的。 这对于避免名称冲突非常有效,并且可以解决广播问题。 你卖了我我喜欢!

我希望你今天在办公室! 我觉得这本来是一场非常有趣的对话!

我个人很高兴他没有,因为这也使我们所有人陷入循环;-)。

我真的很喜欢Symbol的东西。 在潜在的外来树上,上下文对字符串键的过度依赖总是带来一些不良影响。

例如,如果两个库依赖于第三个库中的父子虫洞,并且碰巧使用了它的独立副本,则这些副本不会冲突或彼此看到。 可以肯定,这是一个奇怪的场景,但是似乎更加一致。

公平地说,符号已经可以在当前对象上工作。 即使使用Symbols,您也可能最终需要通过潜在的全局命名空间(例如npm顶级模块名称)来请求模块(尽管希望它是相对的)。

仅仅是当前的API使使用简单的字符串名称变得非常自然。 它还将运行时语义与特定的属性标识符结合起来,使诸如闭包编译器高级模式之类的事情更加难以推理。 还要配合VM优化。

@sebmarkbage您可以使用UUID(也称为符号),但是您的新建议确实允许在运行时确定密钥,这意味着父项可以通过props影响它(尽管不确定您的想法)。 话虽如此,我不确定如果没有在jsx范围内提供可用值,那将有多大用处,我需要考虑更多,但这确实增加了一些很酷的灵活性(也许)。

我不太确定“虫洞”的类比,广播场景和i18n示例是我遇到了拟议的PR旨在解决的问题。

我也不太清楚https://github.com/facebook/react/issues/2517#issuecomment -106597895如何提供帮助-对我来说,这在语义上等效于getChildContext和contextTypes。

对于shouldUpdateChildContext PR,我喜欢的是它不需要枚举任何深度的所有子级来传播上下文更改。

AIUI最初的问题始终是,尽管shouldComponentUpdate被传递了context ,但是上下文的性质意味着“广播者”和“接收者”之间的组件可能返回false,因为它不知道上下文正在通过。

解决该问题的两种方法似乎是跟踪从树中它们上方的上下文中读取哪些组件,或者具有在树中传播上下文更改的其他机制。

表单组件中使用基于父级的上下文后,我想在此问题上提供2美分。 父母和孩子之间缺乏依赖于上下文的良好更新机制,导致创建了一种并行的,类似流量的机制,该机制(充当“存储”实例)通过上下文传递了一个listen()方法, Field组件在安装时向其注册。 表单将触发向下传播的更改。 都是非常容易变化的并且可以正常工作,但是它破坏了React Components的可组合性模型。

将组件包装在HoC中的一般策略在这里不起作用,因为它将新的包装组件_outside_放置在更新通道中,而不是放置在更新通道的中间(与props一样)。 支撑系统的最大好处是流自上而下,并且可以被夹在中间的任何东西拦截。 在这里使用诸如更新之类的助焊剂的情况下,包装组件也需要侦听表单,但随后遇到一个问题,即包装组件和原始(被包裹的)组件都在侦听表单,而不是HoC“接管”并将数据向下传递到原始组件。

显然,上下文需要在某种程度上不受与特定上下文无关的组件的“保护”,但是,如果某个组件愿意,确实需要简单地跳入数据流。 在表单组件的特定情况下,应将Form大多数子代与其传递的上下文隔离开来,只有Field组件才能接收它。 但是,如果消费者希望将Field包装在HoC中(设置默认值或调整行为),则应该有一些简单的方法。

这就是说,任何上下文API都确实应该允许中间(授权)组件充当该上下文的简化器,就像组件在将道具传递给子对象时可以减少/映射道具一样。 区别在于,道具是“公共”蒸汽,而上下文是私有的,选择加入的流

我已经在react-side-context上使用了基于订阅的上下文的想法。

在幕后,上下文广播者向他们的孩子公开了一个{ subscribe(key, cb), getValue(key) }接口。 对上下文密钥的订阅会在上下文树中冒出气泡,直到它到达该特定密钥的广播者或树的顶部。 广播公司通过broadcast({ [key]: newValue, ...otherKeys })发出对上下文密钥的更改。

@broadcasts([...keys])@observes([...keys])类装饰器将此API绑定到React组件,使用context通过组件树传播上下文树,并使用setState入队更新对环境变化的反应。

有了一些样板,您可以设置一个等效于当前shouldUpdateChildContext建议的API。

为避免名称冲突,上下文树是唯一的,因此您需要引用上下文以广播或观察其中的键。

我从未使用上下文传递任何动态值,仅使用不变的引用。 我在这里看到了一些玩具示例,但是有人用它来传递生产应用程序中的动态值吗? 我经常发现现实中的用例更具启发性。

以I18n为例,有人直接传递键值对吗? 还是只是传递对对象组件的不变引用可以从中获取值并进行观察?

我一次有兴趣在上下文中实现某种冒泡事件分发,我忘记了确切的用途,但我的想法是允许父母拦截来自孩子的事件并为其添加更多上下文,这样孩子就不会了解它们在全球范围内的适合位置。

要在这里加2美分,我强烈希望这里的任何修复都以不破坏StaticContainer

我认为StaticContainer仍然可以继续工作,并阻止来自父母的所有更新,包括propscontext ,这是最有意义的。

IMO处理此问题的最佳方法是允许shouldComponentUpdate潜在地检测儿童使用的上下文的更改,而不是在涉及上下文时完全忽略shouldComponentUpdate

@ jedwards1211我的用例是在I18nProvider组件内部获取翻译消息,然后传递Jed实例(使用gettext方法)。

在接收消息之前,gettext方法将返回空字符串,以使UI乐观地呈现。 当I18nProvider收到消息后,我希望所有内容都重新呈现,只有具有可翻译字符串的部分会在DOM中更新。

我实际上需要在视图组件中进行gettext()调用,以便提取字符串(在这种情况下使用xgettext)。

Reactiflux上用shouldComponentUpdate有一个“超级false ”概念,这将很有用。

虽然通常情况下,上下文更新应该传播通过shouldComponentUpdate返回false (或者至少这会使大多数用例更简单),但我也认为有些特殊情况,例如<StaticContainer>要求父级能够阻止对其后代的所有更新,例如冻结冻结正在过渡的子树之类的目的,例如<StaticContainer>

例如,考虑https://github.com/rackt/react-router/pull/2454 –如果无法阻止对子级的上下文更新,我们将无法轻松地阻止更新以将过渡状态链接到活动状态,路由组件。

我同意shouldComponentUpdate返回false不应阻止上下文更新,但是应该有某种SUPER_FALSE哨兵来保留<StaticContainer>按原样工作。

@taion

不能轻易阻止更新以过渡到过渡路由组件上的活动状态。

我很困惑,理想的用例是什么? 通常,不应将shouldComponentUpdate用于阻止会导致UI更改的更新。 它仅是一种优化机制,旨在避免执行不必要的对帐工作。

@jimfb您已经在中继中使用<StaticContainer>来完成此操作–即在加载新数据时阻止更新。

我认为用例是用户单击链接,从而触发了要加载/交换的新路由。 发生这种情况时,我们的想法是阻止所有在旧路线上的互动/更新,当新路线完成加载时将被替换掉。

听起来这是通过在加载新路由器状态时始终在shouldComponentUpdate返回false来完成的。

@taion我将与接力者进行交谈,并弄清楚那里发生了什么。 除非我们对此有确切的答案,否则我不建议您在自己的代码中应用该技术。

cc @sebmarkbage

@jimfb

您可能更容易做到这一点(:

我对这个想法的理解是,在触发要加载的新数据时,默认情况下,我们希望继续渲染旧数据,直到准备好新数据为止。

通过从shouldComponentUpdate返回false ,中继根容器/渲染器具有一种简单的方法来保持渲染之前的内容。

它本身并不是在阻止交互,而是在处于过渡状态时阻止上游数据获取更新。

@taion是的,我知道它在做什么,但是我怀疑这是我们要支持的模式。 我将与塞巴斯蒂安(Sebastian)和接力人员交谈,但我的猜测是答案将是“是的,那是hack,不要这样做”。

通常,完成该行为的方法是将旧值放入状态(直到新值显示/到达)并从状态渲染旧值。

@jimfb

该模式仅适用于不可变数据或类似数据。 如果有(不可克隆的)有状态对象,则可能无法遵循这种模式。 我怀疑中继可能没有此问题,但是当前的React Router实现确实存在此问题。

对于常规的异步数据库,这似乎是一种合理的常规且方便的模式。

@taion您正在考虑的方法似乎更容易出错,特别是在您具有可变数据的情况下。 如果您的数据是可变的,那么您将遇到种族状况,有时孩子会随机更新(由于事件处理程序,上下文更改,强制更新等),并且代码变得非常不可预测。 让我与一些人同步并弄清楚这一点。 如果我还没有发布讨论的结果,请在两天内给我发送Ping。

我不认为应通过该模式来处理StaticContainer用例。 尝试从其他功能的副作用中获得效果似乎是一种不稳定的方法。 对实际要求的内容提供明确支持会更好。

在这种情况下,似乎要求某些低层的React库需要一种方法来拦截卸载,将来自React树的组件产生的DOM与之分离,切断其连接,并将卸载生命周期推迟到完成为止。

我认为这是我们应该使用显式接口支持的低级功能。 卸载或拦截组件卸载的某种方法会断开链接,使调用方可以在一段时间内与基础上下文混淆,并返回一个函数,该函数将在被调用时恢复卸载生命周期流程。

我们可能需要某种特殊形式的片段或占位符,该API可以用来显式告诉React与React断开连接的DOM节点应保存在特定位置。

如果没有超级特殊的内部节点,那将是不可能的。 但是,再次出现了一种特殊的低层类型的React节点的想法,该节点呈现为Noscript(典型的React hack的null返回值等),并声明其nextSibling是应该保留在该位置的DOM节点。但否则,React会忽略它,这将是一个非常有趣的节点类型,它对于修复各种其他错误很有用。

@taion好吧,我刚刚与Relay团队的一个人以及其他几个React成员进行了交谈。 我们都同意这不是一个好的模式。 请不要这样做。 官方的解决方案是在等待更新时使用状态存储数据,直到我们针对该用例提出更好的api /推荐为止。

我可以解决这个问题–我认为我们已经在React Router方面进行了一些整理,以使我们无论如何都可以不再使用此模式。

谢谢!

我也想补充一个问题。 我有这样的东西。

var BlogPosts = React.createClass({
  getChildContext: function() {
    return {
      currentBlogPost: this.props.currentBlogPost,
      currentUser: this.props.currentUser
    };
  },

  childContextTypes: {
    currentBlogPost: React.PropTypes.object,
    currentUser: React.PropTypes.object
  },

  render: function() {
    return <BlogPosts blogPosts={this.props.blogPosts}/>
  }
});

function select(state) {
  const { blogPosts, currentUser, currentBlogId } = state;
  console.log( state.blogs[currentBlogId]); 
  // first time the above is undefined and then blogs get populated and I have the object;
  return { blogPosts, currentUser, currentBlogPost: state.blogs[currentBlogId] };
};

export default connect(select)(BlogPosts);

现在BlogPosts组件中有BlogPostText,BlogPostImage,PodCast ...,具体取决于blogPosts [index] .type是文本,图像还是音频。

在我检查过所有者的其中一个组件中,

var BlogPostText = React.createClass({
  canDeleteMemory: function(post, blog, user) {
    return user && (blog.userId == user.id || post.userId == user.id)
  },
  render: function() {
    let isOwner = this.canDeleteMemory(this.context.currentBlogPost, post, this.context.currentUser);
    return isOwner ? <a>Delete</a> : null;
  }
});

那么我总是在blog.userId上收到错误消息,因为博客是未定义的...所以我将条件更改为let isOwner = this.context.currentBlogPost && this.canDeleteMemory(this.context.currentBlogPost, post, this.context.currentUser);
但是然后删除图标将永远不会显示...但是如果我使用redux select包装BlogPostText组件并使用this.props.currentBlogPost,则不会使用contextType,而是可以正常工作。

因此,上下文的变化不会触发重新渲染或类似的操作……或者我使用的是错误的。

@ aghosh47我认为上下文更改确实会触发重新渲染,但是有可能我们错过了一个边缘情况。 如果您可以创建一个简单的jsfiddle来演示问题,那将有助于我们进行调查。

另外,请尽量将其发布在新的发行版上,因为我们希望保持github发行版的主题。

@jimfb好的,我将重新检查我的代码,如果我无法解决,请看是否错过了某些内容并创建一个新线程。 因此,感谢您的反馈。

@jimfb有关在不重新安装后代的情况下进行重新

我偶然发现的上下文的另一个用例是组件在多个过渡组之下。 例如,我希望能够在其所有祖先过渡组实际出现/输入而不是最初安装时集中输入。 注册祖先的回调是通过上下文传递的方法来显示/输入的,这将是最方便的方法。

@ jedwards1211老实说,我不记得在哪里讨论过它。 完全有可能是一个面对面的讨论,但是在线上有许多重新主持的讨论,也许您会在那找到。

平@ aghosh47

哈哈哈

@jimfb很好,问题已解决...代码中存在一些错误,并且编写代码的人员没有使用catch来解决来自promise的任何错误,因此reducer状态没有得到正确的更新,因此未进行更改没有得到反映。

因此,如果父项中的props发生变化,子项中的contextType确实会得到反映...谢谢。

将窗口用于存储这些全局变量的一个对象的真正错误是什么?

@cauburtin在所有创建的组件共享相同全局状态的服务器端,这实际上并不起作用。 上下文是在单个树中呈现的所有组件的全局范围,但对于树本身而言是局部的。 与全局状态不同,它不会泄漏到其他树。

从侧面说,这个问题与问题无关,不是吗? :眨眼:

@fatfisz我不太了解您的用例,您也可能有一个require('foo')指向客户端上的window属性

顺便说一句,在我的应用程序中,由于上下文似乎不清楚,并且由于在我的情况下几乎所有组件都使用它,因此我最终在所有组件的道具中传递了一个对象

谢谢

做错了什么:

// context.js
module.exports  = { // sorry for using commonjs, since most of you use import/export I guess
  // some shared variables, initialized and used by components
};

然后require('./ context.js'); 在所有需要访问上下文变量和方法的文件中

封装可能不被尊重,但是很好。

(@gaeron)

@cauburtin上下文非常有用,因为:

1)与模块导出不同,它可以更改并触发重新渲染
2)它可以被父级覆盖,这是它最有用的功能
3)不必是单例,这对于要隔离数据的服务器渲染很有用

同意2)和3)
对于1)我还不需要这样做,但是您也可以在上下文中传递一个react实例(我经常听说这是一种不好的做法,您可能也这么认为,但是在文档中甚至提到了这一点:回想一下如果愿意,您也可以在props中传递整个React组件(在这种情况下,props可以是此单例上下文)。对我而言,React是视图,事件侦听器和(顶部/根目录)远程控件。该共享上下文将是对远程控件的共享访问。子组件可以在其道具发生更改时更新,并且可能会访问和使用该全局上下文。(在这种情况下,尤其不是单例的粉丝,我可以重用“传递”以道具的方式来反应实例,如果有用的话,这个“传递”可以或多或少地被一个普通的父类或合成自动化/隐藏)

写得很好,我意识到单例上下文的想法很糟糕:)

令人困惑的是,React文档并没有邀请太多使用上下文,它们实际上就像是可选的道具,当您要求它们时可以在哪里得到它们?

我正在使用react native在Android中实现Drawer,并尝试为抽屉菜单代码和抽屉内容代码创建不同的文件。 为此,我创建了不同的react组件。 我可以在这些文件中完成所有工作,但是我需要抽屉引用来在组件文件中执行抽屉的某些操作。 这是我的代码,如何将抽屉引用传递给其他组件文件,以使用抽屉方法(如openDrawer())。

'use strict';

    var React = require('react-native');
    var { View,
          StyleSheet,
          TouchableHighlight,
          } = React;

    var DrawerLayout = require('react-native-drawer-layout');
    var DrawerScreen = require('./DrawerScreen');
    var DrawerMenu = require('./DrawerMenu');

    var DrawerLayoutExample = React.createClass({

      render: function() {
        var navigationView = (
          <View >
               <DrawerMenu/>
          </View>
        );

        return (
          <DrawerLayout
            onDrawerSlide={(e) => this.setState({drawerSlideOutput: JSON.stringify(e.nativeEvent)})}
            onDrawerStateChanged={(e) => this.setState({drawerStateChangedOutput: JSON.stringify(e)})}
            drawerWidth={200}
            ref={(drawer) => { return this.drawer = drawer  }}
            keyboardDismissMode="on-drag"
            renderNavigationView={() => navigationView}>
            <View style={styles.container}>
 // Here is content component for drawer, need to refer drawer reference
            <DrawerScreen ></DrawerScreen>
            </View>
          </DrawerLayout>
        );
      }
    });

    var styles = StyleSheet.create({
      container: {
        alignItems: 'center',
        justifyContent: 'center',
        flex: 1,
        flexDirection: 'column',
      },
     });

    module.exports = DrawerLayoutExample;

DrawerScreen.js

'use strict';
var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Dimensions,
  Image,
  TouchableHighlight,
  TextInput,
} = React;

var deviceWidth = Dimensions.get('window').width;

var DrawerScreen = React.createClass({


  render: function() {
    return (

        <View style={styles.container}>

          <Text style={styles.welcome}>Content!</Text>

          <TouchableHighlight onPress={() => this.state.openDrawer()}>
            <Text>Open drawer</Text>
          </TouchableHighlight>
          <TextInput style={styles.inputField} />
        </View>
    );
  },

});

var styles = StyleSheet.create({
   container: {
      alignItems: 'center',
      justifyContent: 'center',
      flex: 1,
          flexDirection: 'column',
    },
    inputField: {
      backgroundColor: '#F2F2F2',
      height: 40,
    },
});

现在,我正在使用redux来订阅和存储媒体查询,因此组件可以决定在例如电话上进行不同的呈现。

这意味着,否则,纯组件需要订阅商店,这种气味对我来说是不对的,更不用说Redux的订阅者模型了,导致许多很少更改的订阅。

我认为在这种情况下,存储上下文是一个更好的地方,但是这个问题使我无法使用它。

@wmertens这是只需要在componentDidMount上完成的事情吧?

@cauburtin根本没有,用户可以随时选择其他语言,并且用户可以随时调整浏览器的大小……

为了调整大小,您可以在componentDidMount中监听,对于语言,我会重新渲染所有内容

当我有一个很酷的东西时,我宁愿不重复所有调整大小的逻辑
监听整个APO并在服务器端工作的redux actioncreator
太…
我有state.sensitive.isPhone / isPrerender / screenwidth等。在服务器上,
我根据用户代理调度。 整齐。

2016年4月29日星期五,下午4:52西里尔·奥伯丁[email protected]
写道:

要调整大小,您可以在componentDidMount中侦听,
重新渲染一切

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件或在GitHub上查看
https://github.com/facebook/react/issues/2517#issuecomment -215742327

Wout。
(在手机上键入,请简洁)

但是,是的,在树的顶部有一个简单的key = {lang}就足够了
语言。 不过,对于带有动画的网站而言,效果不是很好。

2016年4月29日,星期五,下午6:07 Wout Mertens wout。 [email protected]写道:

当我有一个很酷的东西时,我宁愿不重复所有调整大小的逻辑
监听整个APO并在服务器端工作的redux actioncreator
太…
我有state.sensitive.isPhone / isPrerender / screenwidth等。在
服务器,我基于用户代理调度。 整齐。

2016年4月29日星期五,下午4:52西里尔·奥伯丁[email protected]
写道:

要调整大小,您可以在componentDidMount中侦听,
重新渲染一切

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件或在GitHub上查看
https://github.com/facebook/react/issues/2517#issuecomment -215742327

Wout。
(在手机上键入,请简洁)

Wout。
(在手机上键入,请简洁)

我和您一样,但是调整大小的事件侦听器实际上很便宜,我认为拥有许多相同类型的侦听器并不坏

诺诺,我毫不怀疑它们很便宜,只是因为它容易得多
写this.context.isPhone而不是管理调整大小处理程序。

2016年4月29日星期五,下午6:38西里尔·奥伯丁[email protected]
写道:

我就像你一样,但是调整大小的事件监听器实际上很便宜,我不喜欢
认为拥有许多相同类型的侦听器很不好

-
您收到此邮件是因为有人提到您。
直接回复此电子邮件或在GitHub上查看
https://github.com/facebook/react/issues/2517#issuecomment -215797819

Wout。
(在手机上键入,请简洁)

问题是react-redux返回一个=== ReactElement来表示渲染方法“没有更新”。 但是实际上,只有在上下文也为===的情况下,才能使用此快捷方式,而这不能通过组件进行控制。 不过,我不完全确定为什么要这样做。 对于react-redux家伙来说也许是个问题。

有关使用Redux的信息,请参阅https://github.com/reactjs/react-router/issues/470以及PR中的继续讨论。

在某个时候,我们实际上将解析https://github.com/reactjs/react-router/issues/3484,以使其他库可以轻松使用它,但这比我们最初想象的要难。

请注意有关react-router当前使用的解决方案。 它可以工作,但是令人沮丧。 它要求我们就地更改上下文,或者要求用户在其上下文对象之间维护引用身份。

两者都不是很好,但是真正的问题是围绕组件层次结构的蠕虫上下文破坏了数据流的React组件模型最好的位之一,这是中间的任何东西(具有权限)都可以在上下文通过时调整上下文。 通过“带外”组件传递上下文,组件将需要使用其他机制将上下文映射到子级,或者只是不这样做。 诚然,它不是_common_用例,但无论如何都描述了所有上下文用法

在这两种方法之间,虽然至少是通用的。。。我认为最好的方法是

是的,实际上是:P,只是指出它仍然不是次优的,并且不能很好地代替固定上下文的工作方式

@jquense中间的组件不能只是声明contextTypes来从上方监听上下文对象,而childContextTypes / getChildContext()提供该上下文的修改后的副本以它的后代?

@DarylCantrell组件需要选择加入他们所请求的每个上下文。 没有方法声明contextTypes来将整个上下文批发传递给组件。

在任何情况下,我的观点是,可能会调整上下文的某些位,这是上下文的一项重要功能,但此问题的解决方法破坏了该功能

@ 1000hz可以,但是他说的是“在上下文中调整部分内容”。 在那种情况下,您不需要获取“整个”上下文,而无论您打算覆盖哪个部分。

@DarylCantrell糟糕,我看错了您的意图。

我已经为此问题提交了请求请求:#7213

(来自拉取请求)
我正在使用上下文来传播区域设置和路由信息。 我的问题是某些纯组件在上下文更改时停止子树渲染(因为纯组件仅检查状态和道具)。 对于每个人来说,这是一个问题,问题#2517。

我认为不屏蔽中间组件的上下文会比屏蔽更好,并且定义的contextTypes仅用于验证,而不用于过滤。 这类似于propTypes的工作方式,其中验证指定的属性,但未指定的属性仍可用于组件。 我已经更新了测试以反映此更新。

由于对上下文不感兴趣的组件通常将不使用上下文参数,因此,不屏蔽上下文不会导致任何现有组件中断,除非所述组件明确检查上下文参数未定义。 但是,这将为精制的纯组件(例如ContextAwarePureComponent)创造机会,该组件将在shouldComponentUpdate中比较状态,属性和上下文,从而允许更新上下文来重新渲染纯组件。

我目前正在使用镭和一些react-bootstrap组件使用此修补程序进行开发,并且没有遇到任何问题

您好@bvella ,我想您是说这个请求:#7213,而不是7212。

@DarylCantrell你说的对,谢谢

我认为https://github.com/facebook/react/pull/7213 / https://github.com/facebook/react/pull/7225是一个不错的选择,应该加以修改

我的理解是上下文就像全局变量,因此应限制使用;
但是,这并不意味着用户必须先声明才能访问全局变量。
全局变量应该始终可访问,而不管是否声明。

就像propTypes一样,contextType声明应仅用于验证目的

再次,请考虑一下,因为它给我们带来很多麻烦
(我正在使用react Relay,它隐藏了所有上下文对象)

我在https://github.com/facebook/react/pull/7225#issuecomment -276618328上进行了评论,认为上下文应该只更新所有内容,而sCU仅用于确定该组件是否需要重新渲染; 它的子项将被验证。

用这种方法进行上下文更新是昂贵的,但是比强制渲染整个树要便宜,而后者是当前获取上下文更新使其应用于所有组件的唯一方法。 情境变化不应该太大; 对于快速变化的更新,有更好的选择(例如Redux)。

现在有什么解决方案吗?

正如我们从该线程知道的那样,现有解决方案在根本上已被破坏。
我们必须在不降低所有应用速度的前提下修复当前的API(我们要避免:-)。

因此,我们提出了一个新的API,该API处理相同的用例,但不存在那些设计缺陷。 该计划旨在使API并存,然后在人们迁移后逐步淘汰旧的API。

查看讨论: https :

@acdlite刚刚使用新的上下文API进入了PR: https

我认为我们可以认为这是封闭的。 新的API没有这个问题。 旧的API无法修复,我们将在主要库升级到新的API后的某个时候弃用它。

新的API将在下一个较小的React 16.x发行版之一中提供和记录。

@gaearon不知道您是否经常听到此消息,但是:你们做得很好,我感谢您的工作。 🍺

此页面是否有帮助?
0 / 5 - 0 等级