React: 有没有办法在ComponentDidMount中访问新的上下文api?

创建于 2018-03-18  ·  41评论  ·  资料来源: facebook/react

我们正在构建一个react mapbox gl模块,今天我们使用克隆和注入道具。

我们正在研究使用16.2.0上下文api,但我看到它将在16.3.0上有一个新版本,但我似乎找不到找到读取上下文详细信息的方法
在componentDidMount生命周期上(这对我来说可以在地图实现上使用)。

有没有解决的办法 ?

Question

最有用的评论

在生命周期方法中应该有一种简单的方法来访问或调用上下文。
是的,可以通过将我们的组件包装在另一个组件中来解决! 但是,感觉不仅仅是解决方法,更是一种解决方法。

我了解这感觉像是一个额外的包装器,但这是使新上下文快速运行的原因。 如果树中没有显式的包装器节点,我们将无法快速“查找”需要更新的组件。

如果您需要在生命周期中访问上下文,请以它为支撑。

class Button extends React.Component {
  componentDidMount() {
    alert(this.props.theme);
  }

  render() {
    const { theme, children } = this.props;
    return (
      <button className={theme ? 'dark' : 'light'}>
        {children}
      </button>
    );
  }
}

export default React.forwardRef((props, ref) => (
  <ThemeContext.Consumer>
    {theme => <Button {...props} theme={theme} ref={ref} />}
  </ThemeContext.Consumer>
));

这几乎与contextTypes定义的行数相同。

所有41条评论

添加一个示例,我正在尝试使其工作: https :

编辑:遵循来自https://github.com/reactjs/rfcs/blob/master/text/0002-new-version-of-context.md#class -based-api的准则

这就是使用新的api可以实现的方式。

class BaseMapElement extends React.Component {
  componentDidMount() {
    console.log(this.props.context);
  }

  render() {
    return null;
  }
}

const MapElement = () => (
  <Context.Consumer>
    {context =>
      <BaseMapElement context={context} />
    }
  </Context.Consumer>
)

因此,通过componentDidMount访问的唯一方法是重定向到Props?

编辑:更改为componentDidMount

重新分配到props的高阶组件是一个很好的模式,但是对于那些我通常不想做的多余组件,是将上下文值存储在组件实例上,例如: this.contextValue = value然后在生命周期挂钩。 这有点丑陋,但是我认为总体来说还可以,因为您选择了更好,更特殊的模式作为优化

我正面临着同样的困境。
在生命周期方法中应该有一种简单的方法来访问或调用上下文。
我们可能需要初始化内容,检查和获取数据,甚至在卸载时进行清理。
人们现在正在使用当前据称已损坏的上下文来执行此操作。
是的,可以通过将我们的组件包装在另一个组件中来解决! 但是,感觉不仅仅是解决方法,更是一种解决方法。

将上下文值存储在组件实例上,例如:this.contextValue = value,然后在生命周期挂钩中对其进行访问

我很确定这在异步模式下是不安全的。 请不要这样做。 cc @acdlite

是的,可以通过将我们的组件包装在另一个组件中来解决! 但是,感觉不仅仅是解决方法,更是一种解决方法。

我同意这一点。 可以很好地通过componentDidMountcomponentWillUnmount进行本地访问,以便能够初始化/清理内容,

通常,使用实例变量聪明并欺骗正常数据流将导致异步问题。 只是不要。 今天,这有点令人困惑,因为实例变量是完成某些事情(如计时器)的唯一方法。 在发布异步之前,我们将发布更清晰的建议-并且有一天,我们将拥有一个新的组件API,该API并不太依赖实例。

tl; dr:使用props间接。 并且不必担心额外的组件。

我很确定这在异步模式下是不安全的。 请不要这样做。

这怎么会不安全(以及以何种方式)? 在所有关于异步模式的讨论中还不清楚,“不安全”是什么意思。 人们开始觉得自己像个笨拙的人,行为举止不合理且无法预测,这在人们普遍认为其简单易懂的数据流模型的系统中并没有给人们带来很多保证。 我感觉组件回到了0.13之前的版本中,它们再次成为魔术对象。

