Padrões em React: Provider Pattern

Entendendo um dos padrões mais poderosos do React

Image for post
Image for post
Organizar o estado da sua aplicação, uma disciplina em constante evolução!

O React Provider Pattern é um conceito poderoso. Você não o encontra frequentemente ao usar apenas React, mas pode considerar usá-lo ao planejar seu aplicativo. Basicamente, ele pula a hierarquia obrigatória de passar props para cada componente, na sua árvore de componentes.

Muitas vezes, esse padrão pode ser visto em ação ao usar uma biblioteca de gerenciamento de estado externa. No Redux ou MobX, muitas vezes você acaba com um componente Provider no topo da hierarquia de componentes que cria a sua camada de estado (Redux/MobX) para ser usada na sua camada de exibição (React).

O componente Provider recebe o estado como props e, depois disso, cada componente filho tem acesso implícito ao estado gerenciado.

Mas como isso funciona? Este artigo fornece uma explicação passo a passo em como utilizar o padrão Provider em React. Ao final desse artigo, você saberá implementar seu próprio Provider. Além disso, você entenderá como um componente Provider no Redux ou MobX funciona, se você já viu ou usou algum deles por aí.

Mas antes, há duas coisas que você precisa saber sobre React para que você possa implementar seu próprio Provider, são elas: children e context.

Entendendo React Children

children são úteis para aninhar componentes React uns aos outros. A idéia é semelhante ao aninhar elementos HTML, porém, usando a sintaxe JSX:

class App extends Component {
render() {
return (
<Header>
Olá React Children!
</Header>
);
}
}
function Header({ children }) {
return <h1>{children}</h1>;
}

Nesse caso, o “Olá React Children!” será usado como children no componente Header e, assim, irá renderizar esse nó dentro da tag <h1>.

Essa é uma maneira poderosa de aninhar os componentes React entre si, porque a propriedade children sempre está acessível na props de um componente.

Entendendo React Context

O context em React não é muito famoso. A equipe do React até desencoraja seu uso. Como a própria equipe do React diz, a API do context em React poderá ser alterada no futuro.

No entanto, o context é uma característica poderosa. Você se lembra da última vez que você teve que passar props para vários componentes pela sua árvore de componentes? Usando apenas React, você pode ser confrontado frequentemente com esta questão:

          +----------------+
| |
| |
| A |
| |
| |
+--------+-------+
|
+---------+-----------+
| |
| |
+--------+-------+ +--------+-------+
| | | |
| | | + |
| B | | |Props |
| | | v |
| | | |
+----------------+ +--------+-------+
|
+--------+-------+
| |
| + |
| |Props |
| v |
| |
+--------+-------+
|
+--------+-------+
| |
| |
| C |
| |
| |
+----------------+

Imagine que o componente C tem algumas props obrigatórias, para conseguir passar as props necessárias, você precisa passar as props para cada componente filho dessa hierarquia. Muitas vezes, criando um caminho confuso entre um e outro.

Quando algumas props se tornam obrigatórias, o context do React te oferece uma saída para essa bagunça. Ao invés de passar props explicitamente para cada componente, você pode esconder as props, que são necessárias para um componente específico, no objeto context do React e passá-los implicitamente para cada componente.

O objeto context atravessa invisivelmente a árvore de componentes. Quando um componente precisa acessar o objeto context, ele explicitamente pede acesso:

          +----------------+
| |
| A |
| |
| Provide |
| Context |
+--------+-------+
|
+---------+-----------+
| |
| |
+--------+-------+ +--------+-------+
| | | |
| | | |
| B | | D |
| | | |
| | | |
+----------------+ +--------+-------+
|
+--------+-------+
| |
| |
| E |
| |
| |
+--------+-------+
|
+--------+-------+
| |
| C |
| |
| Consome |
| Context |
+----------------+

Mas você não deve exagerar no uso do context em React. Então, quais são os casos de uso para essa abordagem?

Por exemplo, seu aplicativo pode ter um tema colorido que é configurável. Cada componente deve ser colorido dependendo da configuração. A configuração é obtida uma vez do seu servidor, mas agora você deseja que isso seja acessível de forma implícita para todos os componentes.

Portanto, você pode usar context para fornecer a cada componente o acesso ao tema colorido.

Como o context do React é fornecido e consumido?

Imagine que você teria o componente A como raiz, que fornece o context, e o componente C, como um dos componentes filho que consomem o context.

O aplicativo possui um tema colorido que pode ser usado para modelar seus componentes. Assim, você pode disponibilizar o tema para cada componente através do context.

Nesse exemplo, uma propriedade de tema de cores, mas pode ser qualquer coisa, desde state até props. O componente A renderiza o componente D, mas torna o context disponível para todos os seus filhos.

class A extends React.Component {
getChildContext() {
return {
coloredTheme: "green"
};
}
render() {
return <D />;
}
}
A.childContextTypes = {
coloredTheme: PropTypes.string
};

No seu componente C, ou em qualquer lugar abaixo do componente D, você pode consumir o objeto context. Observe que o componente A não precisa passar nada por meio das props do componente D.

class C extends React.Component {
render() {
return (
<div style={{ color: this.context.coloredTheme }}>
{this.children}
</div>
);
}
}
C.contextTypes = {
coloredTheme: PropTypes.string
};

Ao usar a propriedade do tema a partir de this.context, o componente pode mudar seu estilo. Dessa forma, todos os componentes que precisam ter acesso ao tema colorido podem obter as informações necessárias do objeto context.

Você pode ler mais sobre isso na documentação oficial.

Utilizando o Provider Pattern

As duas funcionalidades do React, context e children, são necessárias para implementar o Provider Pattern. Esses são o básico necessário.

