React: Existe uma maneira de acessar a nova API de contexto no ComponentDidMount?

Criado em 18 mar. 2018  ·  41Comentários  ·  Fonte: facebook/react

Estamos construindo um módulo react mapbox gl e usamos clone e injete adereços hoje.

Estávamos tentando usar a API de contexto 16.2.0, mas vi que haverá uma nova na 16.3.0, mas não consigo encontrar uma maneira de ler os detalhes do contexto
No ciclo de vida do componentDidMount (o que faz sentido para mim usar na implementação do mapa).

Existe uma maneira de contornar isso?

Question

Comentários muito úteis

Deve haver uma maneira fácil de acessar ou chamar o contexto nos métodos de ciclo de vida.
Sim, pode ser resolvido envolvendo nosso componente em outro componente! Mas parece mais uma solução alternativa do que uma solução.

Eu entendo que parece um invólucro extra, mas é o que torna o novo contexto mais rápido . Se não tivéssemos nós wrapper explícitos na árvore, não seríamos capazes de “encontrar” rapidamente os componentes que precisam ser atualizados.

Se você precisa acessar o contexto no ciclo de vida, tome-o como um suporte.

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>
));

Esta é quase a mesma quantidade de linhas que uma definição contextTypes .

Todos 41 comentários

Adicionando um exemplo que estou tentando fazer funcionar: https://codesandbox.io/s/l20yn296w7

EDITAR: Seguindo as diretrizes de https://github.com/reactjs/rfcs/blob/master/text/0002-new-version-of-context.md#class -based-api

É assim que pode ser alcançado com a nova 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>
)

Portanto, a única maneira de acessar por meio de componentDidMount é redirecionar para Props?

Editar: alterado para componentDidMount

O componente de ordem superior que redireciona para props é um bom padrão, mas para casos em que não quero o componente extra, geralmente o que faço é armazenar o valor do contexto na instância do componente como: this.contextValue = value e acessá-lo no ganchos do ciclo de vida. É um pouco feio, mas geralmente está ok, eu acho, já que você optou por não usar o padrão mais legal, hoc, como uma otimização

Estou enfrentando o mesmo dilema.
Deve haver uma maneira fácil de acessar ou chamar o contexto nos métodos de ciclo de vida.
Podemos precisar inicializar coisas, verificar e buscar dados ou até mesmo limpar na desmontagem.
As pessoas estão fazendo isso agora com o contexto atual supostamente quebrado.
Sim, pode ser resolvido envolvendo nosso componente em outro componente! Mas parece mais uma solução alternativa do que uma solução.

armazene o valor do contexto na instância do componente como: this.contextValue = value e acesse-o nos ganchos do ciclo de vida

Tenho certeza de que isso não é seguro no modo assíncrono. Por favor, não faça isso. cc @acdlite

Sim, pode ser resolvido envolvendo nosso componente em outro componente! Mas parece mais uma solução alternativa do que uma solução.

Eu concordo com isso. Seria bom acessar nativamente por meio de componentDidMount e componentWillUnmount para poder inicializar / limpar coisas,

Em geral, usar a instância vars para ser inteligente e enganar o fluxo normal de dados causará problemas no modo assíncrono. Apenas não faça isso. É um pouco confuso hoje, porque vars de instância são a única maneira de fazer certas coisas, como temporizadores. Antes de lançarmos o assíncrono, publicaremos recomendações mais claras - e um dia teremos uma nova API de componente que não depende tanto de instâncias.

tl; dr: Use uma direção indireta. E não se preocupe muito com o componente extra.

Tenho certeza de que isso não é seguro no modo assíncrono. Por favor, não faça isso.

Como isso seria inseguro (e de que maneira)? Não está muito claro em toda essa conversa sobre o modo assíncrono, o que significa "inseguro". Está começando a se sentir como um idiota cujo comportamento é irracional e imprevisível, o que não enche as pessoas de muita segurança em um sistema que geralmente é apreciado por ser simples e facilmente compreensível, modelo de fluxo de dados. Eu sinto que os componentes estão de volta ao terreno anterior ao 0.13, onde são objetos mágicos novamente.

