使用Immer管理React应用程序状态

该状态用于组织对来自React应用程序的数据的监视。 用户与应用程序交互时,状态会发生变化。 当用户执行某些操作时,我们需要更新状态,该状态是一组数据,基于这些数据形成了用户在屏幕上看到的内容。 使用setState方法更新React应用程序的状态。



由于状态不应该直接更新(在React中,状态应该是不可变的),因此由于状态结构的复杂性,使用它们进行处理变得非常困难。 即,程序员难以在该状态下导航并在应用程序中使用他的数据。

在这种情况下,您可以使用Immer库。 它在React应用程序中的使用专门用于材料,我们今天将其翻译发布。

在React应用程序中使用Immer的基础


使用Immer时,可以简化React应用程序的状态结构,这意味着使用起来会更容易。 Immer使用所谓的“草稿”概念。 “草稿”可以视为状态的副本,但不能视为状态本身。

Immer照原样通过“按下” CMD + C键来复制状态,然后使用CMD + V键将他复制的内容插入到可以查看复制的数据而不会干扰原始资料的地方。 在“草稿”中更改状态中包含的数据,然后,根据对“草稿”所做的更改,更新应用程序的当前状态。

假设您的应用程序状态如下所示:

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

这是用户数据。 事实证明,该用户正在庆祝他的31岁生日。 这意味着我们需要更新其年龄( age属性)。 如果使用Immer解决此问题,则将首先创建此状态的副本。

现在想象一下,这笔财富的抄本已经制作好了,然后交给了​​快递员,他把这本抄写本交给了坤乐。 这意味着现在有状态的两个副本。 其中之一是应用程序的当前状态,第二个是已传输给用户的“粗糙”副本。 用户在编辑“草稿”时将其年龄更改为31岁。此后,快递员返回更改后的文档并将“草稿”提供给应用程序。 在那里,将对文档的两个版本进行比较,并且仅对用户的年龄进行更改才能更改应用程序的当前状态,因为“草稿”中没有其他更改。

这样的工作方案不会违反状态豁免的思想-当前状态不会直接更新。 通常,可以说Immer的使用仅有助于提高免疫状态的可用性。

范例1:交通信号灯


让我们看一个使用Immer的示例应用程序。 假设您正在开发一个交通信号灯应用程序。 在此应用程序中,您可以尝试使用Immer。

这是该应用程序在其运行时刻之一的屏幕外观。


交通灯应用

在这里您可以找到项目代码。

考虑到项目使用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是从Immer导入的标准功能。 我们将其作为值传递给setState()方法。 produce函数采用一个函数,该函数作为参数采用draft 。 在此功能内,我们可以编辑“草稿”状态,使其呈现为真实状态。

如果这一切对您来说似乎太复杂-这是另一种编写代码的方法,可以解决与上述代码相同的任务。 首先,创建一个函数:

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

对于produce函数,我们作为参数传递应用程序的当前状态和另一个采用draft参数的函数。 现在让我们利用组件中的所有这些功能:

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

示例2:购物清单


如果您使用React已有一段时间,那么您对扩展语法不会感到惊讶。 使用Immer时,无需使用类似的设计。 特别是在处理包含在状态中的数组时。

我们将继续探索Immer的可能性,创建一个实现购物清单的应用程序。


购物清单

在这里您可以尝试一下。

这是我们正在使用的组件。

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

将新的购物笔记添加到列表中时,我们需要更新组件的状态,在该状态下,应在list数组中保存新元素。 为了使用setState()方法更新list项, setState()需要以下代码:

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

如果在应用程序运行期间需要更新许多状态元素-则必须非常频繁地使用扩展语法。 通过将状态中已存在的内容与新数据进行组合来获得新状态。 随着变更数量的增加,工作变得更加复杂。 如果要使用Immer-这样的事情不会造成困难。 您可以通过查看本节开头的示例代码来验证这一点。

但是,如果我们想在项目中添加一个以回调形式在更新状态后被调用的函数,该怎么办? 例如,如果您需要计算列表中的条目数或所有计划购买的总成本,则可能有必要。

在这里,您可以查看应用程序的代码,我们现在将对其进行分析。 其界面如下所示。


具有计算计划购买总成本功能的应用程序

因此,假设我们要计算计划购买的总价值。 让我们从创建状态更新机制开始。 该机制由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)    }  ) }; 

handleSubmit函数中handleSubmit我们首先根据用户输入的数据创建一个对象。 对对象的引用将写入常量newItem 。 为了形成应用程序的新状态,使用了.concat()方法。 在数组上调用此方法,将返回一个新数组,其中包括原始数组的元素以及一个新元素。 新的数组将被写入draft.list 。 之后,Immer可以更新应用程序的状态。

状态更新后将调用回调, calculateAmount函数。 重要的是要注意,此功能使用状态的更新版本。

calculateAmount函数将如下所示:

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

钩浸


Use-immer是一个钩子,允许开发人员控制React应用程序的状态。 让我们通过在其基础上实现经典计数器应用程序来查看此挂钩的工作方式:

 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; 

useImmer函数与useState方法非常相似。 该函数返回一个状态和一个更新状态的函数。 首次加载组件时,状态的内容(在这种情况下为count属性)对应于传递给useImmer的值。 使用返回的函数更新状态可以使我们创建一个increment函数,以递增count状态属性的值。

这是将钩子用于Immer的代码,让人想起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; 

useImmerReducer函数接受useImmerReducer函数和初始状态。 它返回状态和dispatch功能。 之后,您可以绕过状态以在其中显示元素。 当将新项目添加到待办事项列表中并清除该列表时,将执行使用dispatch功能的发送动作。 将为要发送的动作分配一种类型,基于该类型,在减速器功能中会做出决定,确切地需要执行什么以处理特定动作。

在reducer中,我们像以前一样使用draft实体,而不是state 。 因此,我们有了一种方便的方法来管理应用程序的状态。

在前面的示例中使用的代码可以在这里找到。

总结


在本文中,我们讨论了Immer,它是一个简化React应用程序状态管理的库。 本文作者认为,对此库感兴趣的每个人都可以在其新应用程序中使用Immer,也可以将其缓慢地引入当前项目之一。

在这里 ,您可以找到有关Immer的一些详细信息。

亲爱的读者们! 您打算使用Immer吗?

Source: https://habr.com/ru/post/zh-CN463337/


All Articles