Usando o Immer para gerenciar o status do aplicativo React

O estado é usado para organizar o monitoramento de dados dos aplicativos React. Os estados mudam à medida que os usuários interagem com os aplicativos. Quando o usuário executa alguma ação, precisamos atualizar o estado, que é um conjunto de dados com base no qual o que o usuário vê na tela é formado. Atualize o estado dos aplicativos React usando o método setState .



Como os estados não devem ser atualizados diretamente (em React, o estado deve ser imutável), com a complicação da estrutura dos estados, trabalhar com eles se transforma em uma tarefa não trivial. Nomeadamente, fica difícil para o programador navegar no estado e usar seus dados no aplicativo.

Nessas situações, você pode usar a biblioteca Immer. Seu uso nas aplicações React é dedicado ao material, cuja tradução publicamos hoje.

Noções básicas de uso do Immer em aplicativos de reação


Ao usar o Immer, a estrutura de estado de um aplicativo React pode ser simplificada, o que significa que será mais fácil trabalhar com ele. Immer usa o conceito de "rascunho". Um "rascunho" pode ser tomado como uma cópia do estado, mas não o próprio estado.

O Immer, por assim dizer, copia o estado “pressionando” as teclas CMD + C e, usando as teclas CMD + V, insere o que ele copiou em um local onde os dados copiados podem ser visualizados sem perturbar os materiais originais. A alteração dos dados incluídos no estado é feita no "rascunho", após o qual, com base nas alterações feitas no "rascunho", o estado atual do aplicativo é atualizado.

Suponha que o estado do seu aplicativo tenha esta aparência:

this.state = {   name: 'Kunle',   age: 30,   city: 'Lagos',   country: 'Nigeria' } 

Aqui estão os dados do usuário. Este usuário, como se viu, está comemorando seu 31º aniversário. Isso significa que precisamos atualizar sua idade (propriedade de age ). Se você usar o Immer para resolver esse problema, uma cópia desse estado será criada primeiro.

Agora imagine que uma cópia da fortuna foi feita, foi entregue ao correio e ele a entregou a Kunle. Isso significa que agora existem duas cópias do estado. Um deles é o estado atual do aplicativo e o segundo é uma cópia "aproximada" que foi transferida para o usuário. O usuário, editando o "rascunho", muda sua idade para 31. Depois disso, o correio retorna com o documento alterado e entrega o "rascunho" ao aplicativo. Lá, é feita uma comparação de duas versões do documento e apenas são feitas alterações em relação à idade do usuário no estado atual do aplicativo, pois nada mudou no rascunho.

Esse esquema de trabalho não viola a idéia de imunidade estatal - o estado atual não é diretamente atualizado. Em geral, podemos dizer que o uso de Immer simplesmente ajuda a melhorar a usabilidade do estado imunológico.

Exemplo nº 1: semáforo


Vamos dar uma olhada em um exemplo de aplicativo de trabalho que usa o Immer. Suponha que você esteja desenvolvendo um aplicativo de semáforo. Nesta aplicação, você pode tentar usar o Immer.

Aqui está a aparência da tela deste aplicativo em um dos momentos de sua operação.


Aplicação de semáforo

Aqui você pode encontrar o código do projeto.

Aqui está a aparência do componente, já que o projeto usa o Immer.

 const {produce} = immer class App extends React.Component {  state = {    red: 'red',    yellow: 'black',    green: 'black',    next: "yellow"  }  componentDidMount() {    this.interval = setInterval(() => this.changeHandle(), 3000);  }   componentWillUnmount() {    clearInterval(this.interval);  }  handleRedLight = () => {    this.setState(      produce(draft => {        draft.red = 'red';        draft.yellow = 'black';        draft.green = 'black';        draft.next = 'yellow'      })    )  }   handleYellowLight = () => {    this.setState(      produce(draft => {        draft.red = 'black';        draft.yellow = 'yellow';        draft.green = 'black';        draft.next = 'green'      })    )  }   handleGreenLight = () => {    this.setState(      produce(draft => {        draft.red = 'black';        draft.yellow = 'black';        draft.green = 'green';        draft.next = 'red'      })    )  }  changeHandle = () => {    if (this.state.next === 'yellow') {      this.handleYellowLight()    } else if (this.state.next === 'green') {      this.handleGreenLight()    } else {      this.handleRedLight()    }     }  render() {    return (      <div className="box">        <div className="circle" style={{backgroundColor: this.state.red}}></div>        <div className="circle" style={{backgroundColor: this.state.yellow}}></div>        <div className="circle" style={{backgroundColor: this.state.green}}></div>      </div>  ); } }; 

