API de contexto Redux vs. React



No React 16.3, uma nova API de contexto foi adicionada. Novo no sentido de que a antiga API de contexto estava nos bastidores, a maioria das pessoas não sabia sobre sua existência ou não a usou, porque a documentação aconselhou a evitar usá-la.

No entanto, agora a API de contexto é uma parte completa do React, aberta para uso (não como antes, oficialmente).

Imediatamente após o lançamento do React 16.3, surgiram artigos que anunciavam a morte do Redux devido à nova API de contexto. Se você tivesse perguntado a Redux sobre isso, acho que ele teria respondido - "os relatos da minha morte são muito exagerados ".

Neste post, quero falar sobre como a nova API de contexto funciona, como ela se parece com o Redux, quando você pode usar o Contexto em vez do Redux, e por que o Contexto não substitui o Redux em cada caso.

Se você deseja apenas uma visão geral da API de contexto, pode seguir o link .

Exemplo de aplicação de reagir


Eu vou assumir que você tem uma compreensão dos princípios de trabalhar com o estado no React (adereços e estado), mas se não for, eu tenho um curso gratuito de 5 dias para ajudá- lo a aprender sobre isso .

Vejamos um exemplo que nos leva ao conceito usado no Redux. Começaremos com uma versão simples do React, e depois veremos como fica no Redux e, finalmente, no Context.



Nesta aplicação, as informações do usuário são exibidas em dois locais: na barra de navegação no canto superior direito e no painel lateral ao lado do conteúdo principal.

(Você pode notar que há muitas semelhanças com o Twitter. Não é coincidência! Uma das melhores maneiras de aprimorar suas habilidades no React é copiar (criar réplicas de sites / aplicativos existentes) .

A estrutura do componente é assim:



Usando o React puro (apenas acessórios), precisamos armazenar informações do usuário suficientemente altas na árvore para que possam ser transmitidas aos componentes que precisam. Nesse caso, as informações do usuário devem estar no aplicativo.

Em seguida, para transferir informações sobre o usuário para os componentes que precisam, o aplicativo deve passá-las para Nav e Body. Eles, por sua vez, o transmitirão ao UserAvatar (hurra!) E à barra lateral. Por fim, a Barra Lateral deve transmiti-la ao UserStats.

Vamos ver como isso funciona no código (coloquei tudo em um arquivo para facilitar a leitura, mas na verdade ele provavelmente será dividido em arquivos separados, seguindo alguma estrutura padrão ).

import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; const UserAvatar = ({ user, size }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> ); const UserStats = ({ user }) => ( <div className="user-stats"> <div> <UserAvatar user={user} /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> ); const Nav = ({ user }) => ( <div className="nav"> <UserAvatar user={user} size="small" /> </div> ); const Content = () => <div className="content">main content here</div>; const Sidebar = ({ user }) => ( <div className="sidebar"> <UserStats user={user} /> </div> ); const Body = ({ user }) => ( <div className="body"> <Sidebar user={user} /> <Content user={user} /> </div> ); class App extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { const { user } = this.state; return ( <div className="app"> <Nav user={user} /> <Body user={user} /> </div> ); } } ReactDOM.render(<App />, document.querySelector("#root")); 


Código de exemplo CodeSandbox

Aqui, o aplicativo inicializa o estado que contém o objeto de usuário. Em um aplicativo real, é mais provável que você extraia esses dados do servidor e os salve no estado para renderização.

Em relação aos adereços ("perfuração de adereços"), não é grande coisa . Isso funciona muito bem. Jogando adereços, este é um exemplo ideal de React. Mas jogar profundamente na árvore de estados pode ser um pouco chato ao escrever. E irritante ainda mais se você precisar transmitir muitos props'ov (e não um).

No entanto, há uma grande desvantagem nessa estratégia: ela cria uma conexão entre componentes que não devem ser conectados. No exemplo acima, o Nav deve aceitar um suporte de "usuário" e passá-lo ao UserAvatar, mesmo que o Nav não precise dele.

Os componentes intimamente acoplados (como aqueles que passam adereços para os filhos) são mais difíceis de reutilizar, pois é necessário vinculá-los a novos pais sempre que os usar em um novo local.

Vamos ver como podemos melhorar isso.

Antes de usar o Contexto ou Redux ...