Também é fácil dizer "Basta adicionar outro componente", mas isso geralmente é oneroso e apresenta sua própria categoria de bugs e desafios. Não começo a sentir que as pessoas têm que inventar abstrações sobre uma API de reação para ficar "seguro".

desculpe ^ se o acima soa aborrecido / zangado, não era essa a intenção! em um phhhhooone

Parecia raivoso, haha, mas entendi e compartilho suas perguntas sobre como / por que isso não será seguro

Está começando a se sentir como um idiota cujo comportamento é irracional e imprevisível

Sinto muito, mas estamos trabalhando em uma orientação com sugestões específicas há mais de um mês. Por favor, dê-nos tempo para coletá-los e publicá-los como uma postagem no blog. Também é difícil discutir sem alfas reais para brincar, que também é algo em que temos trabalhado muito.

Portanto, temos que não dizer nada ou avisar com antecedência sobre coisas que não vão funcionar bem. Erramos por avisar, mas posso ver como pode parecer que estamos dificultando as coisas para você. Tenho certeza de que assim que o código for lançado e você puder brincar com ele, verá o que queremos dizer e fará mais sentido.

Como isso seria inseguro (e de que maneira)?

Você teve a chance de assistir a minha palestra ? Isso será um pouco difícil de explicar se você não viu a segunda parte, porque não ficaria claro por que estamos fazendo isso. Então, estou pedindo para você assistir. Espero que minha palestra convença você de que temos como objetivo resolver uma ampla classe de problemas que atormentaram o React desde o início e que vale a pena revisar algumas suposições com as quais nos acostumamos.

Supondo que você tenha visto a palestra, aqui está uma explicação mais específica sobre este caso particular. Para poder “suspender” a renderização como mostrei na demonstração, o React precisa ser capaz de chamar render() a qualquer momento, potencialmente com adereços diferentes. Por exemplo, se pode definir this.props e this.state para adereços de uma nova tela (que está sendo carregada), chame render , mas defina this.props antigo e this.state ao renderizar em resposta a uma interação na versão atual da árvore (por exemplo, se eu pressionar algo enquanto a nova tela está carregando).

No assíncrono, a regra prática é: apenas ciclos de vida como componentDidMount , componentDidUpdate e componentWillUnmount e retornos de chamada ref são executados em pontos bem definidos no tempo com props e estado que correspondem para o que está na tela. Felizmente, temos apenas alguns outros ciclos de vida que não se encaixam perfeitamente nesta imagem e estamos introduzindo alternativas melhores para eles ( getDerivedPropsFromState , getSnapshotBeforeUpdate ). Esta será uma migração gradual. Novamente, tudo estará na postagem do blog.

Agora vamos à raiz deste problema. No modo assíncrono, o React não oferece garantias sobre quando e em qual ordem ele chama o método render . O que é realmente algo que o React nunca garantiu em primeiro lugar - aconteceu de ser sempre a mesma ordem. Armazenar um campo em render e depois lê-lo em um ciclo de vida não é "seguro" porque você pode armazenar um campo no momento React chamado render com diferentes props (como para uma árvore suspensa que ainda não está pronto).

Deve haver uma maneira fácil de acessar ou chamar o contexto nos métodos de ciclo de vida.
Sim, pode ser resolvido envolvendo nosso componente em outro componente! Mas parece mais uma solução alternativa do que uma solução.

Eu entendo que parece um invólucro extra, mas é o que torna o novo contexto mais rápido . Se não tivéssemos nós wrapper explícitos na árvore, não seríamos capazes de “encontrar” rapidamente os componentes que precisam ser atualizados.

Se você precisa acessar o contexto no ciclo de vida, tome-o como um suporte.

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>
));