说“仅添加另一个组件”也很容易,但这通常很麻烦,并介绍了它自己的错误和挑战类别。 我并不觉得人们必须为了安全起见就必须在React API上发明抽象。

抱歉^如果以上听起来很烦/很生气,那我不是故意的! 在phhhhooone上

听起来确实很生气,哈哈,但我明白你的意思,我也向你分享如何/为什么不安全的问题

人们开始觉得自己像个笨蛋,行为举止不合理且无法预测

抱歉,一个多月以来,我们一直在为指南提供具体建议。 请给我们一些时间来收集它们并将其发布为博客文章。 没有实际的Alpha来玩也很难讨论,这也是我们一直在努力的事情。

因此,我们要么什么都不说,要么提前警告那些效果不佳的事情。 我们站在警告的一边,但我可以看到它看起来像是在给您增加难度。 我敢肯定,一旦代码出炉并且可以使用它,您就会明白我们的意思,并且会更有意义。

这怎么会不安全(以及以何种方式)?

您有机会观看我的演讲吗? 如果您还没有看过第二部分,这将很难解释,因为尚不清楚我们为什么这样做。 所以我要你看。 我希望我的演讲能说服您,我们的目标是解决从一开始就困扰着React的各种问题,并且这些功能值得重新审视我们可能已经习惯的一些假设。

假设您已经看过谈话,这是有关此特定情况的更具体的说明。 为了能够像我在演示中所展示的那样“暂停”渲染,React需要能够在任何时间调用render() ,可能使用不同的道具。 例如,如果可以在新屏幕(正在加载)中为道具设置this.propsthis.state ,则调用render ,然后将其设置为旧的this.propsthis.state ,以响应树的当前版本上的交互进行渲染时(例如,如果在加载新屏幕时按了某个按钮)。