Se você puder encontrar uma maneira de combinar a estrutura do seu aplicativo e tirar proveito da passagem de adereços para descendentes, isso poderá tornar seu código mais limpo sem ter que recorrer a um avanço profundo de adereços, Contexto ou Redux .

Neste exemplo, os adereços filhos são uma ótima solução para componentes que precisam ser universais, como Nav, Barra Lateral e Corpo. Lembre-se também de que você pode passar o JSX para qualquer suporte, não apenas para crianças - portanto, se você precisar de mais de um "slot" para conectar componentes, lembre-se disso.

Aqui está um exemplo de um aplicativo React no qual Nav, Body e Sidebar pegam os filhos e os exibem como estão. Portanto, quem usa o componente não precisa se preocupar em transferir certos dados que o componente exige. Ele pode simplesmente exibir o que precisa, usando os dados que ele já possui no escopo. Este exemplo também mostra como usar qualquer suporte para transmitir filhos.

(Obrigado a Dan Abramov por esta oferta !)

 import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; const UserAvatar = ({ user, size }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> ); const UserStats = ({ user }) => ( <div className="user-stats"> <div> <UserAvatar user={user} /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> ); //  children   . const Nav = ({ children }) => ( <div className="nav"> {children} </div> ); const Content = () => ( <div className="content">main content here</div> ); const Sidebar = ({ children }) => ( <div className="sidebar"> {children} </div> ); // Body   sidebar  content,    , //    . const Body = ({ sidebar, content }) => ( <div className="body"> <Sidebar>{sidebar}</Sidebar> {content} </div> ); class App extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { const { user } = this.state; return ( <div className="app"> <Nav> <UserAvatar user={user} size="small" /> </Nav> <Body sidebar={<UserStats user={user} />} content={<Content />} /> </div> ); } } ReactDOM.render(<App />, document.querySelector("#root")); 


Código de exemplo CodeSandbox

Se o seu aplicativo for muito complicado (mais complicado que este exemplo!), Pode ser difícil entender como adaptar o modelo às crianças. Vamos ver como você pode substituir o encaminhamento de adereços pelo Redux.

Exemplo de Redux


Vou dar uma rápida olhada no exemplo do Redux para entender melhor como o Contexto funciona. Portanto, se você não tiver um entendimento claro do Redux, leia primeiro minha introdução ao Redux (ou assista ao vídeo ).

Aqui está o nosso aplicativo React redesenhado para usar o Redux. As informações do usuário foram movidas para o repositório Redux, o que significa que podemos usar a função de conexão react-redux para transmitir diretamente o suporte do usuário aos componentes que precisam deles.

Esta é uma grande vitória em termos de livrar-se da conexão. Dê uma olhada em Nav, Body e Sidebar e você verá que eles não recebem mais nem transmitem props do usuário. Eles não jogam mais batatas quentes com adereços. Não há mais conexões inúteis.

O redutor faz pouco aqui; é bem simples. Eu tenho mais uma coisa sobre como os redutores Redux funcionam e como escrever o código imutável que eles usam.

 import React from "react"; import ReactDOM from "react-dom"; //    createStore, connect, and Provider: import { createStore } from "redux"; import { connect, Provider } from "react-redux"; //  reducer       . const initialState = {}; function reducer(state = initialState, action) { switch (action.type) { //    action SET_USER  state. case "SET_USER": return { ...state, user: action.user }; default: return state; } } //  store  reducer'   . const store = createStore(reducer); // Dispatch' action     user. store.dispatch({ type: "SET_USER", user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }); //   mapStateToProps,      state (user) //     `user` prop. const mapStateToProps = state => ({ user: state.user }); //  UserAvatar    connect(),    //`user` ,      . //     2 : // const UserAvatarAtom = ({ user, size }) => ( ... ) // const UserAvatar = connect(mapStateToProps)(UserAvatarAtom); const UserAvatar = connect(mapStateToProps)(({ user, size }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> )); //   UserStats    connect(),    // `user` . const UserStats = connect(mapStateToProps)(({ user }) => ( <div className="user-stats"> <div> <UserAvatar /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> )); //    Nav      `user`. const Nav = () => ( <div className="nav"> <UserAvatar size="small" /> </div> ); const Content = () => ( <div className="content">main content here</div> ); //   Sidebar. const Sidebar = () => ( <div className="sidebar"> <UserStats /> </div> ); //   Body. const Body = () => ( <div className="body"> <Sidebar /> <Content /> </div> ); //  App    ,     . const App = () => ( <div className="app"> <Nav /> <Body /> </div> ); //     Provider, //   connect()    store. ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.querySelector("#root") ); 