Esta é quase a mesma quantidade de linhas que uma definição contextTypes .

Sim, pode ser resolvido envolvendo nosso componente em outro componente! Mas parece mais uma solução alternativa do que uma solução.

Só queria concordar com o que Dan disse que a abordagem de função filha / render prop é a _ API oficial_ para o novo contexto - então, use-a e deixe o React se preocupar em garantir que seja rápido. (Será!)

Como isso seria inseguro (e de que maneira)?

O rascunho da documentação do Modo estrito também aborda alguns dos motivos pelos quais a mutação da instância (que é apenas outro tipo de efeito colateral) é perigosa no modo assíncrono.

Temos um ramo experimental seguindo as diretrizes aqui propostas. Alguém pode dar uma olhada para ver se faz sentido? https://github.com/commodityvectors/react-mapbox-gl/pull/11

Não estou familiarizado com esta biblioteca, então não sei se as pessoas já fizeram uso de refs com seus componentes - mas se o fizessem, o mixin de withContext naquele PR poderia ser um bom caso de uso para o nova API forwardRef .

Isso faz sentido. Obrigado pela referência. Vou encerrar esse problema por enquanto.

Acabei de encontrar esse problema porque estou tentando ver se consigo alcançar a mesma coisa.

Portanto, pelo que posso deduzir de tudo isso, não é possível no componente que faz uso de Context.Consumer acessar a API fora da função de renderização filha. Eu descobri algo que pode funcionar para tornar tudo isso um pouco mais fácil (gostaria muito de receber algum feedback se isso não for uma boa prática por qualquer motivo):

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

E esse componente auxiliar <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; }
}

Querendo saber se isso vai causar alguma dor de cabeça no futuro. Ainda parece um React bem normal, embora seja um hack.

Existem algumas diferenças sutis que podem tornar essa abordagem uma má ideia. Por exemplo, se MapElement fosse um componente de classe que usasse refs, os refs ainda não teriam sido configurados quando o retorno de chamada runOnMount fosse executado.

😄 Eu sugeriria usar uma abordagem HOC para isso:
https://reactjs.org/docs/context.html#consuming -context-with-a-hoc

A única desvantagem real de usar um HOC para esse tipo de coisa foi mitigada pela API forwardRef :
https://reactjs.org/docs/react-api.html#reactforwardref

Usamos a abordagem como os documentos de reação e o que as pessoas disseram aqui. Está funcionando bem para nós até agora.

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

Existem algumas diferenças sutis que podem tornar essa abordagem uma má ideia. Por exemplo, se MapElement fosse um componente de classe que usava refs, os refs ainda não teriam sido definidos quando o retorno de chamada runOnMount fosse executado.

Obrigado pelo feedback @bvaughn . No momento, estou usando-o puramente como um tipo de componente de proxy de estado que adiciona / remove coisas da IU, dependendo do que está montado na árvore de contexto. Mais ou menos como portais, mas dentro da árvore de componentes do React. Portanto, não renderizando filhos ou lidando com árbitros.

Vou ter em mente se eu precisar fazer algo que interaja com os árbitros.

Olá a todos,

Preciso de dados de contexto nos métodos de ciclo de vida. então, segui a abordagem HOC depois de ver os primeiros comentários e passei os dados de contexto como adereços.
Tudo está funcionando conforme o esperado. Mas agora, quero escrever casos de teste de unidade para os componentes, mas não consigo fazer isso.

Eu realmente apreciaria se alguém pudesse compartilhar como posso escrever casos de teste para este cenário.

Estou usando enzima, enzima-adaptadora-react-16 e brincadeira, mas estou tendo alguns problemas para fazer isso.

@AmnArora

Na empresa para a qual trabalho, fazemos o seguinte (note, isso pode não ser o consenso), exportamos o componente "nu" também e depois o importamos em nossos testes e passamos os adereços manualmente.

Por exemplo

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

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

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