在异步中,经验法则是:仅生命周期(如componentDidMountcomponentDidUpdatecomponentWillUnmount和ref回调会在明确定义的时间点执行,并且道具和状态对应屏幕上的内容。 幸运的是,我们只有少数其他生命周期无法很好地适应这张图片,并且我们正在为它们引入更好的替代方法( getDerivedPropsFromStategetSnapshotBeforeUpdate )。 这将是逐步的迁移。 再次,所有这些都将在博客文章中。

现在到这个问题的根源。 在异步模式下, React不能保证调用render方法的时间和顺序。 实际上,React从来没有保证过要做到这一点–每次碰巧都是相同的顺序。 将字段存储在render ,然后在生命周期中读取它不是“安全的”,因为您可能会在React调用render使用不同的道具存储字段(例如,对于悬挂的树还没准备好)。

在生命周期方法中应该有一种简单的方法来访问或调用上下文。
是的,可以通过将我们的组件包装在另一个组件中来解决! 但是,感觉不仅仅是解决方法,更是一种解决方法。

我了解这感觉像是一个额外的包装器,但这是使新上下文快速运行的原因。 如果树中没有显式的包装器节点,我们将无法快速“查找”需要更新的组件。

如果您需要在生命周期中访问上下文,请以它为支撑。

class Button extends React.Component {
  componentDidMount() {
    alert(this.props.theme);
  }

  render() {
    const { theme, children } = this.props;
    return (
      <button className={theme ? 'dark' : 'light'}>
        {children}
      </button>
    );
  }
}

export default React.forwardRef((props, ref) => (
  <ThemeContext.Consumer>
    {theme => <Button {...props} theme={theme} ref={ref} />}
  </ThemeContext.Consumer>
));

这几乎与contextTypes定义的行数相同。

是的,可以通过将我们的组件包装在另一个组件中来解决! 但是,感觉不仅仅是解决方法,更是一种解决方法。

只是想紧跟Dan所说的,子函数/ render prop方法是新上下文的_official API_-所以请使用它,并让React担心确保它很快。 (这将是!)

这怎么会不安全(以及以何种方式)?

严格模式文档草案还涉及了为什么在异步模式下更改实例(这是另一种副作用)很危险的原因。

我们有一个实验性分支机构,遵循此处提出的准则。 谁能看看它是否有意义? https://github.com/commodityvectors/react-mapbox-gl/pull/11

我对该库不熟悉,所以我不知道人们是否曾经使用ref及其组件-但是,如果这样做,该PR上的withContext mixin可能是一个很好的用例。新的forwardRef API

有道理。 感谢您的参考。 我现在要解决这个问题。

刚遇到这个问题是因为我一直在尝试看看我是否可以实现相同的目标。

因此,从所有这些信息中我可以得出结论,在使用Context.Consumer的组件中不可能访问子渲染功能之外的API。 我想出了一些可以使这一切变得容易的方法(如果出于某种原因这不是很好的做法,我将非常感谢您的反馈):

const MapElement = (props) => (
  <Context.Consumer>
    {context =>
      <RunOnLifecycle
        runOnMount={() => { /*use context*/ }}
        runOnUnMount={() => { /*use context*/ }}
        runOnUpdate={(prevProps) => { /*use context - compare prevProps with props */ }}
        { ...props }
      />
    }
  </Context.Consumer>
)

那个辅助组件<RunOnLifecycle/>

export interface IPropsRunOnLifecycle {
  runOnMount?: () => void;
  runOnUpdate?: (prevProps: object) => void;
  runOnUnMount?: () => void;
  children?: JSX.Element | ReactNode;
  [prop: string]: any;
}

export class RunOnLifecycle extends React.Component<IPropsRunOnLifecycle> {
  componentDidUpdate(prevProps, prevState, snapshot) {
    if (this.props.runOnUpdate != null) {
      this.props.runOnUpdate(prevProps);
    }
  }

  componentDidMount() {
    if (this.props.runOnMount != null) {
      this.props.runOnMount();
    }
  }

  componentWillUnmount() {
    if (this.props.runOnUnMount != null) {
      this.props.runOnUnMount();
    }
  }

  render() { return this.props.children || null; }
}

想知道这是否会引起任何麻烦。 即使有点骇人听闻,仍然感觉像是标准的React。

有一些细微的差异可能会使该方法成为一个坏主意。 例如,如果MapElement是使用引用的类组件,则在运行runOnMount回调时尚未设置引用。

😄我建议为此使用HOC方法:
https://reactjs.org/docs/context.html#consumption -context-with-a-hoc

forwardRef API减轻了使用HOC进行此类操作的唯一真正缺点:
https://reactjs.org/docs/react-api.html#reactforwardref

我们采用了类似react docs和人们在这里所说的方法。 到目前为止,它对我们来说运作良好。

https://github.com/commodityvectors/react-mapbox-gl/blob/master/src/Map.js#L63

有一些细微的差异可能会使该方法成为一个坏主意。 例如,如果MapElement是使用引用的类组件,则在运行runOnMount回调时尚未设置引用。

感谢您的反馈@bvaughn 。 目前,我仅将其用作状态代理组件,它根据上下文树中装入的内容在UI中添加/删除内容。 有点像门户,但在React组件树中。 因此,根本不渲染孩子或处理引用。

如果我需要做与裁判互动的事情,请记住。

嘿大家,

我在生命周期方法中需要上下文数据。 因此,在看到前几条评论后,我遵循了HOC方法,并将上下文数据作为道具传递了。
一切都按预期进行。 但是现在,我想为组件编写单元测试用例,但无法这样做。

如果有人可以分享如何为这种情况编写测试用例,我将非常感激。

我正在使用酶,ases-adapter-react-16和开玩笑,但这样做有一些麻烦。

@AmnArora

在我所在的公司,我们执行以下操作(请注意,这可能不是共识),我们也导出“裸”组件,然后将其导入测试中并手动通过道具。

例如

// MyComponent.js
export class MyComponent extends Component { /* ... */ }
export default HOC()(MyComponent)

// MyComponent.spec.js
import { MyComponent } from '...'

// OtherComponents.js
import MyComponent from '...'

另外,除了讨论之外,我们遇到了相同的问题,并创建了这个https://www.npmjs.com/package/react-context-consumer-hoc ,它消耗了多个上下文。

一切都按预期进行。 但是现在,我想为组件编写单元测试用例,但无法这样做。

@AmnArora为什么您无法编写单元测试? 你尝试了什么? 您看到什么错误?

@pgarciacamou首先,感谢您的快速回复。 好吧,在网上找不到任何内容并在此处发布查询之后。 我想出了您提到的相同解决方案。

测试用例现在正在工作,但这似乎可以解决。 我将看看https://www.npmjs.com/package/react-context-consumer-hoc并与我的团队讨论。

谢谢。 :100:

@bvaughn事情早在我使用redux进行状态管理时,我浅复制了Component并使用了dive()和instance()方法来获取组件的实例。

但是,使用上下文API时,这些方法都不可用。

当我不使用任何这些方法时,我得到以下错误: _标记为12_的未知节点

知道了

这两种听起来都与您所使用的酶版本有关,但并没有正确支持新的上下文API。 那真不幸。

我对reduxunistore (大量的代码污染/额外的移动部件imo)感到非常反感,这导致我进行了以下设置,该设置允许我们的嵌套组件访问单个全局状态。 不应处于全局状态的所有其他内容(例如,文本输入值,切换值)都存储在每个嵌套组件的本地状态中。

https://github.com/davalapar/session-context

你好

这个问题解决了吗?

我有一个场景,其中包含3个组件。 创建者,展示者,单元格。
Cell有我的渲染逻辑,该逻辑在Creator和Display中都使用。

当前,如果正在创建项目或必须显示该项目,则需要传递状态。 我在不同位置使用了Cells,所以我需要分别将状态作为props传递。 所以我想使用上下文作为状态,但是问题是我只想在安装了Display组件后才修改上下文。 我们可以实现这是React的当前版本吗? (我正在使用React 16.7)

上面有几条评论显示了如何访问componentDidMount Context.Consumer。 你尝试过吗? 他们不是因为某种原因而工作吗?

注意React 16.6为类添加了新的contextType API,这使得在componentDidMount读取新上下文变得容易。

https://reactjs.org/docs/context.html#classcontexttype

是的,前提是您只需要在组件中使用单个上下文– contextType是一个方便的选择。

注意React 16.6为类添加了新的contextType API,这使得在componentDidMount读取新上下文变得容易。

它不是。 即使单个上下文类型就足够了,Class.contextType也会中断继承。 HOC也是如此。

在我们的文档中,我们非常明确地建议在继承上推荐组合,以在组件之间重用代码。 除此之外,我真的不明白你在说什么。

contextType API肯定可以_does_,可以轻松访问componentDidMount上下文值(这就是该线程的含义)。

我们在文档中非常明确地建议在继承上推荐合成...

太宽泛了...

我目前正在苦苦挣扎的情况是,我拥有一系列具有支持其实现所需的通用功能的组件家族。 一切都是通过继承完美建模的,除了...上下文位!

考虑到似乎仅在这种情况下我会弄乱上下文,因此上下文API非常令人沮丧。

这就是这个话题

是的,此评论有点题外话。

使用Context Class更改contextType会中断redux存储:/

请注意,React 16.6为类添加了新的contextType API,使在componentDidMount中读取新上下文变得容易。

是的,只要您只需要在组件中使用单个上下文,contextType是一个方便的选项。

在将上下文分配给MyCoolComponent.contextType之前没有办法组合上下文吗?

我现在读的是,如果我们想要一个可以实现以下目的的组件:

a)消耗多个上下文
b)在render以外的方法中使用这些上下文中的内容