Código de exemplo CodeSandbox

Agora você provavelmente está se perguntando como o Redux consegue essa mágica. Incrível Como o React não suporta a passagem de objetos para vários níveis e o Redux pode fazer isso?

A resposta é que o Redux usa a função de contexto React (recurso de contexto). Não a API de contexto moderna (ainda não), mas a antiga. Um que a documentação do React diz que não use, a menos que você esteja escrevendo sua própria biblioteca ou saiba o que está fazendo.

O contexto é semelhante a um barramento de computador seguindo cada componente: para obter a energia (dados) passando por ele, você só precisa se conectar. E a conexão react-redux faz exatamente isso.

No entanto, esse recurso Redux é apenas a ponta do iceberg. Transferir dados para o lugar certo é o mais óbvio dos recursos do Redux. Aqui estão alguns outros benefícios que você obtém da caixa:

conectar é uma função pura

o connect automaticamente torna os componentes conectados “limpos”, ou seja, eles só serão renderizados novamente quando seus acessórios forem alterados - ou seja, quando sua fatia do estado Redux for alterada. Isso evita a renderização desnecessária e acelera o aplicativo.

Depuração fácil com Redux

A cerimônia de escrever ações e redutores é equilibrada pela incrível facilidade de depuração oferecida pelo Redux.

Com a extensão Redux DevTools, você obtém um log automático de todas as ações executadas pelo seu aplicativo. A qualquer momento, você pode abri-lo e ver quais ações foram iniciadas, qual é a carga útil e declarar antes e depois da ação.



Outro ótimo recurso que o Redux DevTools fornece é a depuração usando "viagem no tempo" , ou seja, você pode clicar em qualquer ação anterior e ir para este ponto no tempo, até o atual. A razão pela qual isso funciona é que cada ação atualiza a loja da mesma maneira , para que você possa pegar uma lista das atualizações de estado gravadas e reproduzi-las sem nenhum efeito colateral e terminar no local desejado.

Também existem ferramentas como o LogRocket , que basicamente fornece o Redux DevTools permanente em produção para cada um de seus usuários. Tem um relatório de bug? Não é um problema. Veja esta sessão do usuário no LogRocket e você poderá ver uma repetição do que ele fez e de quais ações foram iniciadas. Tudo isso funciona usando o fluxo de ação do Redux.

Estendendo o Redux com Middleware

O Redux suporta o conceito de middleware (uma palavra sofisticada para "uma função que é executada toda vez que uma ação é enviada"). Escrever seu próprio middleware não é tão difícil quanto pode parecer e permite que você use algumas ferramentas poderosas.

Por exemplo ...

  • Deseja enviar uma solicitação de API sempre que um nome de ação começa com FETCH_? Você pode fazer isso com o middleware.
  • Deseja um local centralizado para registrar eventos em seu software de análise? Middleware é um bom lugar para fazer isso.
  • Deseja impedir que uma ação comece em um momento específico? Você pode fazer isso com o middleware, invisível para o restante do seu aplicativo.
  • Deseja interceptar uma ação que tenha um token JWT e salve-o automaticamente no localStorage? Sim, middleware.

Aqui está um bom artigo com exemplos de como escrever o middleware Redux.

Como usar a API de contexto de reação


Mas talvez você não precise de todas essas esquisitices do Redux. Você pode não precisar de depuração simples, ajuste ou aprimoramentos automáticos de desempenho - tudo o que você quer fazer é transferir dados facilmente. Talvez seu aplicativo seja pequeno ou você só precise fazer algo rapidamente e lidar com as sutilezas mais tarde.

A nova API de contexto é provavelmente ideal para você. Vamos ver como isso funciona.

Publiquei um rápido tutorial da API de contexto no Egghead, se você preferir assistir do que ler (3:43).

Aqui estão três componentes importantes da API de contexto:

  • Função React.createContext que cria contexto
  • Provedor (retorna createContext), que configura o "barramento",
    passando pela árvore de componentes
  • Consumidor (também createContext retornado) que absorve
    "Barramento elétrico" para extração de dados

O provedor é muito semelhante ao provedor no React-Redux. Ele assume um valor que pode ser o que você quiser (pode até ser uma loja Redux ... mas isso seria estúpido). Provavelmente, este é um objeto que contém seus dados e quaisquer ações que você deseja executar com os dados.