Produce é um recurso padrão importado do Immer. Passamos, como um valor, para o método setState() . A função produce assume uma função que, como argumento, aceita draft . É nessa função que podemos editar o estado "rascunho", trazendo-o para a forma que deve assumir um estado real.

Se tudo isso parece muito complicado para você - aqui está outra abordagem para escrever código que resolve as mesmas tarefas que o código acima. Primeiro, crie uma função:

 const handleLight = (state) => {  return produce(state, (draft) => {    draft.red = 'black';    draft.yellow = 'black';    draft.green = 'green';    draft.next = 'red'  }); } 

Para a função produce , como argumentos, passamos o estado atual do aplicativo e outra função que recebe o argumento de draft . Agora vamos aproveitar tudo isso no componente:

 handleGreenLight = () => {  const nextState = handleLight(this.state)  this.setState(nextState) } 

Exemplo 2: lista de compras


Se você trabalha com o React há algum tempo, não deve se surpreender com a sintaxe de propagação . Ao usar o Immer, você não precisa usar modelos semelhantes. Em particular, ao trabalhar com matrizes contidas em um estado.

Continuaremos a explorar as possibilidades do Immer, criando um aplicativo que implementa uma lista de compras.


Lista de compras

Aqui você pode experimentar.

Aqui está o componente com o qual estamos trabalhando.

 class App extends React.Component {  constructor(props) {      super(props)           this.state = {        item: "",        price: 0,        list: [          { id: 1, name: "Cereals", price: 12 },          { id: 2, name: "Rice", price: 10 }        ]      }    }    handleInputChange = e => {      this.setState(      produce(draft => {        draft[event.target.name] = event.target.value      }))    }    handleSubmit = (e) => {      e.preventDefault()      const newItem = {        id: uuid.v4(),        name: this.state.name,        price: this.state.price      }      this.setState(        produce(draft => {          draft.list = draft.list.concat(newItem)        })      )    };  render() {    return (      <React.Fragment>        <section className="section">          <div className="box">            <form onSubmit={this.handleSubmit}>              <h2>Create your shopping list</h2>              <div>                <input                  type="text"                  placeholder="Item's Name"                  onChange={this.handleInputChange}                  name="name"                  className="input"                  />              </div>              <div>                <input                  type="number"                  placeholder="Item's Price"                  onChange={this.handleInputChange}                  name="price"                  className="input"                  />              </div>              <button className="button is-grey">Submit</button>            </form>          </div>                   <div className="box">            {              this.state.list.length ? (                this.state.list.map(item => (                  <ul>                    <li key={item.id}>                      <p>{item.name}</p>                      <p>${item.price}</p>                    </li>                    <hr />                  </ul>                ))              ) : <p>Your list is empty</p>            }          </div>        </section>      </React.Fragment>    )  } } ReactDOM.render(  <App />,  document.getElementById('root') ); 

Ao adicionar novas notas de compras à lista, precisamos atualizar o estado do componente no qual, na matriz da list , novos elementos devem ser salvos. Para atualizar o item da list usando o método setState() , setState() precisa do seguinte código:

 handleSubmit = (e) => {  e.preventDefault()  const newItem = {    id: uuid.v4(),    name: this.state.name,    price: this.state.price  }  this.setState({ list: [...this.state.list, newItem] }) }; 

Se durante a operação do aplicativo você precisar atualizar muitos elementos de estado - a sintaxe de propagação terá que ser usada com muita frequência. Um novo estado é obtido combinando o que já está no estado com novos dados. À medida que o número de mudanças aumenta, o trabalho se torna mais complicado. Se usar Immer - essas coisas não causam dificuldades. Você pode verificar isso consultando o código de exemplo no início desta seção.

Mas e se quisermos adicionar uma função ao projeto que, na forma de um retorno de chamada, será chamado após a atualização do estado? Por exemplo, isso pode ser necessário se você precisar contar o número de entradas na lista ou o custo total de todas as compras planejadas.

Aqui você pode dar uma olhada no código do aplicativo, que iremos analisar agora. Sua interface é mostrada abaixo.


Aplicativo com a função de calcular o custo total das compras planejadas