这意味着我们被描述的模式所困扰,其中包装器使用上下文并将道具传递给子级。

我认为理想的情况是我可以写这样的东西,并获得两全其美的效果(即在整个课堂上都可以使用多种上下文):

MyCoolComponent.contextType =  composeContexts(OneContext, TwoContext, RedContext, BlueContext)

有什么办法吗?

它适用于render方法,但不适用于任何其他方法。

嘿。
是否有可能在类组件的构造函数中使用新的上下文api?

我们正在将项目从v15.x迁移到v16.x,任务之一是使用新的上下文api
在我们的项目中,我们使用css-modules + isomorphic-style-loader,后者提供了一些API来将组件样式表注入DOM。

在V15中,我们将这些API放入旧的上下文api中,然后让每个组件通过类似的方式获取它

    class MyComp extends Component {
        static contextTypes = {
                insertCss: PropTypes.func
           }
         ....
         componentWillMount () {
                // insert a style tag for this component
               this.removeCss = this.context.insertCss(myStyles)
         }
    }

在V15中,我们可以将其放在componentWillMount中。 这将确保组件在渲染之前获得正确的样式。

但是,在V16中,componentWillMount被标记为不安全,以后将不推荐使用。
因此,我们的直接想法是将我们的context.insertCss调用放入组件的构造函数中。