O consumidor funciona um pouco como a função de conexão no React-Redux, conectando-se aos dados e disponibilizando-os para um componente que os utiliza.

Aqui estão os destaques:

 //     context //    2 : { Provider, Consumer } // ,   ,  UpperCase,  camelCase //  ,          , //        . const UserContext = React.createContext(); // ,     context, //   Consumer. // Consumer   "render props". const UserAvatar = ({ size }) => ( <UserContext.Consumer> {user => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> )} </UserContext.Consumer> ); // ,      "user prop", //   Consumer    context. const UserStats = () => ( <UserContext.Consumer> {user => ( <div className="user-stats"> <div> <UserAvatar user={user} /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> )} </UserContext.Consumer> ); // ...    ... // ... (      `user`). //  App   context ,  Provider. class App extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { return ( <div className="app"> <UserContext.Provider value={this.state.user}> <Nav /> <Body /> </UserContext.Provider> </div> ); } } 


Código de exemplo CodeSandbox

Vamos ver como isso funciona.

Lembre-se, temos três partes: o próprio contexto (criado usando React.createContext) e dois componentes que interagem com ele (provedor e consumidor).

Fornecedor e Consumidor trabalham juntos

Fornecedor e Consumidor são relacionados e inseparáveis. Eles só sabem como interagir um com o outro. Se você criou dois contextos separados, diga "Contexto1" e "Contexto2", o Contexto do Fornecedor e do Consumidor1 não poderá se comunicar com o Contexto do Fornecedor e do Consumidor2.

O contexto não contém estado

Observe que o contexto não possui seu próprio estado . Este é apenas um canal para seus dados. Você deve passar o valor para o Fornecedor, e esse valor será passado para qualquer Consumidor que souber procurá-lo (o Fornecedor está vinculado ao mesmo contexto que o Consumidor).

Ao criar um contexto, você pode passar o "padrão" da seguinte maneira:

 const Ctx = React.createContext(yourDefaultValue); 


O valor padrão é o que o Consumidor receberá quando for colocado na árvore sem o Fornecedor acima dele. Se você não passar, o valor será indefinido. Observe que esse é o valor padrão , não o valor inicial . O contexto não salva nada; apenas espalha os dados que você passa para ele.

Padrão de adereços de renderização de usos do consumidor

A função connect Redux é um componente de ordem superior (abreviado como HoC). Ele envolve outro componente e passa adereços para ele.

O consumidor, por outro lado, espera que o componente filho seja uma função. Em seguida, ele chama essa função durante a renderização, passando o valor recebido do Provedor em algum lugar acima dele (o valor padrão do contexto ou indefinido se você não passou o valor padrão).

O provedor aceita um valor.

Apenas um valor, como prop. Mas lembre-se de que o valor pode ser qualquer coisa. Na prática, se você deseja passar vários valores para baixo, deve criar um objeto com todos os valores e transmiti-lo.

API de contexto flexível


Como criar contexto nos fornece dois componentes para trabalhar (provedor e consumidor), podemos usá-los como quisermos. Aqui estão algumas idéias.

Envolver o consumidor no HOC

Não gosta da idéia de adicionar UserContext.Consumer em todos os lugares que precisam? Este é o seu código! Você tem o direito de decidir qual será sua melhor escolha.

Se você preferir obter o valor como prop, você pode escrever um pequeno wrapper em torno do Consumer da seguinte maneira:

 function withUser(Component) { return function ConnectedComponent(props) { return ( <UserContext.Consumer> {user => <Component {...props} user={user}/>} </UserContext.Consumer> ); } } 

Depois disso, você pode reescrever, por exemplo, UserAvatar usando a função withUser:

 const UserAvatar = withUser(({ size, user }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> )); 

E pronto, o contexto pode funcionar como conectar o Redux. Menos limpeza automática.

Aqui está um exemplo de CodeSandbox com este HOC.

Manter Estado no Provedor

Lembre-se de que o provedor é apenas um canal. Não salva nenhum dado. Mas isso não impede que você crie seu próprio invólucro para armazenar dados.

