5 práticas recomendadas para usar ganchos React na produção

O autor do artigo, cuja tradução estamos publicando hoje, diz que os commercetools adotaram os ganchos React no início de 2019 - no momento em que apareceram no React 16.8.0. Desde então, os programadores da empresa processam constantemente seu código, traduzindo-o em ganchos. Os ganchos do React permitem que você use o estado do componente e use outros recursos do React sem usar classes. Usando ganchos, você pode, ao trabalhar com componentes funcionais, "conectar-se" aos eventos do ciclo de vida do componente e responder a alterações em seu estado.



Brevemente sobre os resultados da implementação de ganchos


Se, em poucas palavras, falamos sobre o que os ganchos nos deram, aconteceu que eles ajudaram a simplificar a base de código extraindo a lógica dos componentes e facilitando a composição de diferentes recursos. Além disso, o uso de ganchos levou ao fato de que aprendemos muito. Por exemplo, como estruturar melhor seu código por meio da melhoria contínua da funcionalidade existente. Temos certeza de que aprenderemos muitas coisas mais interessantes, continuando a trabalhar com os ganchos do React.

Pontos fortes da implementação do gancho


Aqui está o que a introdução de ganchos nos deu:

  • Melhorando a legibilidade do código. Isso é possível graças ao uso de árvores de componentes menores do que antes. A criação dessas árvores de componentes é facilitada pelo nosso desejo de abandonar propriedades de renderização e componentes de ordem superior.
  • Melhorando os recursos de depuração de código. Tínhamos à nossa disposição uma representação visual aprimorada do código e informações adicionais de depuração fornecidas pelas ferramentas de desenvolvedor do React.
  • Melhorando a modularidade do projeto. Devido à natureza funcional dos ganchos, o processo de criação e aplicação de lógica adequada para uso repetido foi simplificado.
  • Separação de responsabilidades. Os componentes de reação são responsáveis ​​pela aparência dos elementos da interface e os ganchos fornecem acesso à lógica do programa encapsulado.

Agora, queremos compartilhar nossas recomendações sobre o uso de ganchos na produção.

1. Recupere oportunamente os ganchos dos componentes e crie ganchos personalizados


Começar a usar ganchos em componentes funcionais é bastante fácil. Por exemplo, rapidamente nos inscrevemos em React.useStatealgum lugar , em algum lugar - React.useEffecte continuamos a fazer nosso trabalho. Essa abordagem, no entanto, não permite tirar o máximo proveito de todas as oportunidades que os ganchos são capazes de oferecer. Apresentando React.useStatena forma de um pequeno use<StateName>State, e React.useEffect- na forma use<EffectName>Effect, conseguimos o seguinte:

  1. Os componentes são menores do que os ganchos comuns.
  2. Os ganchos podem ser reutilizados em vários componentes.
  3. , React, , . , State<StateName> State. React.useState, .

A recuperação de ganchos também contribui para o fato de que a lógica reutilizável e compartilhada é mais visível em várias partes do aplicativo. Lógica semelhante ou duplicada é mais difícil de perceber se você usar apenas ganchos embutidos no código do componente. Os ganchos resultantes podem ser pequenos e conter muito pouco código useToggleState. Por outro lado, ganchos maiores, como useProductFetcher, agora podem incluir mais funcionalidades. Ambos nos ajudaram a simplificar a base de código, reduzindo o tamanho dos componentes do React.

O exemplo a seguir cria um pequeno gancho React projetado para controlar a seleção de elementos. Os benefícios de encapsular essa funcionalidade tornam-se aparentes imediatamente após você perceber com que frequência essa lógica é usada no aplicativo. Por exemplo, para selecionar um conjunto de pedidos em uma lista.

//  

function Component() {
    const [selected, setSelected] = React.useState()
    const select = React.useCallback(id => setSelected(/** ... */), [
        selected,
        setSelect
    ])

    return <List selectedIds={selected} onSelect={id => select(id)} />
}

//   

const useSelection = () => {
    const [selected, setSelected] = React.useState()
    const select = React.useCallback(id => setSelected(/** ... */), [
        selected,
        setSelect
    ])

    return [selected, select]
}

function Component() {
    const [selected, select] = useSelection()

    return <List selectedIds={selected} onSelect={id => select(id)} />
}

2. Sobre os benefícios do React.useDebugValue