Portanto, suponha que desejamos calcular o valor total das compras planejadas. Vamos começar criando um mecanismo de atualização de estado. Este mecanismo é representado pela função handleSubmit :

 handleSubmit = (e) => {  e.preventDefault()  const newItem = {    id: uuid.v4(),    name: this.state.name,    price: this.state.price  }   this.setState(    produce(draft => {      draft.list = draft.list.concat(newItem)    }), () => {      this.calculateAmount(this.state.list)    }  ) }; 

Na função handleSubmit primeiro criamos um objeto com base nos dados inseridos pelo usuário. A referência ao objeto é gravada na constante newItem . Para formar um novo estado do aplicativo, o método .concat() é usado. Esse método, chamado em uma matriz, retorna uma nova matriz, que inclui elementos da matriz original, bem como um novo elemento. A nova matriz é gravada em draft.list . Depois disso, o Immer pode atualizar o estado do aplicativo.

O retorno de chamada, a função calculateAmount , é chamado após uma atualização de estado. É importante observar que essa função usa uma versão atualizada do estado.

A função calculateAmount terá a seguinte aparência:

 calculateAmount = (list) => {  let total = 0;    for (let i = 0; i < list.length; i++) {      total += parseInt(list[i].price, 10)    }  this.setState(    produce(draft => {      draft.totalAmount = total    })  ) } 

Hooks immer


Use-immer é um gancho que permite que um desenvolvedor controle o estado dos aplicativos React. Vejamos como esse gancho funciona implementando o aplicativo de contador clássico em sua base:

 import React from "react"; import {useImmer} from "use-immer"; const Counter = () => {  const [count, updateCounter] = useImmer({    value: 0  });  function increment() {    updateCounter(draft => {      draft.value = draft.value +1;    });  }  return (    <div>      <h1>        Counter {count.value}      </h1>      <br />      <button onClick={increment}>Increment</button>    </div>  ); } export default Counter; 

A função useImmer muito semelhante ao método useState . A função retorna um estado e uma função que atualiza o estado. Quando o componente é carregado pela primeira vez, o conteúdo do estado (nesse caso, a propriedade count ) corresponde ao valor passado para useImmer . O uso da função retornada para atualizar o estado nos permite criar uma função de incremento que incrementa o valor da propriedade do estado da count .

E aqui está o código que usa o gancho para o Immer, remanescente do useReducer :

 import React, { useRef } from "react"; import {useImmerReducer } from "use-immer"; import uuidv4 from "uuid/v4" const initialState = []; const reducer = (draft, action) => {  switch (action.type) {    case "ADD_ITEM":      draft.push(action.item);      return;    case "CLEAR_LIST":      return initialState;    default:      return draft;  } } const Todo = () => {  const inputEl = useRef(null);  const [state, dispatch] = useImmerReducer(reducer, initialState);   const handleSubmit = (e) => {    e.preventDefault()    const newItem = {      id: uuidv4(),      text: inputEl.current.value    };    dispatch({ type: "ADD_ITEM", item: newItem });    inputEl.current.value = "";    inputEl.current.focus();  }   const handleClear = () => {    dispatch({ type: 'CLEAR_LIST' })  }   return (    <div className='App'>      <header className='App-header'>        <ul>          {state.map(todo => {            return <li key={todo.id}>{todo.text}</li>;          })}        </ul>        <form onSubmit={handleSubmit}>          <input type='text' ref={inputEl} />          <button            type='submit'          >            Add Todo          </button>        </form>        <button          onClick={handleClear}        >          Clear Todos        </button>      </header>    </div>  ); } export default Todo; 

A função useImmerReducer aceita a função useImmerReducer e o estado inicial. Retorna a função de estado e dispatch . Depois disso, você pode ignorar o estado para exibir os elementos nele. As ações de envio usando a função de dispatch são executadas quando um novo item é adicionado à lista de tarefas e quando a lista é limpa. A ação a ser enviada recebe um tipo com base no qual uma decisão é tomada na função redutora sobre o que exatamente precisa ser feito para processar uma ação específica.

No redutor, usamos, como antes, uma entidade de draft , não um state . Graças a isso, temos uma maneira conveniente de gerenciar o estado do aplicativo.

O código usado no exemplo anterior pode ser encontrado aqui .

Sumário


Neste artigo, falamos sobre o Immer, uma biblioteca que simplifica o gerenciamento do estado dos aplicativos React. O autor do artigo acredita que todos os interessados ​​nesta biblioteca podem usar o Immer em seus novos aplicativos ou introduzi-lo lentamente em um dos projetos atuais.

Aqui está o material onde você pode encontrar alguns detalhes sobre o Immer.

Caros leitores! Você planeja usar o Immer?

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


All Articles