No exemplo acima, os dados são armazenados no aplicativo, portanto, a única coisa que você precisava entender era os componentes Fornecedor + Consumidor. Mas talvez você queira criar sua própria loja. Você pode criar um componente para armazenar o estado e transmiti-lo pelo contexto:

 class UserStore extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { return ( <UserContext.Provider value={this.state.user}> {this.props.children} </UserContext.Provider> ); } } // ...    ... const App = () => ( <div className="app"> <Nav /> <Body /> </div> ); ReactDOM.render( <UserStore> <App /> </UserStore>, document.querySelector("#root") ); 

Agora, os dados do usuário estão contidos em seu próprio componente, cuja única tarefa são esses dados. Legal. O aplicativo pode novamente se tornar apátrida. Eu acho que parece um pouco mais limpo.

Aqui está um exemplo do CodeSandbox com esta UserStore.

Jogue ações no contexto

Lembre-se de que o objeto passado pelo provedor pode conter tudo o que você deseja. Isso significa que ele pode conter funções. Você pode até nomear ações.

Aqui está um novo exemplo: uma sala simples com uma chave para mudar a cor de fundo - oh, quero dizer luz.



O estado é armazenado na loja, que também possui uma função de comutação de luz. Estado e função são passados ​​pelo contexto.

 import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; //  context. const RoomContext = React.createContext(); // ,     //   . class RoomStore extends React.Component { state = { isLit: false }; toggleLight = () => { this.setState(state => ({ isLit: !state.isLit })); }; render() { //  state  onToggleLight action return ( <RoomContext.Provider value={{ isLit: this.state.isLit, onToggleLight: this.toggleLight }} > {this.props.children} </RoomContext.Provider> ); } } //    ,    , //       RoomContext. const Room = () => ( <RoomContext.Consumer> {({ isLit, onToggleLight }) => ( <div className={`room ${isLit ? "lit" : "dark"}`}> The room is {isLit ? "lit" : "dark"}. <br /> <button onClick={onToggleLight}>Flip</button> </div> )} </RoomContext.Consumer> ); const App = () => ( <div className="app"> <Room /> </div> ); //     RoomStore, //           . ReactDOM.render( <RoomStore> <App /> </RoomStore>, document.querySelector("#root") ); 

Aqui está um exemplo completo de trabalho no CodeSandbox .

Então, afinal, o que usar, Contexto ou Redux?

Agora que você viu os dois caminhos, qual deles vale a pena usar? Eu sei que você só quer ouvir a resposta para esta pergunta, mas eu tenho que responder - "depende de você".

Depende do tamanho do seu aplicativo agora ou da rapidez com que ele crescerá. Quantas pessoas trabalharão nele - somente você ou uma equipe grande? Qual é a sua experiência com sua equipe no trabalho com os conceitos funcionais nos quais o Redux se baseia (como imutabilidade e recursos puros).

Um erro fatal que permeia todo o ecossistema JavaScript é a ideia de competição . Há uma idéia de que toda escolha é um jogo de soma zero: se você usa a biblioteca A, não deve usar a biblioteca concorrente B. Que, quando uma nova biblioteca sair melhor do que a anterior, ela deve excluir a existente. Que tudo deve ser / ou que você deve escolher o mais novo e o melhor ou ser relegado a segundo plano com desenvolvedores do passado.

A melhor abordagem é olhar para essa maravilhosa escolha com um exemplo, um conjunto de ferramentas. É como escolher entre usar uma chave de fenda ou uma poderosa chave de fenda. Em 80% dos casos, uma chave de fenda fará o trabalho mais fácil e mais rapidamente do que uma chave de fenda. Mas, para os outros 20%, uma chave de fenda seria a melhor opção (não há espaço suficiente ou o item é fino). Quando comprei uma chave de fenda, não joguei imediatamente a chave de fenda, ele não a substituiu, mas simplesmente me deu outra opção. Outra maneira de resolver o problema.

O contexto não "substitui" o Redux, nem reage "substitui" o Angular ou o jQuery. Inferno, eu ainda uso o jQuery quando preciso fazer algo rapidamente. Às vezes, ainda uso modelos EJS do lado do servidor em vez de implantar um aplicativo React. Às vezes, o React é mais do que o necessário para concluir uma tarefa. O mesmo vale para o Redux.

Hoje, se o Redux é mais do que você precisa, você pode usar o contexto.

O Learning React pode ser difícil - existem tantas bibliotecas e ferramentas!

Meu conselho Ignore todos eles :)

Para um tutorial passo a passo, leia meu livro , Pure React .

Source: https://habr.com/ru/post/pt419449/


All Articles