Além disso, acrescentando a esta discussão, encontramos o mesmo problema e criamos este https://www.npmjs.com/package/react-context-consumer-hoc que consome vários contextos.

Tudo está funcionando conforme o esperado. Mas agora, quero escrever casos de teste de unidade para os componentes, mas não consigo fazer isso.

@AmnArora Por que você não consegue escrever um teste de unidade? O que você tentou? Que erro você está vendo?

@pgarciacamou Em primeiro lugar, obrigado pela resposta rápida. Bom, depois de não encontrar nada na web e postar a consulta aqui. Eu vim com a mesma solução que você mencionou.

Os casos de teste estão funcionando agora, mas isso parece uma solução alternativa. Vou dar uma olhada em https://www.npmjs.com/package/react-context-consumer-hoc e discutir com minha equipe.

Obrigado. : 100:

@bvaughn A coisa é anterior, quando eu estava usando redux para gerenciamento de estado, copiei superficialmente o Component e usei os métodos dive () e instance () para obter a instância do componente.

Mas nenhum desses métodos estava disponível ao usar a API de contexto.

E quando não estava usando nenhum desses métodos, recebia o seguinte erro: _nó desconhecido com a tag 12_

Peguei vocês.

Ambos parecem problemas com a versão do Enzyme que você está usando e não oferece suporte adequado para a nova API de contexto. Isso é lamentável.

Eu tive uma aversão enorme por redux e unistore (muita poluição de código / partes móveis extras), o que me levou à seguinte configuração que permite que nossos componentes aninhados acessem um único estado global. Tudo o mais que não deveria estar no estado global (ou seja, valores de entrada de texto, valores de alternância) são armazenados no estado local de cada componente aninhado.

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

Oi,

este problema está resolvido?

Tenho um cenário onde tinha 3 componentes. Criador, exibição, células.
Cell tem minha lógica de renderização que é usada tanto no Creator quanto no Display.

Atualmente preciso passar o status se o item está sendo criado ou deve ser exibido. Usei células em locais diferentes, então preciso passar o status como adereços separadamente. Então, eu queria usar o contexto para o status, mas o problema é que eu queria modificar o contexto apenas se o componente Display estivesse montado. Podemos conseguir isso nas versões atuais do React? (Estou usando o React 16.7)

Houve alguns comentários acima mostrando como acessar Context.Consumer valores em componentDidMount . Você experimentou? Eles não funcionaram por algum motivo?

Observe que o React 16.6 adicionou uma nova API contextType para classes, o que torna a leitura do novo contexto em componentDidMount fácil.

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

Sim, desde que você só precise usar um único contexto em seu componente - contextType é uma opção conveniente.

Observe que o React 16.6 adicionou uma nova API contextType para classes, o que torna a leitura do novo contexto em componentDidMount fácil.

Isso não. Mesmo se um único tipo de contexto for suficiente, Class.contextType interrompe a herança. O mesmo se aplica ao HOC.

Somos bastante explícitos em nossos documentos sobre a recomendação de composição em vez de herança para reutilizar código entre componentes. Além disso, eu realmente não entendo o que você está dizendo.

A API contextType definitivamente _faz_ torna fácil acessar os valores de contexto em componentDidMount (que é sobre o que este tópico se trata).

Somos muito explícitos em nossos documentos sobre a recomendação de composição em vez de herança ...

Isso é muito amplo ...

O caso com o qual estou lutando agora é que tenho uma família de componentes com várias funcionalidades comuns necessárias para dar suporte à sua implementação. Tudo é modelado por herança perfeitamente, exceto ... bits de contexto!

Dado que parece que eu mexo com o contexto apenas em situações como essa, a API de contexto é muito frustrante.

... que é sobre o que este tópico é ...

Sim, este comentário está um pouco fora do tópico.

Alterar contextType com a classe de contexto quebra o armazenamento redux: /

Observe que o React 16.6 adicionou uma nova API contextType para classes, o que facilita a leitura do novo contexto em componentDidMount.