但是,从文档中可以看到,

如果在组件中定义了contextTypes,则以下生命周期方法将接收一个附加参数,即上下文对象:

构造函数(属性,上下文)

此用法(使用上下文作为第二个参数)也将被弃用。

我尝试了新的上下文api,分配MyComp.contextType = StyleContext
但是我发现我得到了this.context在构造函数中未定义。

    class MyComp extends Component {
        static contextType = StyleContext

         constructor (props) {
             super(props)
             console.log(this.context) // undefined
         }

    }

有没有关于如何在构造函数中使用上下文的实用指南?

有什么建议吗?

嘿。
是否有可能在类组件的构造函数中使用新的上下文api?

我们正在将项目从v15.x迁移到v16.x,任务之一是使用新的上下文api
在我们的项目中,我们使用css-modules + isomorphic-style-loader,后者提供了一些API来将组件样式表注入DOM。

在V15中,我们将这些API放入旧的上下文api中,然后让每个组件通过类似的方式获取它

    class MyComp extends Component {
        static contextTypes = {
                insertCss: PropTypes.func
           }
         ....
         componentWillMount () {
                // insert a style tag for this component
               this.removeCss = this.context.insertCss(myStyles)
         }
    }

在V15中,我们可以将其放在componentWillMount中。 这将确保组件在渲染之前获得正确的样式。

但是,在V16中,componentWillMount被标记为不安全,以后将不推荐使用。
因此,我们的直接想法是将我们的context.insertCss调用放入组件的构造函数中。

但是,从文档中可以看到,

如果在组件中定义了contextTypes,则以下生命周期方法将接收一个附加参数,即上下文对象:

构造函数(属性,上下文)

此用法(使用上下文作为第二个参数)也将被弃用。

我尝试了新的上下文api,分配MyComp.contextType = StyleContext
但是我发现我得到了this.context在构造函数中未定义。

    class MyComp extends Component {
        static contextType = StyleContext

         constructor (props) {
             super(props)
             console.log(this.context) // undefined
         }

    }

有没有关于如何在构造函数中使用上下文的实用指南?

有什么建议吗?

您可以这样做,而不是使用contextType

class MyComponent extends React.Component {
   render(){
       const {
         //props including context props
       } = this.props;
       return(<View />);
   }
};

const withContext = () => (
  <MyContext.Consumer>
    { (contextProps) => (<MyComponent {...contextProps}/>)}
  </MyContext.Consumer>
);

export default withContext;

对于像我这样想要在渲染功能之外使用它的人,只需在您的子组件中使用以下命令即可:

useContext()

例如:

const context = useContext(yourContext)

MainComponent.Subcomponent = () => {
 const context = useContext(context)

  useEffect(()=> {
    console.log(context)
  })
}
此页面是否有帮助?
0 / 5 - 0 等级