O gancho React.useDebugValue padrão refere-se aos recursos pouco conhecidos do React. Esse gancho pode ajudar o desenvolvedor durante a depuração do código; pode ser útil para usá-lo em ganchos projetados para compartilhamento. Esses são ganchos de usuário usados ​​em muitos componentes do aplicativo. No entanto, não é useDebugValuerecomendável usá-lo em todos os ganchos do usuário, pois os ganchos internos já registram valores de depuração padrão.

Imagine criar um gancho personalizado projetado para tomar uma decisão sobre a necessidade de ativar alguns recursos do aplicativo. Os dados do estado do aplicativo nos quais a decisão se baseia podem vir de diferentes fontes. Eles podem ser armazenados em um objeto de contexto React, cujo acesso é organizado React.useContext.

Para ajudar o desenvolvedor na depuração, enquanto trabalha com as ferramentas do desenvolvedor do React, pode ser útil conhecer o nome do sinalizador analisado ( flagName) e a variante do valor do sinalizador ( flagVariation). Nesse caso, o uso de um pequeno gancho pode nos ajudar React.useDebugValue:

export default function useFeatureToggle(flagName, flagVariation = true) {
    const flags = React.useContext(FlagsContext)
    const isFeatureEnabled = getIsFeatureEnabled(flagName, flagVariation)(flags)

    React.useDebugValue({
        flagName,
        flagVariation,
        isEnabled: isFeatureEnabled
    })

    return isFeatureEnabled
}

Agora, trabalhando com as ferramentas do desenvolvedor do React, podemos ver informações sobre a opção do valor do sinalizador, sobre o nome do sinalizador e se o recurso de nosso interesse está ou não ativado.


Trabalhando com as ferramentas do desenvolvedor do React após a aplicação do gancho React.useDebugValue

Observe que nos casos em que os ganchos do usuário usam ganchos padrão comoReact.useStateouReact.useRef, esses ganchos já registram o status correspondente ou os dados do objeto ref. Como resultado, o uso de construções de exibição aquiReact.useDebugValue({ state })não é particularmente útil.

3. A combinação e composição de ganchos


Quando começamos a implementar ganchos em nosso trabalho e começamos a usar cada vez mais ganchos em componentes, rapidamente descobrimos que 5 a 10 ganchos poderiam estar presentes no mesmo componente. Nesse caso, ganchos de vários tipos foram usados. Por exemplo, podemos usar 2-3 ganchos React.useState, depois um gancho React.useContext(por exemplo, para obter informações sobre o usuário ativo), um gancho React.useEffecte também ganchos de outras bibliotecas, como react-router ou react-intl .

O acima foi repetido várias vezes, como resultado, mesmo componentes muito pequenos não eram tão compactos. Para evitar isso, começamos a extrair esses ganchos individuais em ganchos de usuário, cujo dispositivo dependia do componente ou de algum recurso do aplicativo.

Imagine um mecanismo de aplicativo destinado a criar um pedido. Ao desenvolver esse mecanismo, muitos componentes foram usados, além de ganchos de vários tipos. Esses ganchos podem ser combinados como um gancho personalizado, aumentando a usabilidade dos componentes. Aqui está um exemplo de combinação de um conjunto de pequenos ganchos em um gancho.

function OrderCreate() {
    const {
        orderCreater,
        orderFetcher,
        applicationContext,
        goToNextStep,
        pendingOrder,
        updatePendingChanges,
        revertPendingChanges
    } = useOrderCreate()

    return (/** ...children */)
}

4. Comparação de React.useReducer e React.useState


Costumamos recorrer React.useStatecomo um gancho padrão para trabalhar com o estado dos componentes. No entanto, com o tempo, o estado do componente pode precisar ser complicado, o que depende dos novos requisitos para o componente, como a presença de vários valores no estado. Em certos casos, o uso React.useReducerpode ajudar a evitar a necessidade de usar vários valores e simplificar a lógica de atualização do estado. Imagine o gerenciamento de estado ao fazer solicitações HTTP e receber respostas. Para fazer isso, você pode precisar de trabalhar com valores tais como isLoading, dataeerror. Em vez disso, o estado pode ser controlado usando um redutor, tendo à sua disposição várias ações para gerenciar alterações de estado. Essa abordagem, idealmente, incentiva o desenvolvedor a perceber o estado da interface na forma de uma máquina de estado.

O redutor transmitido React.useReduceré semelhante aos redutores usados ​​no Redux , onde o sistema recebe o estado atual da ação e deve retornar o próximo estado. A ação contém informações sobre seu tipo, bem como dados com base nos quais o próximo estado é formado. Aqui está um exemplo de um redutor simples projetado para controlar um determinado contador:

const initialState = 0;
const reducer = (state, action) => {
    switch (action) {
        case 'increment': return state + 1;
        case 'decrement': return state - 1;
        case 'reset': return 0;
        default: throw new Error('Unexpected action');
    }
};

Este redutor pode ser testado isoladamente e depois usado no componente usando React.useReducer:

function Component() {
    const [count, dispatch] = React.useReducer(reducer, initialState);

    return (
        <div>
            {count}
            <button onClick={() => dispatch('increment')}>+1</button>
            <button onClick={() => dispatch('decrement')}>-1</button>
            <button onClick={() => dispatch('reset')}>reset</button>
        </div>
    );
};

Aqui podemos aplicar o que examinamos nas três seções anteriores, ou seja, podemos extrair tudo useCounterReducer. Isso melhorará o código ocultando informações do tipo de ação de um componente que descreve a aparência de um elemento da interface. Como resultado, isso ajudará a evitar o vazamento de detalhes de implementação no componente e também nos dará oportunidades adicionais para código de depuração. Veja como será o gancho personalizado resultante e o componente que o usa:

const CounterActionTypes = {
    Increment: 'increment',
    Decrement: 'decrement',
    Reset: 'reset',
}

const useCounterReducer = (initialState) => {
    const [count, dispatch] = React.useReducer(reducer, initialState);

    const increment = React.useCallback(() => dispatch(CounterActionTypes.Increment));
    const decrement = React.useCallback(() => dispatch(CounterActionTypes.Decrement));
    const reset = React.useCallback(() => dispatch(CounterActionTypes.Reset));

    return {
        count,
        increment,
        decrement
    }
}

function Component() {
    const {count, increment} = useCounterReducer(0);

    return (
        <div>
            {count}
            <button onClick={increment}>+1</button>
        </div>
    );
};

5. Implementação gradual de ganchos


À primeira vista, a ideia de introduzir gradualmente ganchos pode não parecer inteiramente lógica, mas aqui sugiro que aqueles que pensam assim sigam simplesmente o meu raciocínio. Com o tempo, vários padrões encontram aplicação na base de código. No nosso caso, esses padrões incluem componentes de ordem superior, propriedades de renderização e agora ganchos. Ao traduzir um projeto para um novo padrão, os desenvolvedores não procuram reescrever instantaneamente todo o código, o que, em geral, é quase impossível. Como resultado, você precisa desenvolver um plano para transferir o projeto para os ganchos do React, o que não fornece grandes alterações no código. Essa tarefa pode ser muito difícil, pois fazer alterações no código geralmente leva a um aumento no tamanho e na complexidade. Ao introduzir ganchos, nos esforçamos para evitar isso.

Nossa base de código usa componentes baseados em classe e componentes funcionais. Independentemente de qual componente foi usado em uma determinada situação, procuramos compartilhar a lógica através dos ganchos React. Primeiro, implementamos a lógica nos ganchos (ou repetimos a implementação existente de certos mecanismos neles) e depois criamos pequenos componentes de ordem superior, dentro dos quais esses ganchos são usados. Depois disso, esses componentes de ordem superior são usados ​​para criar componentes baseados em classe. Como resultado, temos à nossa disposição a lógica localizada em um único local, que pode ser usado em componentes de vários tipos. Aqui está um exemplo de implementação da funcionalidade de gancho em componentes através de componentes de ordem superior.

export const injectTracking = (propName = 'tracking') => WrappedComponent => {
    const WithTracking = props => {
        const tracking = useTracking();

        const trackingProp = {
            [propName]: tracking,
        };

        return <WrappedComponent {...props} {...trackingProp} />;
    };

    WithTracking.displayName = wrapDisplayName(WrappedComponent, 'withTracking');

    return WithTracking;
};

export default injectTracking;

Isso mostra a implementação da funcionalidade do gancho useTrackingem um componente WrappedComponent. A propósito, isso nos permitiu, entre outras coisas, separar as tarefas de implementar ganchos e reescrever testes nas partes antigas do sistema. Com essa abordagem, ainda temos à disposição mecanismos para o uso de ganchos em todas as partes da base de código.

Sumário


Aqui estão alguns exemplos de como o uso de ganchos React melhorou nossa base de código. Esperamos que os ganchos também possam beneficiar seu projeto.

Queridos leitores! Você usa ganchos React?


All Articles