Sim, desde que você só precise usar um único contexto em seu componente - contextType é uma opção conveniente.

Não há como compor contextos antes de atribuí-los a MyCoolComponent.contextType ?

Minha leitura é por agora, se quisermos um componente que possa:

a) Consumir múltiplos contextos
b) Use coisas desses contextos em métodos diferentes de render

Isso significa que estamos presos ao padrão descrito, em que o wrapper consome o contexto e passa os acessórios para o filho.

Acho que uma situação ideal seria que eu pudesse escrever algo assim e obter o melhor dos dois mundos (ou seja, vários contextos disponíveis em toda a classe):

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

Há alguma maneira de fazer isso?

Funciona para o método render, mas não funciona para nenhum outro método .... alguma ideia ??

Olá.
Existe alguma possibilidade de usar a nova API de contexto no construtor de um componente de classe?

Estamos migrando nossos projetos de v15.x para v16.x, uma das tarefas é usar a nova API de contexto
Em nosso projeto, usamos css-modules + isomorphic-style-loader, o último expõe algumas APIs para injetar as folhas de estilo do componente no DOM.

Na V15, colocamos essas APIs na API de contexto antiga e deixamos que cada componente as receba por meio de algo como

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

No V15, podemos colocar isso no componentWillMount. Isso garantirá que o componente obtenha o estilo correto antes de renderizar.

No V16, no entanto, o componentWillMount está marcado como inseguro e será preterido no futuro.
Portanto, nossos pensamentos imediatos seriam colocar nossa chamada context.insertCss no construtor do componente.

No entanto, visto no documento ,

Se contextTypes for definido em um componente, os seguintes métodos de ciclo de vida receberão um parâmetro adicional, o objeto de contexto:

construtor (adereços, contexto)

Este uso (com contexto como segundo parâmetro) também será descontinuado.

Tentei a nova API de contexto, atribuindo MyComp.contextType = StyleContext
Ainda assim, descobri que entendi isso. O contexto é indefinido no construtor.

    class MyComp extends Component {
        static contextType = StyleContext

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

    }

Existe algum guia prático sobre como usar o contexto no construtor?

Algum conselho?

Olá.
Existe alguma possibilidade de usar a nova API de contexto no construtor de um componente de classe?

Estamos migrando nossos projetos de v15.x para v16.x, uma das tarefas é usar a nova API de contexto
Em nosso projeto, usamos css-modules + isomorphic-style-loader, o último expõe algumas APIs para injetar as folhas de estilo do componente no DOM.

Na V15, colocamos essas APIs na API de contexto antiga e deixamos que cada componente as receba por meio de algo como

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

No V15, podemos colocar isso no componentWillMount. Isso garantirá que o componente obtenha o estilo correto antes de renderizar.

No V16, no entanto, o componentWillMount está marcado como inseguro e será preterido no futuro.
Portanto, nossos pensamentos imediatos seriam colocar nossa chamada context.insertCss no construtor do componente.

No entanto, visto no documento ,

Se contextTypes for definido em um componente, os seguintes métodos de ciclo de vida receberão um parâmetro adicional, o objeto de contexto:

construtor (adereços, contexto)

Este uso (com contexto como segundo parâmetro) também será descontinuado.

Tentei a nova API de contexto, atribuindo MyComp.contextType = StyleContext
Ainda assim, descobri que entendi isso. O contexto é indefinido no construtor.

    class MyComp extends Component {
        static contextType = StyleContext

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

    }

Existe algum guia prático sobre como usar o contexto no construtor?

Algum conselho?

Você pode fazer assim em vez de usar 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;

Para quem como eu está lutando para usá-lo fora de sua função de renderização, basta usar o seguinte em seu subcomponente:

useContext ()

Por exemplo:

const context = useContext(yourContext)

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

  useEffect(()=> {
    console.log(context)
  })
}
Esta página foi útil?
0 / 5 - 0 avaliações