Agora que você entendeu as duas partes necessárias, você será capaz de implementá-lo! O Provider Pattern consiste de duas partes, uma parte no padrão, torna as propriedades acessíveis no context, e a outra parte, disponibiliza o context aos componentes que precisam consumi-lo.

Vamos começar com a primeira parte, um componente Provider, responsável por tornar as propriedades acessíveis:

class ThemeProvider extends React.Component {
getChildContext() {
return {
coloredTheme: this.props.coloredTheme
};
}
render() {
return this.props.children;
}
}
ThemeProvider.childContextTypes = {
coloredTheme: PropTypes.string
};

O componente Provider apenas define o tema colorido a partir das props. Além disso, ele só retorna children e não adiciona nada ao JSX.

Depois de declarar o componente Provider, você pode usar suas propriedades em qualquer lugar na sua árvore de componentes.

É importante declara-lo no topo da hierarquia de seus componentes para tornar o context, o tema colorido, acessível a todos.

const coloredTheme = "green";
// tema de exemplo
// imagine que a configuração esteja localizada em outro lugar
// precisaria ser obtido primeiro (do servidor, por exemplo)
// e seria diferente para cada usuário de sua aplicação
ReactDOM.render(
<ThemeProvider coloredTheme={coloredTheme}>
<App />
</ThemeProvider>,
document.getElementById('app')
);

Agora, cada componente filho pode consumir o tema colorido fornecido pelo componente ThemeProvider. Não precisa ser um children direto, neste caso, o componente da aplicação, mas qualquer componente na árvore de componentes.

class App extends React.Component {
render() {
return (
<div>
<Paragraph>
That's how you would use children in React
</Paragraph>
</div>
);
}
}
class Paragraph extends React.Component {
render() {
const { coloredTheme } = this.context;
return (
<p style={{ color: coloredTheme }}>
{this.props.children}
</p>
);
}
}
Paragraph.contextTypes = {
coloredTheme: PropTypes.string
};

Um componente funcional sem estado, também terá acesso ao context, mas somente se contextTypes for definido como propriedade da função.

class App extends React.Component {
render() {
return (
<div>
<Headline>Hello React</Headline>
<Paragraph>
That's how you would use children in React
</Paragraph>
</div>
);
}
}
...function Headline(props, context) {
const { coloredTheme } = context;
return (
<h1 style={{ color: coloredTheme }}>
{props.children}
</h1>
);
}
Headline.contextTypes = {
coloredTheme: PropTypes.string
};

Esse é o básico para o padrão Provider do React. Você tem o componente Provider que torna as propriedades acessíveis no context e seus componentes consomem o context explicitamente.

Agora, você já tem conhecimento suficiente para começar a implementar esse padrão você mesmo!

No entanto, há mais uma coisa que você deve saber, para não criar uma bagunça na sua aplicação, precisamos colocar ordem de alguma maneira. Iremos alcançar isso utilizando high order components.

A dupla dinâmica, Context e High Order Components

Como falamos, o padrão Provider é freqüentemente usado em bibliotecas de gerenciamento de estado, como Redux ou MobX. Você fornecerá o estado, que é gerenciado por uma dessas bibliotecas, por meio de um componente Provider. Este componente Provider usa o context do React para passar o estado implicitamente.

No entanto, acho que você raramente, ou nunca usou, this.context em sua árvore de componentes ao usar uma dessas bibliotecas de gerenciamento de estado. Muitas vezes, um high order component, disponibilizado pela biblioteca de gerenciamento de estado (como o connect() do react-redux), fornece o acesso ao estado em seu componente.

Qual seria o high order component, para fornece o context do React como props? Basicamente, o high order component se parece com o seguinte:

const getContext = contextTypes => Component => {
class GetContext extends React.Component {
render() {
return <Component { ...this.props } { ...this.context } />
}
}
GetContext.contextTypes = contextTypes;return GetContext;
};
// usageclass App extends React.Component {
render() {
return (
<div>
<Headline>Hello React</Headline>
<Paragraph>
That's how you would use children in React
</Paragraph>
<SubHeadlineWithContext>
Children and Context
</SubHeadlineWithContext>
</div>
);
}
}
...function SubHeadline(props) {
return (
<h2 style={{ color: props.coloredTheme }}>
{props.children}
</h2>
);
}
const contextTypes = {
coloredTheme: PropTypes.string
};
const SubHeadlineWithContext = getContext(contextTypes)(SubHeadline);

Agora você pode envolver qualquer componente com getContext, nosso high order component, para expor as propriedades do tema colorido. As propriedades serão passadas como props e não no objeto context.

O nosso high order component, getContext, é uma versão simplificada da que pode ser encontrado na biblioteca de recompose. recompose tem vários componentes úteis seguindo o padrão high order component. Recomendo você dar uma olhada nessa biblioteca.

A versão final desse exemplo, usando create-react-app, pode ser encontrada no repositório do GitHub. Ele mostra os exemplos apresentados anteriormente, como usar componentes funcionais sem estado e componentes de classe ES6, que consomem o context, mas também a abordagem usando high order component com getContext. Você pode dar uma olhada na pasta src/ e os arquivos src/index.js e src/App.js.

O que aprendemos?

No final, o padrão Provider, explica como a camada de estado é colada na sua camada de exibição ao usar uma biblioteca de gerenciamento de estado.

Ao usar o Redux ou MobX, você não precisa mais passar props a partir do componente raiz. Você fornece o estado ao componente Provider, o componente Provider irá disponibiliza-los no context do React e todos os componentes filho podem acessar o estado usando um high order component, como connect() da biblioteca react-redux.

A idéia principal, é fornecer seu estado para seus componentes sem a necessidade de passa-lo explicitamente como props na sua árvore de componentes.

Créditos

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store